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:

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:

<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.

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:

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:

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

Defining MessageDialogs in XAML

I’m a fan of the MVVM pattern and recently needed to present a dialog (actually a MessageDialog in WinRT) to the user if they were about to leave the data entry window but hadn’t saved their changes. I initially thought that the view model would need to create a dialog, however, I thought about it some more and came to the conclusion that actually the view should be responsible for this – the view model only needs to let the view know that it has unsaved changes and provide commands to the view (which will expose them to the user) for it to either save and close or close without saving.

So the next thing I needed was to be able to define a dialog in XAML, as I wanted to bind the buttons on the dialog to the commands in the view model. As well as defining it in the XAML, I wanted to either launch it in response to the user clicking a button or, if the dialog isn’t required (i.e. there are no unsaved changes in my scenario) make the button perform the default action without prompting the user. To do this I’ve used an attached property which, when set on a button, will subscribe to the Click event and show the dialog in the handler, if required, or perform the default action if the PromptUser property is set to false. The resulting XAML looks something like this (note that the MessageDialogTemplate can be moved to a resource, I’ve put it inline here to make the code snippet shorter and easier to follow):

<AppBarButton Icon="Cancel"
              Label="Close">
    <dlg:MessageDialogTemplate.ShowOnClick>
        <dlg:MessageDialogTemplate Content="You have unsaved changes to the document."
                                   ShowDialog="{Binding HasUnsavedChanges}">
            <dlg:DialogCommand Command="{Binding SaveAndClose}"
                               Label="Save changes" />
							   
            <dlg:DialogCommand Command="{Binding Cancel}"
                               Label="Discard changes" />

            <!-- If no command is specified then the button will -->
            <!-- do dismiss the dialog without triggering any action. -->
            <dlg:DialogCommand Label="Cancel" />
        </dlg:MessageDialogTemplate>
    </dlg:MessageDialogTemplate.ShowOnClick>
</AppBarButton>

So, that’s it’s usage. For the above there are two classes required:

namespace DialogHelper
{
    using System.Windows.Input;
    using Windows.UI.Popups;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Markup;

    [ContentProperty(Name = "Label")]
    public sealed class DialogCommand : FrameworkElement
    {
        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.Register("CommandParameter", typeof(object), typeof(DialogCommand), new PropertyMetadata(null));

        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand), typeof(DialogCommand), new PropertyMetadata(null));

        public static readonly DependencyProperty LabelProperty =
            DependencyProperty.Register("Label", typeof(string), typeof(DialogCommand), new PropertyMetadata(string.Empty));

        public ICommand Command
        {
            get { return (ICommand)this.GetValue(CommandProperty); }
            set { this.SetValue(CommandProperty, value); }
        }

        public object CommandParameter
        {
            get { return this.GetValue(CommandParameterProperty); }
            set { this.SetValue(CommandParameterProperty, value); }
        }

        public string Label
        {
            get { return (string)GetValue(LabelProperty); }
            set { SetValue(LabelProperty, value); }
        }

        internal void Invoke(IUICommand command)
        {
            if (this.Command != null)
            {
                this.Command.Execute(this.CommandParameter);
            }
        }
    }
}

Nothing too special about this class, apart from it inherits from FrameworkElement so that it gets the inheriting data context scaffolding, which means you can use bindings for the commands and they will pick it up from the button the dialog is attached to. Also worth noting is that originally I tried to make the class implement the IUICommand interface, however, because the dialog is running on a different thread to what the dependency properties were created on, there were some exceptions when the MessageDialog would try to read the values from the properties. Instead, as shown in the next class, these commands are translated into UICommands.

