WPF: Simple “Busy” Overlay

I’m working on a WPF project that does some background processing which could take more than a few seconds.  Instead of a busy cursor, I want to give the user a simple “Please wait…” message via an overlay that appears on top of the form.  I found the solution in this stackoverflow question.  However, the solution presented there was an overview.  In this post I’ll show a complete example along with a downloadable sample project so you can play with it for yourself.

Disclaimer

First, a disclaimer: I’m currently working on my very first WPF application.  There may be an easier way to do this, but in building this solution from the stackoverflow question, it seemed pretty simple to me (hence the title).  Also, this form was using a Grid for layout.  I would think the general approach I use should work for other layouts, but I’ve only ever used this technique in a grid.

This sample project is done using the MVVM pattern.  If you’re not familiar with this pattern, don’t worry.  This example is pretty straightforward so you should be able to get the general idea.  Also, if you’re using the MVVM pattern with your WPF (or Silverlight) apps, you should strongly consider using Simon Cropp’s Notify Property Weaver.  This little tool allows you to write regular auto-properties in your view models and it will “weave” the required IL into the generated property getters and setters to handle the INotifyPropertyChanged interface.  Highly recommended! 

Initial Window

Let’s start with a very simple window:

image

This form collects a couple of pieces of information and has a link button to start the process.  The entire view model to run this form is show here (see Google and/or the source code if you need more information on the DelegateCommand):

public class BusyViewModel : INotifyPropertyChanged
{
    private ICommand doSomethingCommand;
 
    public event PropertyChangedEventHandler PropertyChanged;
 
    public string Item1 { get; set; }
    public string Item2 { get; set; }
    public List<String> Results { get; set; }
 
    public ICommand DoSomething
    {
        get { return doSomethingCommand ?? (doSomethingCommand = new DelegateCommand(LongRunningTask)); }
    }
 
    private void LongRunningTask()
    {
        var results = Enumerable.Range(0, 12).Select(x =>
                                        {
                                               Thread.Sleep(250);
                                               return "Result " + x;
                                        }).ToList();
        this.Results = results;
    }
}

 

If you clone my repository to your local machine and update your working directory to RevID 0 (the initial commit) you can run the app and see how clicking on the “DoSomething” link initiates the ICommand which runs a simple LINQ query to build up the sample results.  I stuck a Thread.Sleep in there to simulate load.  As a result, the window is unresponsive for about 3 seconds.  Let’s add a simple overlay to let the user know the application is busy.

Improved UI

First off, we want our view model to expose an “IsBusy” flag to indicate that it is busy doing something.  This is as easy as adding a property:

public bool IsBusy { get; set; }

And then we’ll put our long-running process into a background Task which will set the IsBusy flag when it starts and reset it when complete:

private void LongRunningTask()
{
    var task = new Task(ComputeResults);
    task.Start();
}
 
private void ComputeResults()
{
    this.IsBusy = true;
    var results = Enumerable.Range(0, 12).Select(x =>
                                                    {
                                                        Thread.Sleep(250);
                                                        return "Result " + x;
                                                    }).ToList();
    this.Results = results;
    this.IsBusy = false;
}

That’s all we need to do in the view model.  See how nice the MVVM pattern is?

From the UI side of things, we need to add our busy overlay.  Right before the closing </Grid> tag, I added a border and gave it a Grid.RowSpan of 3 (there’s three rows in my grid so this covers the entire grid):

<Border BorderBrush="Black" BorderThickness="1" Background="#80000000" Visibility="Collapsed" Grid.RowSpan="3">
    <Grid>
        <TextBlock Margin="0" TextWrapping="Wrap" Text="Please Wait..." HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="24" FontWeight="Bold" Foreground="#7EFFFFFF"/>
    </Grid>
</Border>

The Visibility of the Border is set to “Collapsed” which means it takes up no space and is invisible.  We’ll want that to change based on our view model’s IsBusy flag.  While data binding will work, we have to remember that we can’t directly bind a boolean type (IsBusy) to a Visibility type.  We’ll need to create an IValueConverter which can convert a boolean to a Visibility value.  I’ve named mine “BoolToVisibilityConverter”:

public class BoolToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        try
        {
            var v = (bool) value;
            return v ? Visibility.Visible : Visibility.Collapsed;
        }
        catch (InvalidCastException)
        {
            return Visibility.Collapsed;
        }
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Since we’re not doing 2-way binding, we don’t need to implement the ConvertBack method.  With the converter coded, we need to tell the WPF window how to use this converter when binding the Visibility attribute of the Border to the IsBusy value on the view model.

First, we’ll add a namespace to the main <Window> tag that points to the namespace where our converter exists:

xmlns:conv="clr-namespace:SampleBusyOverlay"

And then we can add a resource to the window pointing to our converter:

<Window.Resources>
    <conv:BoolToVisibilityConverter x:Key="boolConverter"/>
</Window.Resources>

Now we can bind the Border’s Visibility attribute to the view model’s IsBusy flag using the converter:

<Border BorderBrush="Black" BorderThickness="1" Background="#80000000" Visibility="{Binding IsBusy, Converter={StaticResource boolConverter}}" Grid.RowSpan="3">
    <Grid>
        <TextBlock Margin="0" TextWrapping="Wrap" Text="Please Wait..." HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="24" FontWeight="Bold" Foreground="#7EFFFFFF"/>
    </Grid>
</Border>

That’s it!  Update your working copy to RevID 1. If you re-run the project now, you’ll see a nicer UI letting the user know the application is busy doing something:

image

The source for this sample can be found in my Bitbucket repository SampleBusyOverlay.

Technorati Tags: ,,

No Comments