Mobile Corner

Display Loading Progress Using Data Binding

Mobile Corner columnist Nick Randolph walks through how to use data binding to automatically display a progress bar when loading data in Windows Phone 7.

All but the most trivial Windows Phone 7 applications require some data to be loaded and displayed. This process, whether the data is being loaded from Isolated Storage or from across the Web, can take time to complete.

When you're designing your application you need to consider how you handle this in order to ensure the application always remains responsive to the user. Typically this is done by carrying out any data loading on a background thread, allowing the user to continue to interact with the application whilst the data is being loaded. However, this often leads the user to query whether anything is happening; which leads to the use of a progress bar to indicate that some activity is being executed in the background. Once the data has been loaded the progress bar should be hidden and the relevant controls to display the data should be shown.

Let's walk this through with a simple example, starting with a basic user interface that contains a start Button, a ProgressBar and a TextBlock. When the user clicks the start Button, the ProgressBar is displayed while the data is being loaded. Once the data has loaded the ProgressBar will be hidden and the TextBlock will be displayed, showing the data. The XAML for the layout of the page contains a StackPanel with the start Button, ProgressBar and TextBlock nested within it.

<phone:PhoneApplicationPage x:Class="BackgroundProcessingSample.MainPage"
                            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                            xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" 
                            xmlns:sample="clr-namespace:BackgroundProcessingSample">
    <StackPanel>
        <Button Content="Start" Click="StartClick" />
        <ProgressBar Height="10" />
        <TextBlock TextWrapping="Wrap" />
    </StackPanel>
</phone:PhoneApplicationPage>

One way to adjust the layout of the page would be to explicitly write code in the MainPage.xaml.cs file that would hide or show the elements as required. However, this can result in code that is hard to test and maintain. An alternative strategy is to create an additional class that will be used to hold the current state of the user interface. This pattern is often referred to as Model-View-ViewModel, where the Model is the data we're loading, the View is the page and the ViewModel is the state of the View. We can then use data binding to connect the View with the ViewModel.

The MainPageViewModel class will be used to track the current state of the page. Any property on the page, or the controls on the page, that will change should have a corresponding property on the MainPageViewModel. In this case we need to adjust the following properties:

[Button].IsEnabled: In order to disable the start Button whilst data is loading
[ProgressBar].IsIndeterminate: Rather than show an actual progress we'll just use the Indeterminate state to illustrate that data is being loaded
[ProgressBar].Visibility: Used to display and hide the ProgressBar
[TextBlock].Text: Displays the data that is loaded
[TextBlock].Visibility: Used to display and hide the TextBlock

This would result in the following MainPageViewModel:

public class MainPageViewModel {
    public bool ButtonIsEnabled { get; set; }
    public bool ProgressBarIsIndeterminate { get; set; }
    public Visibility ProgressBarVisibility { get; set; }
    public Visibility TextBlockVisibility { get; set; }
    public string TextBlockText { get; set; }
}

As you can see this can lead to a lot of properties that need to be updated every time data is loaded. With the exception of the TextBlockText property, the rest of the properties are either directly, or indirectly, associated with whether data is currently being loaded. StartButtonIsEnabled will always be false, when data is being loaded, and vice versa; ProgressBarVisibility will always be Visible when data is being loaded, and Collapsed when it's not; TextBlockVisibility will always be Collapsed when data is being loaded, and Visible when it's not.

Ideally there should be a single property, for example IsLoading, that the various elements can be data bound to. One option would be to make this a property of type bool, and then use value converters (i.e., classes that implement IValueConverter and are able to convert from one data type to another) as part of the data binding. However, this adds considerable overhead both when wiring up the data binding but also can negatively affect the performance of your application.

Another option is to use a more complex type that supports a series of derived property values. The VisualBoolean struct has a single field, isTrue, which represents the underlying Boolean value. It then exposes properties, IsTrue, IsFalse, IsTrueVisibility and IsFalseVisiblity, which can be data bound to. To make it easier to work with, the VisualBoolean class supports implicit casting to and from bool values.

public struct VisualBoolean {
    private readonly static VisualBoolean True = new VisualBoolean() { isTrue = true };
    private readonly static VisualBoolean False = new VisualBoolean() { isTrue = false }; 

    private bool isTrue;

    public bool IsTrue { get { return isTrue; } }

    public bool IsFalse { get { return !IsTrue; } }

    public Visibility IsTrueVisibility
    {
        get { return IsTrue ? Visibility.Visible : Visibility.Collapsed; }
    }
    public Visibility IsFalseVisibility
    {
        get { return IsFalse ? Visibility.Visible : Visibility.Collapsed; }
    }

    public static implicit operator VisualBoolean(bool isTrue) {
        if (isTrue) return True;
        return False;
    }

    public static implicit operator bool(VisualBoolean visualBoolean) {
        return visualBoolean.isTrue;
    }
}

