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:
- 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 1.
LISTING 1. 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);
}
...
}
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 2).
LISTING 2. 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 3).
FIGURE 3 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.
Slider
The Slider
is an interactive control that allows the user to set its Value
property by dragging or pressing the Slider
track. It is ideal for values that do not require a high level of precision, such as the volume setting for music in a game.
The following demonstrates how to define a Slider
in XAML:
<Slider Value="5" Minimum="0" Maximum="100" LargeChange="20" />
The Value
, Minimum
, and Maximum
properties are described in the previous section on the RangeBase
control.
When the user taps the Slider
track, the Value
property is incremented or decremented by the value of the LargeChange
property.
The default orientation of the Slider
is horizontal. The orientation can be changed by setting the Orientation
property to Vertical
.
The Slider
is an especially useful control on the Windows Phone platform, since
the use of touch makes the slider easier to control than it would
otherwise be with the mouse.
Note
The built-in Slider
control is, however, somewhat cumbersome to use. The control template for the Slider
was built for Silverlight for the Browser, where mice are better at
hitting small targets. A number of custom templates are available on
the Web that provide better usability. Dave Relyea, the lead developer
of the Windows Phone Toolkit team, provides an improved Slider
control. See http://bit.ly/b0YtQj for more information.
Best Practice
Do not place a horizontal Slider
control on a Pivot
or Panorama
control. Doing so may interfere with a user’s ability to move between items, or it may prevent a user from manipulating the Slider
without moving between Panorama
or Pivot
items. This also applies to other controls like the Bing Maps control, which rely on the user performing a drag gesture.
Note that you may see a Slider
placed in a Pivot
in the sample code. This has been done for the sake of convenience and should not be taken as guidance.
ScrollBar
ScrollBar
is a control that has a sliding thumb whose position corresponds to a value. The ScrollBar
class provides a ViewportSize
property, which determines the amount of scrollable content that is visible and an Orientation
property, which can be either Horizontal
or Vertical
.
It is rare to see the ScrollBar
used directly. It is more commonly used within other controls such as the ScrollViewer
or in custom controls.