Managing busy state with MVVM Light

Nov 29, 2012 at 3:36 AM
Edited Nov 29, 2012 at 4:06 AM

First let me say thank you for creating this project. We've been using MVVM Light for quite some time now and absolutely love it. It's become a core piece of our infrastructure and we continue to use it regularly on all of our projects we create.

That being said, early on we noticed that managing when an application is busy is quite difficult within Silverlight when you have multiple threads pulling data simultaneously. After thinking about this problem for quite some time, I came up with a class which uses your framework to assist with managing this problem area. I've re-written this class so many times now as I transition to other projects, I have a feeling many other people might find great use for it. So, I am posting it here for your consideration to include it in your project.

Basically, this class' role is to handle busy state changes within a multi-threaded application as pieces are started and stopped. For example:

Say you have a view model which has to retrieve 3 separate pieces of data in a Silverlight based application using RIA services. In order to decrease load time, you perform the 3 load operations of the data concurrently. If you have some sort of busy indicator on your page while the data is being retrieved, you need to know when to start and when to stop that indicator. With multiple threads retrieving the data concurrently, having a class that acts as a controller for when each thread starts and stops is highly useful. Without it, your application may transition between states when one thread finishes before the 2nd thread finishes being started usually causing a flash on the screen. This area is exactly what this class is designed to help manage by providing a thread-safe mechanism for managing this.

XAML:

<Page.Resources>
  <helpers:BusyController x:Key="BusyController"/>
</Page.Resources.

<BusyIndicator IsBusy="{Binding Default.IsBusy, Mode=OneWay, Source={StaticResource BusyController}"/>

In the above example, you have a BusyController instance defined in the page resources, and you can either use the default instance for controlling application-wide busy state, or bind to the IsBusy property on an instance of the BusyController if you are wanting to control the busy state on a specific control. This example uses a Silverlight Toolkit BusyIndicator control to display a visual indicator that the application is busy doing something.

Usage: 

public void LoadData()
{
    BusyController.Default.SendMessage(true);

    EntityQuery<MyEntity> query = this.DomainContext.GetMyEntityQuery();
    this.DomainContext.Load(query, this.LoadDataCompleted, null);
}

private void LoadDataCompleted(LoadOperation<MyEntity> op)
{
    BusyController.Default.SendMessage(false);
}

Basically, before each thread begins whatever asynchronous work must be performed, a quick call to the busy controller indicating that the busy state is being changed. And once the operation completes, another call to indicate that the work has finished.

Also, if your application needs to know when the busy state of a controller changes, using the normal registration mechanism for the messenger instance can be used to receive notifications.

Messenger.Default.Register<BusyMessage>(this, (message) =>
    {
        // Do something useful.
    });

The Code:

Rather than posting all of the code here for the class and related message, I'm going to upload it to pastebin. Here is the link: http://pastebin.com/qqZ14SDm

I hope this post helps someone else, and I hope I see it included with MVVM Light at some point in the future!

- Jeff

Edit: I wanted to add, this class has been used in Windows Phone 7 and 8, WPF, and both Silverlight 4 and 5 applications.

Coordinator
Nov 29, 2012 at 7:08 AM

Hi,

It's not the first time I got a request to add a "IsBusy" handling to MVVM Light. I have not done it so far because I was thinking it is already too specific. Personally I handle IsBusy differently: In every framework I work in, I have a "PleaseWaitControl" and a ViewModelBaseEx with specific features. For instance in WP8 and Win8, which are navigation frameworks with pages, ViewModelBaseEx has additional features around navigation. The "Busy" feature is into ViewModelBaseEx. I also have a PageBase class which does a lot of thing automatically, such as setting the busy state, saving its own navigation state, etc. 

In summary, my approach has been to extend MVVM Light with framework specific features in a "Utils" assembly. I have one for Silverlight, one for WPF, one for Windows Phone, one for Windows 8, etc. Sometimes I feel that a feature from my Utils is unified enough to be placed into MVVM Light "core". Maybe the Busy state handling is at this point, I will need to think about it.

Cheers and thanks for the feedback.

Laurent

Nov 30, 2012 at 1:00 AM
Edited Nov 30, 2012 at 1:01 AM

You are correct in thinking that it can be specific, each developer finds their own way to make it work because there is no standard for managing busy state of an application. If you're not careful with what you build, like extending the view model base to support busy state functionality at the view model, you might end up not being able to handle scenarios that need busy management that aren't view specific.

That's why the BusyController class isn't responsible for anything other than tracking the number of threads that are currently in use, and sending a notification via the IsBusy property change and a message when that state changes. If your situation requires you to have a control handling busy state independently of the view (for example downloading an image and wanting a busy indicator wrapping the image control while it downloads), tossing an instance of the BusyController on the control class allows you to manage busy state on that control independently of the view.

Control XAML:

<UserControl x:Class="MyApp.MyControl">
    <UserControl.Resources>
        <helpers:BusyController x:Key="BusyController"/>
    </UserControl.Resources>

    <BusyIndicator IsBusy="{Binding IsBusy, Mode=OneWay, Source={StaticResource BusyController}}">
        <Image Source="{Binding ImageSource, Mode=OneWay}"/>
    </BusyIndicator>
</UserControl>

Code:

public class MyControl : UserControl
{
    public MyControl()
    {
        this.InitializeComponent();

        this.BusyController = (BusyController)this.Resources["BusyController"];
        this.client = new WebClient();
        this.client.OpenReadCompleted += this.client_OpenReadCompleted;
    }

    private WebClient client;

    public void LoadImage()
    {
        this.BusyController.SendMessage(true);

        this.client.OpenReadAsync(new Uri("http://localhost/SomeHugeImage.png"));
    }

    private void client_OpenReadCompleted(object sender, System.Net.OpenReadCompletedEventArgs e)
    {
        using (BinaryReader reader = new BinaryReader(e.Result))
        {
            byte[] data = reader.ReadBytes(int.MaxValue);
        }
       
        this.BusyController.SendMessage(false);
    }
}

View XAML: 

<Page x:Class="MyApp.MyPage" xmlns:local="using:MyApp">
    <Grid x:Name="LayoutRoot">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>

        <local:MyControl Grid.Column="0"/>
        <local:MyControl Grid.Column="1"/>
    </Grid>
</Page> 

It keeps you able to use the class in situations that aren't specific to only allowing busy state management on a view. If whatever ends up being built or put into the MVVM Light "core" is too specific, there will undoubtedly be occasions arise where it cannot be used. I had a situation earlier where I needed to be able to manage the busy state of a behavior to ensure it didn't do something while it was already busy doing something else. Granted, it would have been just as easy to add thread synchronization objects to the behavior, but instead I just tossed the busy controller on the instance to let that manage it.

As long as whatever is baked into the MVVM Light "core" doesn't require an artificially imposed construct like a view to make use of it, I think it could be highly used amongst people that use this wonderful framework you've given us.

Thanks for taking the time to read my feedback!

- Jeff