Using the VisualBoolean class, our MainPageViewModel becomes much simpler:
public class MainPageViewModel {
    public VisualBoolean IsLoading { get; set; }
    public string TextBlockText { get; set; }
}

In the XAML for the MainPage you need to create an instance of the MainPageViewModel in the Resources section of the page, which is then set as the DataContext for the page so that you can data bind to properties on it. Alternatively you could create this in code when the page is loaded and assign it to the DataContext property of the page.

<phone:PhoneApplicationPage x:Class="BackgroundProcessingSample.MainPage"
                            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                            xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" 
                            xmlns:sample="clr-namespace:BackgroundProcessingSample">
    <phone:PhoneApplicationPage.Resources>
        <sample:MainPageViewModel x:Key="ViewModel" />
    </phone:PhoneApplicationPage.Resources>

    <phone:PhoneApplicationPage.DataContext>
        <Binding Source="{StaticResource ViewModel}" />
    </phone:PhoneApplicationPage.DataContext>

    <StackPanel>
        <Button Content="Start"
                IsEnabled="{Binding IsLoading.IsFalse}" 
                Click="StartClick" />
        <ProgressBar Height="10"
                     IsIndeterminate="{Binding IsLoading.IsTrue}"
                     Visibility="{Binding IsLoading.IsTrueVisibility}" />
        <TextBlock TextWrapping="Wrap"
                   Text="{Binding TextBlockText}" 
                   Visibility="{Binding IsLoading.IsFalseVisibility}" />
    </StackPanel>
</phone:PhoneApplicationPage>

For each of the element properties you need to establish the appropriate data binding in the XAML.

[Button].IsEnabled: IsLoading.IsFalse
[ProgressBar].IsIndeterminate: IsLoading.IsTrue
[ProgressBar].Visibility: IsLoading.IsTrueVisibility
[TextBlock].Text: TextBlockText
[TextBlock].Visibility: IsLoading.IsFalseVisibility

Note that each of the properties that relates to whether data is being loaded has a binding expression, which includes the IsLoading property. When the IsLoading property changes, the corresponding PropertyChanged event (the MainPageViewModel will need to implement INotifyPropertyChanged) will cause each of these properties to be updated, automatically showing and hiding the appropriate elements.

The only thing left to do is to create the StartClick method, which will be invoked when the start Button is clicked, which will in turn call the Start method on the MainPageViewModel. The following code shows both the code behind for the MainPage, in MainPage.xaml.cs, and the full source code for the MainPageViewModel class.

public partial class MainPage {
    public MainPage() {
        InitializeComponent();
    }

    private void StartClick(object sender, System.Windows.RoutedEventArgs e) {
        (DataContext as MainPageViewModel).Start();
    }
}

public class MainPageViewModel:INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;

    private VisualBoolean _IsLoading;
    public VisualBoolean IsLoading {
        get { return _IsLoading; }
        set {
            _IsLoading = value;
            RaisePropertyChanged("IsLoading");
        }
    }

    private string _TextBlockText;
    public string TextBlockText {
        get { return _TextBlockText; }
        set {
            _TextBlockText = value;
            RaisePropertyChanged("TextBlockText");
        }
    }

    private void RaisePropertyChanged(string propertyName) {
        if (PropertyChanged!=null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    public void Start() {
        ThreadPool.QueueUserWorkItem(async => {
               var dispatcher = Deployment.Current.Dispatcher;
               dispatcher.BeginInvoke(() => {
                               IsLoading = true;
                       });

               Thread.Sleep(10000);
                                                 
               dispatcher.BeginInvoke(() => {
                               TextBlockText = "Loaded data goes here. For the timebeing there is nothing here other than some sample text which is hidden whilst data is being loaded.";
                               IsLoading = false;
                       });
        });
    }
}

Note the due to the implicit casting between bool and VirtualBoolean setting, the IsLoading property is as simple as assigning it true or false. Setting the properties on the MainPageViewModel has to be wrapped with a call to dispatcher.BeginInvoke to ensure it is carried out on the UI thread. As you can see from the code, MainPageViewModel implements the INotifyPropertyChanged interface and raises the PropertyChanged event whenever either the TextBlockText or the IsLoading property changes. This in turn triggers an update of any elements that are data bound to the updated property.

When you run this you will see that when the Start button is clicked, the ProgressBar is shown in the indeterminate state; after 10000 milliseconds the ProgressBar is hidden and the TextBlock is displayed containing the loaded text. This progression is shown in Figure 1.


[Click on image for larger view.]
Figure 1.

In this column you have seen how you can use data binding to wire up a number of different element properties to a single ViewModel property. Data binding is incredibly powerful and can be used to significantly simplify the logic within your Windows Phone 7 application.

About the Author

Nick Randolph runs Built to Roam, a consulting company that specializes in training, mentoring and assisting other companies build mobile applications. With a heritage in rich client applications for both the desktop and a variety of mobile platforms, Nick currently presents, writes and educates on the Windows Phone platform.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.