namespace DialogHelper
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using Windows.UI.Popups;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Controls.Primitives;
    using Windows.UI.Xaml.Markup;

    [ContentProperty(Name = "Commands")]
    public sealed class MessageDialogTemplate : Panel
    {
        public static readonly DependencyProperty CancelCommandIndexProperty =
            DependencyProperty.Register("CancelCommandIndex", typeof(int), typeof(MessageDialogTemplate), new PropertyMetadata(-1));

        public static readonly DependencyProperty ContentProperty =
            DependencyProperty.Register("Content", typeof(string), typeof(MessageDialogTemplate), new PropertyMetadata(string.Empty));

        public static readonly DependencyProperty DefaultCommandIndexProperty =
            DependencyProperty.Register("DefaultCommandIndex", typeof(int), typeof(MessageDialogTemplate), new PropertyMetadata(-1));

        public static readonly DependencyProperty PromptUserProperty =
            DependencyProperty.Register("PromptUser", typeof(bool), typeof(MessageDialogTemplate), new PropertyMetadata(true));

        public static readonly DependencyProperty ShowOnClickProperty =
            DependencyProperty.RegisterAttached("ShowOnClick", typeof(MessageDialogTemplate), typeof(MessageDialogTemplate), new PropertyMetadata(null, OnShowOnClickChanged));

        public static readonly DependencyProperty TitleProperty =
            DependencyProperty.Register("Title", typeof(string), typeof(MessageDialogTemplate), new PropertyMetadata(string.Empty));

        private readonly ObservableCollection<DialogCommand> commands =
            new ObservableCollection<DialogCommand>();

        public MessageDialogTemplate()
        {
            this.commands.CollectionChanged += OnCommandsCollectionChanged;
        }

        public int CancelCommandIndex
        {
            get { return (int)this.GetValue(CancelCommandIndexProperty); }
            set { this.SetValue(CancelCommandIndexProperty, value); }
        }

        public IList<DialogCommand> Commands
        {
            get { return this.commands; }
        }

        public string Content
        {
            get { return (string)this.GetValue(ContentProperty); }
            set { this.SetValue(ContentProperty, value); }
        }

        public int DefaultCommandIndex
        {
            get { return (int)this.GetValue(DefaultCommandIndexProperty); }
            set { this.SetValue(DefaultCommandIndexProperty, value); }
        }

        public bool PromptUser
        {
            get { return (bool)this.GetValue(PromptUserProperty); }
            set { this.SetValue(PromptUserProperty, value); }
        }

        public string Title
        {
            get { return (string)this.GetValue(TitleProperty); }
            set { this.SetValue(TitleProperty, value); }
        }

        public static MessageDialogTemplate GetShowOnClick(DependencyObject obj)
        {
            return (MessageDialogTemplate)obj.GetValue(ShowOnClickProperty);
        }

        public static void SetShowOnClick(DependencyObject obj, MessageDialogTemplate value)
        {
            obj.SetValue(ShowOnClickProperty, value);
        }

        private static void OnButtonClick(object sender, RoutedEventArgs e)
        {
            MessageDialogTemplate template = GetShowOnClick(sender as DependencyObject);
            if (template != null)
            {
                template.OnButtonClick();
            }
        }

        private static void OnButtonDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
        {
            MessageDialogTemplate template = GetShowOnClick(sender);
            if (template != null)
            {
                template.DataContext = sender.DataContext;
            }
        }

        private static void OnShowOnClickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var button = d as ButtonBase;
            if (button != null)
            {
                button.Click += OnButtonClick;
                button.DataContextChanged += OnButtonDataContextChanged;
            }
        }

        private void ExecuteDefaultCommand()
        {
            int index = this.DefaultCommandIndex;
            if (index == -1)
            {
                // Guidlines state that if not specified, the default is the
                // leftmost button.
                index = 0;
            }

            if (index < this.commands.Count)
            {
                DialogCommand command = this.commands[index];
                if ((command != null) && (command.Command != null))
                {
                    command.Command.Execute(command.CommandParameter);
                }
            }
        }

        private void OnCommandsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            this.Children.Clear();
            foreach (DialogCommand command in this.commands)
            {
                this.Children.Add(command);
            }
        }

        private void OnButtonClick()
        {
            if (this.PromptUser)
            {
                this.ShowMessageDialog();
            }
            else
            {
                this.ExecuteDefaultCommand();
            }
        }

        private async void ShowMessageDialog()
        {
            var dialog = new MessageDialog(this.Content, this.Title);

            foreach (DialogCommand command in this.commands)
            {
                dialog.Commands.Add(
                    new UICommand(command.Label, command.Invoke));
            }

            dialog.CancelCommandIndex = (uint)this.CancelCommandIndex;
            dialog.DefaultCommandIndex = (uint)this.DefaultCommandIndex;

            await dialog.ShowAsync();
        }
    }
}

It may seem strange to not bind the DataContext to the DataContext of the button, however, I was bit by the bug where changes to the parent data context are not propagated through the binding (see this blog for more details) – strangely enough changes to the parent’s data context do cause the buttons DataContextChanged event to fire, which makes it even more confusing the binding doesn’t work!?

I’m quite pleased with the result; it’s nice to define the dialog layout in the XAML with the rest of the UI and the view model doesn’t need to raise some event that the view has to listen for (which seems to be the way for it to show a dialog in most examples I’ve seen.)