Introduction
Certain type of operations in mobile applications take time. For good customer experience, it is suggested that the user be informed that the activity is in progress. Providing such an indication helps prevents users from guessing whether their action was registered with the application or not.
Displaying activity progress also allows a user to visualize how long it will take for the task to complete.
Windows Phone enables displaying activity progress via controls like ProgressIndicator.
The ProgressIndicator class resides in the Microsoft.Phone.Shell namespace in the Microsoft.Phone.dll assembly.
The control can be declared in XAML as shown below:
<ProgressIndicator .../>
Note that the ProgressIndicator is visible in the status bar, but unlike desktop applications, the status bar is at the very top of the phone screen (next to where one would find the battery icon and the phone signal icon.
ProgressIndicator comes in two variants: determinate and indeterminate. If the IsIndeterminate property value is set to true, the progress indicator will only display “…….” to indicate progress of the activity.
To display the progress indicator on a XAML page, we need to specify the instance of the ProgressIndicator class as one of the properties on the SystemTray’s SetProgressIndicator API.
Hands-On
Let us build a simple Windows Phone application that demonstrates how to display activity progress using ProgressIndicator. We’ll call this project WPProgressIndicatorDemo.
New Project
We’ll add a TextBlock on the MainPage as well as a button. In our demo application, we will start a long running activity on the click of the button and indicate completion of the activity using the TextBlock. While the activity is in progress, we will use ProgressIndicator to indicate.
After adding the two controls, our XAML is shown below.
<phone:PhoneApplicationPage x:Class="WPProgressIndicatorDemo.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:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}" SupportedOrientations="Portrait" Orientation="Portrait" shell:SystemTray.IsVisible="True"> <!--LayoutRoot is the root grid where all page content is placed--> <Grid x:Name="LayoutRoot" Background="Transparent"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!-- LOCALIZATION NOTE: To localize the displayed strings copy their values to appropriately named keys in the app's neutral language resource file (AppResources.resx) then replace the hard-coded text value between the attributes' quotation marks with the binding clause whose path points to that string name. For example: Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}" This binding points to the template's string resource named "ApplicationTitle". Adding supported languages in the Project Properties tab will create a new resx file per language that can carry the translated values of your UI strings. The binding in these examples will cause the value of the attributes to be drawn from the .resx file that matches the CurrentUICulture of the app at run time. --> <!--TitlePanel contains the name of the application and page title--> <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> <TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/> <TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/> </StackPanel> <!--ContentPanel - place additional content here--> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Button x:Name="buttonStart" Content="Button" HorizontalAlignment="Left" Margin="167,140,0,0" VerticalAlignment="Top" /> <TextBlock x:Name="textBlockStatus" HorizontalAlignment="Left" Margin="115,81,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/> </Grid> <!--Uncomment to see an alignment grid to help ensure your controls are aligned on common boundaries. The image has a top margin of -32px to account for the System Tray. Set this to 0 (or remove the margin altogether) if the System Tray is hidden. Before shipping remove this XAML and the image itself.--> <!--<Image Source="/Assets/AlignmentGrid.png" VerticalAlignment="Top" Height="800" Width="480" Margin="0,-32,0,0" Grid.Row="0" Grid.RowSpan="2" IsHitTestVisible="False" />--> </Grid> </phone:PhoneApplicationPage>
Now, we will add a click event handler for the button. In this method, we will create an instance of the ProgressIndicator class and specify it as indeterminate and set the progress indicator of the system tray to the instance.
private void buttonStart_Click(object sender, RoutedEventArgs e) { ProgressIndicator progressIndicator = new ProgressIndicator(); progressIndicator.IsVisible = true; progressIndicator.IsIndeterminate = true; progressIndicator.Text = "Downloading..."; SystemTray.SetProgressIndicator(this, progressIndicator); Deployment.Current.Dispatcher.BeginInvoke(() => { textBlockStatus.Text = "Async runner started. Please wait..."; }); AsyncRunner(DateTime.Now); }
Note that in the above method, we used a delegate to update the UI thread, and also called an asynchronous runner method. This async runner method will create a new background worker object, which will do the work.
public void AsyncRunner(DateTime dumpDate) { BackgroundWorker worker = new BackgroundWorker(); worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted); worker.DoWork += new DoWorkEventHandler(worker_DoWork); worker.RunWorkerAsync(dumpDate); }
The above method introduces two new methods we would need to implement – one which will be called when the worker completes the activity (we will use this to signal MainPage.xaml to update the TextBlock to say “Done”). The second method will actually do the hard work (in our case, looping over all the possible values from 0 to int.MaxValue.
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; worker.RunWorkerCompleted -= new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted); worker.DoWork -= new DoWorkEventHandler(worker_DoWork); Deployment.Current.Dispatcher.BeginInvoke(() => { textBlockStatus.Text = "Done."; }); SystemTray.ProgressIndicator.IsVisible = false; }
Note that when the worker has completed the work, we set the progress indicator to become invisible.
private void worker_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker worker = new BackgroundWorker(); //Thread.Sleep(1000); for (int i = 0; i < int.MaxValue; i++) ; //Thread.Sleep(1000); }
The above code snippet is a representative of a time consuming task (For simplicities purpose, we are looping here.
Our demo is complete. If you run the application now, you will notice the following screenshots.
Task not started
Task in progress
Task complete
You can see how simple it is to display activity progress using the ProgressIndicator class.
You can download the sample code used in this walkthrough below.
Summary
In this article, we learned how to display activity progress using the ProgressIndicator class. I hope you have found this information useful.
About the Author
Vipul Patel is a Program Manager currently working at Amazon Corporation. He has formerly worked at Microsoft in the Lync team and in the .NET team (in the Base Class libraries and the Debugging and Profiling team). He can be reached at vipul.patel@hotmail.com