Click here to Skip to main content
15,881,882 members
Articles / Programming Languages / C# 5.0

Loading Indicator for Content Controls

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
26 Aug 2014CPOL3 min read 7.4K   3  
Loading indicator for content controls

With C# 5 (.NET 4.5), it became a lot easier to create asynchronous methods. There’s also a great MSDN article on how to leverage this using MVVM so that you can have properties update the view when they’ve finished loading. What I wanted was a simple control that would indicate to the user that the content was being fetched (via binding to the NotifyTaskCompletion.IsNotCompleted property).

What I wanted though was a simple attached property on a ContentControl (or HubSection in my case but it’s easy to modify for other control types) and it would switch to the loading indicator based on the bound value, as I didn’t want to have to modify lots of DataTemplates to add the indicator to them and then mess around with the visibility of the content/indicator based on if the value was being loaded or not.

The actual visual part of the control is straight forward:

C#
public sealed class AsyncIndicator : Control
{
    protected override Size MeasureOverride(Size availableSize)
    {
        Size desiredSize = base.MeasureOverride(availableSize);

        return new Size(
            GetSize(availableSize.Width, desiredSize.Width),
            GetSize(availableSize.Height, desiredSize.Height));
    }

    private static double GetSize(double available, double desired)
    {
        return double.IsPositiveInfinity(available) ? desired : available;
    }
}

All this does is make the control take all the available size of the parent, taking into account if the parent provides us with an infinite amount of space (e.g. StackPanel). Here’s the simple template for it, which I tuck away inside Generic.xaml:

XML
<Style TargetType="controls:AsyncIndicator">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="controls:AsyncIndicator">
                <Border Background="#33888888">
                    <StackPanel HorizontalAlignment="Center"
                                VerticalAlignment="Center">
                        <ProgressRing HorizontalAlignment="Center"
                                      IsActive="True" />

                        <TextBlock Style="{ThemeResource BodyTextBlockStyle}"
                                   Text="Loading" />
                    </StackPanel>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Like I said, nothing complex about this nor nothing that would really warrant a new control; it’s just a spinning progress (new to WinRT) with a label. However, I’m also going to store the attached property in this control. How the attached property works is when it’s set to true, it will replace the ContentTemplate property of the attached control to one which just contains the loading indicator; when it’s set to false, it restores the original value to the attached control. Therefore, I’m going to use two dependency properties, a public one for the attached property and a private one for storing the current value of the ContentTemplate before it is overwritten.

C#
public static readonly DependencyProperty IsLoadingProperty =
    DependencyProperty.RegisterAttached("IsLoading", typeof(bool), 
    typeof(AsyncIndicator), new PropertyMetadata(false, OnIsLoadingChanged));

private static readonly DependencyProperty OriginalTemplateProperty =
    DependencyProperty.RegisterAttached("OriginalTemplate", 
    typeof(TemplateData), typeof(AsyncIndicator), new PropertyMetadata(null));

public static bool GetIsLoading(DependencyObject obj)
{
    return (bool)obj.GetValue(IsLoadingProperty);
}

public static void SetIsLoading(DependencyObject obj, bool value)
{
    obj.SetValue(IsLoadingProperty, value);
}

private static void OnIsLoadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if ((bool)e.NewValue)
    {
        TemplateData data = BackupTemplateProperty(d);
        if (data != null)
        {
            SetIndicator(d, data);
        }
    }
    else
    {
        RestoreTemplateProperty(d);
    }
}

private class TemplateData
{
    public DependencyProperty Property { get; set; }

    public object Template { get; set; }
}

The reason for the private TemplateData nested class is to allow for different properties of different controls to be backed up, replaced and restored (at the moment, I need ContentControl and HubSection, which don’t share the same ContentTemplate property). Here’s the actual code which switches out the current template and replaces it with one that just contains the new control:

C#
private static DependencyProperty GetTemplateProperty(object element)
{
    if (element is ContentControl)
    {
        return ContentControl.ContentTemplateProperty;
    }
    else if (element is HubSection)
    {
        return HubSection.ContentTemplateProperty;
    }

    return null;
}

private static TemplateData BackupTemplateProperty(DependencyObject d)
{
    TemplateData data = null;

    DependencyProperty templateProperty = GetTemplateProperty(d);
    if (templateProperty != null)
    {
        data = new TemplateData();
        data.Property = templateProperty;
        data.Template = d.GetValue(templateProperty);
    }

    d.SetValue(OriginalTemplateProperty, data);
    return data;
}

private static void RestoreTemplateProperty(DependencyObject d)
{
    var data = (TemplateData)d.GetValue(OriginalTemplateProperty);
    if (data != null)
    {
        d.SetValue(data.Property, data.Template);
        d.ClearValue(OriginalTemplateProperty);
    }
}

private static void SetIndicator(DependencyObject d, TemplateData data)
{
    // Note: The namespace must match the namespace this class belongs to.
    const string IndicatorDataTemplate =
@"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
                xmlns:local='using:PurplePhobos.View.Controls'>
    <local:AsyncIndicator />
</DataTemplate>";

    d.SetValue(data.Property, XamlReader.Load(IndicatorDataTemplate));
}

To keep the code generalised, it tries to find a suitable dependency property based on the type of the control it is being attached to. If it finds a property, it then saves the current value of it to our private attached property (this also works fine for static resources, however, it won’t work for bindings, but there shouldn’t be much need for a binding of a DataTemplate). A new DataTemplate is created from a string that contains the new control – note if you use this code and change the namespace of where the class is, be sure to update the string as well to the new namespace! Finally, restoring is a lot easier, the control just needs to check if a template property has been backed up to the private attached property and, if so, set it back (clearing the OriginalTemplateProperty to reduce resources).

The full class can be found here – you’ll just need to style it somewhere in your app. Usage is something like this:

C#
<HubSection ctrls:AsyncIndicator.IsLoading="{Binding Property.IsNotCompleted}"
            ContentTemplate="{StaticResource ExampleTemplate}" />

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --