Progress Indicator

,

In the initial release of the Windows Phone OS, there was no way to control the native progress bar located in the status bar of the phone. Fortunately, since the release of Windows Phone 7.5, you can with the ProgressIndicator class.

ProgressIndicator is a subclass of the DependencyObject class and provides the following bindable properties:

Image IsIndeterminateA bool value that allows you to enable or disable the indeterminate state of the indicator.

Image IsVisibleA bool value that allows you to show or hide the ProgressIndicator.

Image TextA string that is displayed beneath the indicator.

Image ValueA double value between 0.0 and 1.0 that allows you to set the length of the progress bar when in determinate mode, that is, when IsIndeterminate is set to false.


Note

The default value of ProgressIndicator.IsVisible is false. You must set IsVisible to true, either explicitly or via a data binding for the control to be displayed.


A ProgressIndicator may be either defined in XAML or in code. The following example demonstrates adding a ProgressIndicator to a page’s XAML file:

<phone:PhoneApplicationPage
...
    shell:SystemTray.IsVisible="True">

    <shell:SystemTray.ProgressIndicator>
        <shell:ProgressIndicator IsVisible="True"
                                 IsIndeterminate="False"
                                 Value="0.9"
                                 Text="{Binding Message}" />
    </shell:SystemTray.ProgressIndicator>
...
</phone:PhoneApplicationPage>


Note

SystemTray.IsVisible must be set to true for the ProgressIndicator to be displayed.


Adding a ProgressIndicator to a page must occur after the page has been initialized. This can be done by attaching the ProgressIndicator to the page within a Loaded event handler.

The ProgressIndicator instance may be retained as a field in the page and used directly, or data bindings can be established when the ProgressIndicator is instantiated, as demonstrated in Listing 5.3.

LISTING 5.3. Initializing a ProgressIndicator in Code


public partial class ExampleView : PhoneApplicationPage
{
    public ExampleView()
    {
        InitializeComponent();
        DataContext = new ExampleViewModel();
        Loaded += HandleLoaded;
    }

    void HandleLoaded(object sender, RoutedEventArgs e)
    {
        var progressIndicator = SystemTray.ProgressIndicator;
        if (progressIndicator != null)
        {
            return;
        }

        progressIndicator = new ProgressIndicator();

        SystemTray.SetProgressIndicator(this, progressIndicator);

        Binding binding = new Binding("Busy") { Source = ViewModel };
        BindingOperations.SetBinding(
            progressIndicator, ProgressIndicator.IsVisibleProperty, binding);

        binding = new Binding("Busy") { Source = ViewModel };
        BindingOperations.SetBinding(
            progressIndicator,
            ProgressIndicator.IsIndeterminateProperty,
            binding);

        binding = new Binding("Message") { Source = ViewModel };
        BindingOperations.SetBinding(
            progressIndicator, ProgressIndicator.TextProperty, binding);
    }
...
}


Chapter 29, “Storing App Data in a Local Database,” uses the ProgressIndicator to display the progress of retrieving a live Twitter feed.


Best Practice

Performing time-consuming operations, such as downloads and database queries, on the UI thread is bad practice, as it locks the user interface making it unresponsive. Therefore, always perform any potentially long-running operation using a background thread.


Using a BackgroundWorker to Update the ProgressBar

The BackgroundWorker class allows you to run an operation on a separate, dedicated thread. When you want a responsive user interface and you must perform time-consuming operations, the BackgroundWorker class provides a convenient solution.

The BackgroundWorker class has events that are automatically raised on the UI thread, giving you the opportunity to update UIElements without risking cross-thread access exceptions. You can listen for events that report the progress of an operation and signal when the operation is completed. The following section uses a BackgroundWorker to perform an arbitrary time-consuming operation during which a ProgressBar is periodically notified of the progress.

ProgressBar and BackgroundWorker Sample Code

An example for the ProgressBar can be found in the ControlExamplesView page and the ControlExamplesViewModel class in the downloadable sample code.

The viewmodel contains a BackgroundWorker field. In the viewmodel’s constructor, the BackgroundWorker.WorkerReportsProgress property is set to true. This causes the BackgroundWorker to raise an event when its ReportProgress method is called. We then subscribe to three events: the DoWork event, which is raised when the RunWorkerAsync method is called; the ProgressChanged event, which is called when the BackgroundWorker ReportProgress method is called; and the RunWorkerCompleted, which is raised when the backgroundWorker_DoWork method completes (see Listing 5.4).

