Creating Progress Dialog for WP7.

When creating Windows Phone 7 applications we'll inevitably encounter the situation when a certain procedure takes some time to complete. In this cases it's a good practice to show a progress indicator to the user. The UI Design and Interaction Guide specifies two major types of the progress indicator - indeterminate and determinate. If you don't know exactly how long your operation is going to take you should use the indeterminate progress indicator. Both types are implemented by the ProgressBar control in the WPDT. When using the WP7 device or emulator you will notice that the progress bar could be located at the top of page or in the middle:

 

Of course we can easily add the ProgressBar control to our pages, but as my favorite saying goes: "devil is in the details". First of all when progress indicator is shown in the middle of the page the background could be either a current theme's chrome color or would get semi transparent. Secondly, there also could be a status shown next to the progress indicator. So I thought that better approach would be to create a Progress Dialog that could be shown without messing with the page's design. The easiest way to approach this task is to create a UserControl, place ProgressBar and TextBox on it and use this user control as a child for the Popup. And so I created ProgressDialog UserControl and this is how the LayoutRoot looks like:

 <Grid x:Name="LayoutRoot" Background="Transparent" Width="480" Height="800">
     <Rectangle x:Name="backgroundRect" Grid.Row="0" Fill="{StaticResource PhoneChromeBrush}" />
     <Image x:Name="screenShot"  />
     <StackPanel x:Name="stackPanel" Orientation="Vertical" VerticalAlignment="Center">
         <ProgressBar Opacity="1" Height="4" HorizontalAlignment="Left" VerticalAlignment="Center"  
                  Name="progressBar" Style="{StaticResource PerformanceProgressBar}"
                  Foreground="{StaticResource PhoneForegroundBrush}"
                  Width="480" />
         <TextBlock Opacity="1" Height="30" HorizontalAlignment="Center" VerticalAlignment="Center" 
                  Name="textBlockStatus" Text="Loading..." />
      </StackPanel>
 </Grid>

As you can see I've got the StackPanel with ProgressBar and TexBlock to display status. You'll probably notice the Rectangle and the Image. Rectangle would be filled with the current theme's chrome color and will play an important role of show a transparent background without changing the transparency of the ProgressBar and the TextBlock. The Image (with the obvious name "screenShot") will be used to display a screen shot image that we are going to make of the current RootVisual. We need to do it because the whole layout of the controls would be shifted when we hide the SystemTray which presents an unpleasant gittering in the UI.

To make the ProgressDialog developer friendly I have also created the followig enum:

  public enum ProgressTypes
 {
     IndeterminateMiddle,
     IndeterminateTop,
     DeterminateMiddle,
     DeterminateTop,
     CustomMiddle,
 }

This enum will be used to predermine some standard locations for the ProgressBar and its type. The obvious method to make the dialog to show its content is:

  public void Show()
 {
      if (this.ChildWindowPopup == null)
      {
           this.ChildWindowPopup = new Popup();
           try
           {
               this.ChildWindowPopup.Child = this;
           }
           catch (ArgumentException)
           {
               throw new InvalidOperationException("The control is already shown.");
           }
      }
 
      if (this.ChildWindowPopup != null && Application.Current.RootVisual != null)
      {
           // Configure accordingly to the type
           InitializeProgressType();
           // Show popup
           this.ChildWindowPopup.IsOpen = true;
      }
 }
 

The patter that you see in this Show method should be already familiar for you from the PickerBox control. In this method above we create and instance of the Popup, assign the ProgressDialog control to its child, initialize the positions and the visibility for the ProgressBar and the status TextBlock and then display the Popup. Now let's take a look at the InitializeProgressType method:

 private void InitializeProgressType()
{
      this.HideSystemTray();
 
      switch (this.progressType)
      {
          case ProgressTypes.IndeterminateMiddle:
               this.Opacity = 1;
               this.screenShot.Visibility = System.Windows.Visibility.Collapsed;
               this.stackPanel.VerticalAlignment = System.Windows.VerticalAlignment.Center;
               this.progressBar.Foreground = (Brush)Application.Current.Resources["PhoneForegroundBrush"];
               this.textBlockStatus.Text = defaultText;
               this.textBlockStatus.Visibility = System.Windows.Visibility.Visible;                  
               this.progressBar.IsIndeterminate = true;
               break;
          case ProgressTypes.DeterminateMiddle:
               this.Opacity = 1;
               this.screenShot.Visibility = System.Windows.Visibility.Collapsed;
               this.stackPanel.VerticalAlignment = System.Windows.VerticalAlignment.Center;
               this.progressBar.Foreground = (Brush)Application.Current.Resources["PhoneForegroundBrush"];
               this.textBlockStatus.Text = defaultText;
               this.textBlockStatus.Visibility = System.Windows.Visibility.Visible;                             
               break;
            //... THERE'S SOME MORE CODE 
       }

   }

In this method we check which progress type was set and appropriate adjustments to our controls. You can take a look at the full implementation in the download on the MSDN code galleries.

OK, let's take a look at how we can use this dalog in our applicatins. To demonstrate its functionality I've got the following page in the test project:

 

 And here's a code snip from the button's event handler:

  // Create instance of the progress dialog
 if (this.progress == null)
 {
      this.progress = new ProgressDialog();
 }
 
 // Initiaze background worker
 if (backgroundWorker == null)
 {
       backgroundWorker = new BackgroundWorker();
       backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
       backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
       backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
 
  }     
            
 if (radioButtonMiddle.IsChecked.Value)
 {
      // Set the progress type
      progress.ProgressType = ProgressTypes.IndeterminateMiddle;
      backgroundWorker.WorkerReportsProgress = false;
 }
 
 if (radioButtonTop.IsChecked.Value)
 {
      progress.ProgressType = ProgressTypes.IndeterminateTop;
      backgroundWorker.WorkerReportsProgress = false;
 }
  
 ...
  
  // Show dialog
 progress.Show();
 // Start some long running process
 backgroundWorker.RunWorkerAsync();

In this code above I create an instance of the ProgressDialog,  set a ProgressType depending on the radionbutton selected on the page and then show it. I also use the BackgroundWorker class to emulate some long running process:

   void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    Dispatcher.BeginInvoke(() =>
    {
        // Change the progress bar
        progress.ProgressBar.Value = e.ProgressPercentage;
    }
    );
}

 void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
 {
     Dispatcher.BeginInvoke(() =>
     {
        // Close the Progress Dialog
         progress.Hide();
     }
     );

 }

 void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
 {
     if (progress.ProgressType == ProgressTypes.CustomMiddle || progress.ProgressType == ProgressTypes.DeterminateTop || progress.ProgressType == ProgressTypes.DeterminateMiddle)
     {
          for (int i = 0; i < 100; i++)
          {
              i += 20;
              backgroundWorker.ReportProgress(i);
              Thread.Sleep(1000);
          }
     }
     else
   {
          // Emulate some work by sleeping
          Thread.Sleep(7000);
     }
}

And here are a few screenshots of the different types of the progress from the sample app:

 

 

You can download the full source code from MSDN code galleries resource page.