BusyIndicator for Windows Store Apps in C# / XAML

Preface

The BusyIndicator for Windows Store apps is a variation of the Busy-Indicating Dialog for Windows Store apps.

The motivation for creating such a control is almost the same. No such a control is available. And I want to make sure the user cannot start a new task while a running one has not finished, without blocking the UI thread or fiddling around with flags and disabled controls.

In contrast to the busy-indicating dialog, the busy indicator should not offer the ability to cancel the operation. This is because the underlying operation can’t be canceled too. And it cannot show progress, because the underlying operation is an indeterminate task.

This leads to the following requirements:

  • Disable user interaction (‘lock’ the app)
  • Give visual feedback to let the user know interaction is disabled
  • Do not block the UI thread
  • Show some progress indication

Also, it should comply with these Windows 8 user experience guidelines for indeterminate tasks:

  • If the task is modal — it blocks interaction until its completion — use the indeterminate progress ring style
  • Provide status text to the right of the progress ring
  • Make the progress ring the same color as its status text
  • Disable controls that user shouldn’t interact with while the task is running (matches with the first item of the requirements list above)

Disable User Interaction

Disabling the user to interact with the app is quite simple. Rob Caplan gave me the initial idea in his answer on how one might implement a modal popup. He suggested to use a full screen popup with a similar look to the MessageDialog.

Creating a full screen popup, or better say a popup having the current size of the app, disables the interaction with the app. No matter if the user clicks, taps, or uses the pen.

The first requirement is (almost) fulfilled.

Disable Keyboard Interaction

The reason for the ‘almost’ above is this. When you enable keyboard shortcuts in your app like this sample does, you still need some flag fiddling.

The BusyIndicator only disables interaction via the screen (mouse, touch, pen), but not via the keyboard. This is because there is no control embedded into the BusyIndicator that will handle keyboard input. I tried to handle the KeyDown events, but hadn’t received any.

So the bad news is, one has to set a flag when an indeterminate task starts, and evaluate that flag when another task should be started.

The busy-indicating dialog does not have that issue, because it contains a button to cancel the operation. This button captures the keyboard input.

Give Visual Feedback

Depending on how the full screen popup is implemented, the user might not recognize that the interaction is disabled.

Looking at MessageDialog, one might notice that the area that is not covered by the dialog itself is dimmed out.

To achieve this, a UserControl is implemented. In the sample code, this user control is named BusyIndicator and can be found in UI/Xaml/Popups.

BusyIndicator contains one Grid without any content. This Grid has a black background and an opacity of 0.4.

Setting an instance of BusyIndicator as the child of the Popup, the app’s window gets dimmed out when the Popup is opened. The second requirement is fulfilled.

Do Not Block the UI Thread

Because common UI controls are used, this requirement can be fulfilled without any additional effort.

Of course, one has to take care that the operation to be performed is non-blocking. Means it should be implemented in an asynchronous way. But this is independent from the implementation of the BusyIndicator.

Show Some Progress Indication

Now, the user sees that the interaction is disabled. But there is no information about what is going on. This might lead to the impression that the app has stopped working.

To show that there is something going on in the background, BusyIndicator contains a second Grid. It is something like a modal dialog, showing processing information. This Grid contains a ProgressRing and aTextBlock.

The ProgressRing is what the guidelines demands for indeterminate tasks. The TextBlock makes the control comply with the guideline to provide some status text.

The text will be set when the BusyIndicator is created. There is no binding required, since the text will not change while the BusyIndicator is active.

Make the Progress Ring the Same Color as Its Status Text

To comply with this UX guideline, the file Assets/ApplicationStyles.xaml contains the following SolidColorBrush definition:

<SolidColorBrush x:Key="IndicatorTextBrush" Color="#383838"/>

This brush is used by the TextBlock and the ProgressRing of the indicator to set the Foreground property.

Creating the Busy Indicator

The following code snippet shows all the steps that needs to be done to show the busy indicator.

// Implemented by BusyIndicator
public static BusyIndicator Start
  (
  string title
  )
{
  // Create a popup with the size of the app's window.
  Popup popup = new Popup()
  {
    Height = Window.Current.Bounds.Height,
    IsLightDismissEnabled = false,
    Width = Window.Current.Bounds.Width
  };

  // Create the BusyIndicator as a child, 
  // having the same size as the app.
  BusyIndicator busyIndicator = new BusyIndicator(title)
  {
    Height = popup.Height,
    Width = popup.Width
  };

  // Set the child of the popop
  popup.Child = busyIndicator;

  // Postion the popup to the upper left corner
  popup.SetValue(Canvas.LeftProperty, 0);
  popup.SetValue(Canvas.TopProperty, 0);

  // Open it.
  popup.IsOpen = true;

  // Return the BusyIndicator
  return (busyIndicator);
}

Prepare the Indeterminate Task

Because there are several steps to be done before an indeterminate task can start, this is encapsulated in a separate method.

// Implemented by MainPage
private BusyIndicator PrepareIndeterminateTask
  (
  string title
  )
{
  // Disable the app bar
  BottomAppBar.IsEnabled = false;

  // Lock the screen by starting the BusyIndicator
  BusyIndicator busyIndicator = BusyIndicator.Start(title);

  // Set the flag
  IsTaskInProgress = true;

  // return the indicator
  return (busyIndicator);
}

Please notice the fact that the app bar is disabled explicitly. The popup does not ‘block’ the app bar.

Cleanup

When the indeterminate task has finished, some cleanup is required.

// Implemented by MainPage
private void CleanUpIndeterminateTask
  (
  BusyIndicator busyIndicator
  )
{
  // close the BusyIndicator
  busyIndicator.Close();

  // Reset the flag
  IsTaskInProgress = false;

  // Enable the app bar
  BottomAppBar.IsEnabled = true;
}

Consider Windows Store App Lifecycle

In the article ‘The Windows Store App Lifecycle‘, Rachel Appel points out that “Windows 8 is changing how and when applications run”. In fact, it does!

Unlike desktop applications, Windows 8 suspends Windows Store apps (after a few seconds) when the user switches to another app. No matter what the app is currently doing. You cannot keep the app ‘alive’.

One should be aware of this. It means the user has to wait for long running operation to complete, and should not switch to another app meanwhile. This is kind of ‘blocking’ the device. Keeping this in mind, one should think twice before using Windows Store apps to execute long running operations, e.g. copy gigabytes of data.

Yes, there are background tasks available. But you cannot trigger them from within your app. So the usage is limited.

The Sample

… can be found here.

Screenshot

Screenshot

Links

Busy-Indicating Dialog for Windows Store apps

Forum question Modal popup

Download Windows 8 User Experience Guidelines for Windows Store apps

The Windows Store App Lifecycle

Background Tasks

Forum question ‘Trigger background task manually