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:
IsIndeterminate—A bool value that allows you to enable or disable the indeterminate state of the indicator.
IsVisible—A bool value that allows you to show or hide the ProgressIndicator
.
Text—A string that is displayed beneath the indicator.
Value—A 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.
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.
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.
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).
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).
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.
3.15.235.188