LISTING 5.4. ControlExamplesViewModel Class (excerpt)


public class ControlExamplesViewModel : ViewModelBase
{
    readonly BackgroundWorker backgroundWorker = new BackgroundWorker();

    public ControlExamplesViewModel()
        : base("Controls")
    {
        backgroundWorker.WorkerReportsProgress = true;
        backgroundWorker.DoWork
            += new DoWorkEventHandler(backgroundWorker_DoWork);
        backgroundWorker.ProgressChanged
            += new ProgressChangedEventHandler(
                        backgroundWorker_ProgressChanged);
        backgroundWorker.RunWorkerCompleted
            += new RunWorkerCompletedEventHandler(
                backgroundWorker_RunWorkerCompleted);

        backgroundWorker.WorkerSupportsCancellation = true;
        cancelProgressCommand = new DelegateCommand(
            obj => backgroundWorker.CancelAsync());

        backgroundWorker.RunWorkerAsync();
        Message = "BackgroundWorker performing task.";
    }
    void backgroundWorker_ProgressChanged(
            object sender, ProgressChangedEventArgs e)
    {
        ProgressPercent = e.ProgressPercentage;
    }

    void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = (BackgroundWorker)sender;
        for (int i = 0; i < 100; i++)
        {
            if (worker.CancellationPending)
            {
                e.Cancel = true;
                return;
            }
            Wait(300);
            worker.ReportProgress(i);
        }
    }
    void backgroundWorker_RunWorkerCompleted(
            object sender, RunWorkerCompletedEventArgs e)
    {
        Message = e.Cancelled
            ? "Background worker cancelled."
            : "Background worker completed.";
    }

    int progressPercent;

    public int ProgressPercent
    {
        get
        {
            return progressPercent;
        }
        private set
        {
            Assign(ref progressPercent, value);
        }
    }
    bool cancelProgress;
    DelegateCommand cancelProgressCommand;

    public ICommand CancelProgressCommand
    {
        get
        {
            return cancelProgressCommand;
        }
    }

    string message;

    public string Message
    {
        get
        {
            return message;
        }
        set
        {
            Assign(ref message, value);
        }
    }

    static readonly object waitLock = new object();

    static void Wait(int delayMs)
    {
        if (delayMs > 0)
        {
            lock (waitLock)
            {
                Monitor.Wait(waitLock, delayMs);
            }
        }
    }
}


While running, the worker thread repeatedly sleeps, using the viewmodel’s Wait method, and then signals that the progress has changed.


Note

If the BackgroundWorker.WorkerReportsUpdates property is set to false and the BackgroundWorker ReportProgress method is called, an InvalidOperationException will be thrown.


The following excerpt shows the view’s XAML for the relevant controls:

<TextBlock Text="ProgressBar" />
<ProgressBar Value="{Binding ProgressPercent}"
        Minimum="0" Maximum="100" />
<Button Content="Cancel Progress"
        Command="{Binding CancelProgressCommand}" />
<TextBlock Text="Message" />
<TextBlock Text="{Binding Message}" Height="80" />

On completion of the backgroundWorker_DoWork method, the backgroundWorker_RunWorkerCompleted method is called, and the viewmodel’s MessageProperty is updated (see Figure 5.15).

Image

FIGURE 5.15 Updating the UI using a BackgroundWorker.

The BackgroundWorker class also supports operation cancellation. By pressing the Cancel Progress button, the CancelProgressCommand is executed, which, in turn, calls the backgroundWorker’s CancelAsync method. This is then detected in the backgroundWorker_DoWork method, which sets the Cancel property of the DoWorkEventArgs.

Although cancelled, the BackgroundWorker.RunWorkCompleted event is still raised, and the backgroundWorker_RunWorkerCompleted handler is still called; however, the RunWorkerCompletedEventArgs.Cancelled property allows you to determine whether the operation was cancelled.


Note

The NotifyPropertyChangeBase class in the downloadable sample code, which is the base class of the ViewModelBase class, automatically raises INotifyPropertyChanged.PropertyChanged events on the UI thread (one of its many benefits). Therefore, you could achieve the same thing shown in the example by replacing the BackgroundWorker with a thread from, for example, the ThreadPool, and by including two Boolean cancel and cancelled fields. It would make for a simpler implementation with fewer event subscriptions.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.52.124