Displaying background activity in a Silverlight RIA application

Well, I told you there would be exciting news at MIX!  After months of working on Silverlight 3 Beta 1, it’s finally available for you to try!  Also coming live for MIX is the “.NET RIA Services” March 2009 Preview, which will assist in building multi-tiered applications and provides end-to-end support for things like data validation, authentication, and roles, as well as data access.

With the Silverlight 3 beta, you can build really rich user experiences for the web.  For the beta, we’ve added a number of controls and features to enable rich experiences when working with data (e.g. an DataForm, DataPager, ErrorSummary, an updated DataGrid, etc.), and the .NET RIA Services preview makes it much easier to bring the data on your servers down to your Silverlight client.

All of this presents a fairly common problem: what should you do with your UI while data is being loaded or work is being done in the background?  If the amount of work done or data to load is small and completes quickly, it would probably make sense to leave the experience uninterrupted for the user.  For larger amounts of work, however, leaving the user with an unresponsive UI or worse with a responsive UI that doesn’t work properly because data is in the midst of being reloaded is less than ideal.

Imagine, for example, that you’re using a DataGrid whose data takes 3 seconds to load (without taking any explicit steps to show activity).  When your user first loads his application, he sees this:

image_21

Not a terribly appealing user experience.  Is the data loading?  What is the user allowed to do with the app right now?  One could show a progress bar somewhere on the screen, but how do you tell the user not to touch the DataGrid until the data is fully loaded?

In the above application, the data is paginated in order to keep the load sizes small and thus speed up the browsing experience.  As a result, moving from page to page may cause load operations to happen as well.  At this point, it is clear that something is strange with the user experience during a page change.  The user would see something like this:

image_41

Even though the data in the DataGrid is the previous page’s data, everything is still interactive.  I can navigate from item to item and attempt to make changes, but in a few seconds everything will be wiped out when the data finishes loading.

There are a number of options for dealing with this:

  • Progress bar
  • Enable/disable controls
  • Show/hide controls
  • Combinations of these

I’ve been playing around with a slightly different idea: an “Activity” control.  This is a control that displays activity (when appropriate) over its content.  During active periods, it adds an overlay to its wrapped controls, graying them out, disabling them, and displaying a progress indicator, like so:

image_61

This way, the user knows which controls he can interact with, and he is also given some additional information (“Loading…” in this case) about why the application is busy.

The control I’m experimenting with itself is pretty straightforward.  Some of its more important features:

  • IsActive property – this can be bound to a boolean value that represents activity.  With .NET RIA Services’ DomainDataSource, this is the “IsBusy” property.
  • DisplayAfter property – this allows you to set a minimum amount of “active” time before the activity UI appears.  This way, tiny bouts of activity don’t interrupt your user’s workflow.
  • MinDisplayTime property – this allows you to set a minimum amount of time to display the activity UI.  This way, users have enough time to digest why their screen changed, and the activity UI won’t just flash on and off the screen.
  • AutoBind/ActivityPropertyName – if AutoBind is set to true (which is the default), the control will search its content for controls that have a property with the name specified (“IsBusy” by default), and will automatically hook itself up to display activity whenever any of its children are busy.  If no such children are found, the control will just use its IsActive property.
  • ActiveContentTemplate property – allows you to set what is actually displayed (and defaults to what you see above!)

With this combination of features, the control makes it easy to add activity UI to your application.  In particular, it works pretty well with the DomainDataSource for .NET RIA Services, since it’s really easy to use ElementName binding to bind IsActive to the IsBusy property on the DDS.  Furthermore, if the DDS is actually inside of the activity control, it will automatically pick up the DDS’s activity:

<activity:Activity>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <riaControls:DomainDataSource x:Name="northwindData"
                              LoadMethodName="LoadProducts"
                              LoadSize="20"
                              AutoLoad="True">
            <riaControls:DomainDataSource.DomainContext>
                <northwind:NorthwindDomainContext />
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>
        <data:DataGrid ItemsSource="{Binding Data, ElementName=northwindData}"
                       CanUserSortColumns="{Binding HasChanges, ElementName=northwindData, Converter={StaticResource boolConverter}}">
        </data:DataGrid>
        <dataControls:DataForm Grid.Column="1"
                               ItemsSource="{Binding Data, ElementName=northwindData}">
        </dataControls:DataForm>
    </Grid>
</activity:Activity>

Otherwise, it’s a simple matter of using ElementName binding to wire up your activity UI:

<activity:Activity IsActive="{Binding IsBusy, ElementName=northwindData}">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <data:DataGrid ItemsSource="{Binding Data, ElementName=northwindData}"
                       CanUserSortColumns="{Binding HasChanges, ElementName=northwindData, Converter={StaticResource boolConverter}}">
        </data:DataGrid>
        <dataControls:DataForm Grid.Column="1"
                               ItemsSource="{Binding Data, ElementName=northwindData}">
        </dataControls:DataForm>
    </Grid>
</activity:Activity>

With this control, it’s easy to have multiple controls all show activity at the same time (either by wrapping them all with the Activity control or by binding activity controls to the same value), and the “busy” experience can be consistent across the app.

This is just a simple prototype control, but I’d love to hear what you think!  Is there something important that’s missing?  Are there other user experience deficiencies you see (either when using SL3 for RIA applications generally or with the .NET RIA Services preview)?

In the meantime, if you’d like to play with my prototype/source, you can find it below.  This code is provided under the Microsoft Public License and is also provided "as is", without warranty of any kind.

Enjoy!

 

Update 6/11/2009 – A new version of the control is available here.

Update 7/11/2009 – Samples updated for Silverlight 3!

Update 9/14/2009 – I’ve made a small update to the Activity control to fix a performance issue.  You can find it here.

P.S. Thanks to CorrinaB for helping me snazz up the UI!

58 thoughts on “Displaying background activity in a Silverlight RIA application”

  1. I am doing this ‘manualy’ in my applications now, for every form / area in which I’m updating data I have to build additional Grids and containing Canvases with TextBlocks and the in code control when the “LoadingGrid” is visible during my async calls to my WCF services. It makes me write a lot of “repeat, glue” type code (as I have to handle WCF timeouts, errors, etc., etc.).

    Having a control of this type to aid in this development would help me tremendously.

    Paul Jenkins

  2. This is great. I have something very similar but I use an attached property on a Panel to integrate it to mark-up.
    I think I like your solution better.

  3. Hi Dave;

    Yes, I saw that during Brad’s presentation and looked great. I remember seeing it I think at Alex CTP 5 as part of the data source, but it went away in the following CTP and I’m glad it’s back again.

    I would like to make a couple of suggestions:
    a) Allow us to set the mask color and it’s opacity.
    b) Same thing for progress bar color.
    c) hopefully the colors will also respect the theme color.
    d) If possible to give us a couple of choices like progress bar or the circle that is part of SL initial load time.

    Thanks!
    ..Ben

  4. Thanks for this control. Was about to use the child window control to simulate this behavior.

    Some improvements would be
    1. ability to set a custom message and auto size the progress bar based on the length of the message.
    2. When displaying the activity at startup, there is a visible flash as the controls are rendered and the activity control appears later. Is there a way to avoid this? Code below:

    <StackPanel…

    Thank you

  5. the code…

    <activity:Activity IsActive=”True” x:Name=”activity” DisplayAfter=”00:00:00.00″>
    <Canvas><StackPanel…</Canvas>
    </activity:Activity>

  6. Thanks David, this is fantastic – a MUST HAVE for an data driven Silverlight website.

    I look forward to seeing this in the toolkit/release of SL3

    Thanks again!

  7. nice idea!
    (haven’t seen it working yet)
    i’ve done something similar in an app’ i m developing, but instead of disabling per-element i disabled the whole app.

    i really like you’re idea.

    a problem i’ve encountered is what to do when you have some concurrent async calls.
    example: call 1 to a service, call 2, call 1response, call 2 response.
    this means ‘call 1′ is deactivating but only the response from ‘call 2′ will activate.

    i will keep an eye on this.

  8. Hey folks, really sorry I was having some problems with getting comments to show up. The blog/site is new, so I’m still working out a few kinks! Thanks for all the feedback and for bearing with me!

  9. Yes, very good, although for very short delays, there is a flicker as it suddenly appears – after the MinDisplayTime and then suddenly goes away. It can be a little jarring when you are paging through a lot of records.

    Can you put in a smoother animation by default?

  10. Hi everyone! Could somebody draw a way how to customize the message. Very interesting how to do it for current control. Thanks

  11. I think I’ve found a bug you might be able to trace rather quickly. I have built a sample app based on the Silverlight Business Application template to demonstrate it. I added Northwind to (EF) with three tables (Categories, Suppliers, Regions). I created a new Maintenance menu item on the MainPage, and then on the MaintenancePage, I have a sub-menu on the left and another Navigation frame on the right. When an item is clicked, the right frame fills with a simple page with a DataForm connecting to each table via a DomainDataSource in markup. I have code to set the DataForm.CurrentIndex to 0 when the dds DataLoaded event fires. The behavior is that the first form fills in and the navigation elements are enabled. The other two forms get their data, but the navigation elements are disabled. If I comment out the ActivityControl in the markup, they all work fine. I can get you this sample app zipped up (about 3MB) if you’d like to take a look. Thanks.

  12. Love the control, exactly what I was looking for.

    I’ve modified it to be perfect for my needs… here is what I added (as suggested additions for the future):

    I set the ProgressBar, x:Name=”contentControl”, and the default control itself to have IsTabStop set to false in the Control Template. Otherwise you get mysterious tabbing issues.

    I added storyboards to the Active and Inactive states to set the IsHitTestVisible and IsEnabled values immediately, without waiting for the delay. The delay to reduce flashing is great for sub 3s operations, but I still need all the controls disabled immediately to prevent users from double clicking buttons and causing bad things to happen.

    I mean asynchronous stuff is great for long running operations, but there are tons of very short operations in our LOB app that need to appear synchronous to the user. This control allows me to force some synchronicity on to Silverlight. Thanks!

  13. Neat control David! A few suggestions.

    1. A public property for the display text. Simple and necessary… I know we can swap out the template… but it’s overkill when all you want is to set the string.

    2. More of a pondering really. I’m interested in the AutoBind feature. I’m nor really sure about querying the properties of the child controls to see if they have a particular property. None of the base SL controls have properties that would be intended for that use. I tried adding an attached property… no joy! So then I created myBusyButton:Button that has an IsBusy property and it works fine.

    However, most people won’t want to subclass their controls. I think it’s more likely that people might bind controls IsBusy property to it’s ‘buddy’ on a ViewModel.

    If our ViewModelBase class has an IsBusy property then the control need not query all its children.

    I can see that you are storing an internal list of controls that are busy, popping them off the list as it changes. It works, but maybe better in a viewModel… if it knows about all it’s child viewModels.

    3. If a ViewModel ‘knows’ it’s child ViewModels, then it would be rather easy to have not only a ‘busy’ dialog but an active list of activities… it would be nice for the end user to see what is going on if there are multiple activities.

    Any thoughts? Do you know if anyone is working on something similar?

    1. 1. Yup — totally agreed. I’ve got a working version with that modification that I’ll be posting soon.
      2. Yeah… this becomes nice when I’m using DomainDataSource from .NET RIA Services. This is a control that has that kind of “busyness” indication. I imagine there may be more in the future. Maybe this isn’t quite as useful of a feature… what do others think?
      3. Yep — I actually did a bunch of prototyping of this exact concept. I tried to have it keep the list of “Busy” controls and expose them for display, but I got all sorts of circular dependency errors. Something like what you’re suggesting might be useful, though. Presumably, if I gave you a place where you could bind to these things on your ViewModel (i.e. instead of having “displaytext”, it was a “displaycontent” of type object), you’d be in good shape to add a listbox bound to that list or whatever UX you think is appropriate.

  14. I don’t know how to use this control without doing :

    Activity.IsActive = true;
    ….
    ….
    Activity.IsActive = false;

    How can i bind it when i use a web service wcf to get data OnNavigatedTo :(

    Thank you

    1. I’m not sure I understand the issue. You need only bind to a property that is either a DependencyProperty or on a class that supports INotifyPropertyChanged with a return type of “bool”. Otherwise, setting it explicitly is perfectly fine as well.

  15. Hi
    After a research we have found a nasty performance problem with the Activity Control. Your ContentControl is listening to the LayoutUpdate event. Based on the default value (=True) of the AutoBind property (generic.xaml), the control iterates on any keyboard/mouse event through the children control hierarchy.

    Until a control is not garbage collected – including hidden controls – it will be considered. After a while (loading/unloading controls), the application performance gets diminished.

    Cheers,
    Jani

    1. So, I’ve looked this, and you’re quite right. I’m about to blog an update that makes this value default to false, which should help resolve this issue for now. I wonder how many people actually like that AutoBind functionality, or whether it should be removed?

  16. Hi, i’ve put the activity control in my masterpage. I get something like that :

    How can i active it in page ? I don’t succed in getting parent of my frame :( do you knw how to do this ?

    Thanks a lot.

    1. One way to do it is to bind into the page for its status (i.e. make a property on the pages to bind the Activity control to)

      If you do that, you can use IsActive=”{Binding Content.ActivityProperty, ElementName=MyFrame}” on your activity control, and it will stay bound to whatever page is currently being displayed.

      Gotta love binding!

  17. Hi, I am trying to follow Ria services examples, but I am unable to build the solution.
    Error The type or namespace name ‘Activity’ could not be found (are you missing a using directive or an assembly reference?).

    I have tried in VS2008 and VS2010, same thing. (Deleting the reference and adding it back in, did not work either.)
    However, in a separate SL project, I am able to have a valid reference to a build dll.
    Any suggestion? You can email me at
    eddy33012atyahoo.com

  18. Using this control as the root element destroys the ability to open your UserControl / Page in Blend. Also, I have huge problems using 3rd party controls which expect the root element to be a grid in order to function correctly. Is there some way to have this overlay without being the root element?

    1. There is no requirement to use the control as the root element. It’s simply a content control — wrap whatever you want it to appear over in it. If you have some other control that requires the root to be a grid, put it inside of the grid with other content within it.

  19. i am using your control and it looks great.
    but now i have made some changes in my code like -
    i put my domaindatasource on a ResourceDictionary xaml file and bind it like :

    this is not working…
    any tips ?

  20. The activity control has a memory leak that is pronounced if you bind IsActive to the view model. All of the data in my view model is being held onto by the Timer inside the activity control. The Timer class has been known to cause memory leaks in the past. Perhaps using DispatcherTimer or another method would best suit this.

  21. I am also having a problem with a memory leak while using the Activity Control.
    I am setting the IsActive from code, and end up with handles stuck in memory point to the _TimerCallBack. After removing the Activity Control from my views the problem disappeared.
    Any recommendations?

  22. Greetings I recently finished going through through your blog as well as I’m very impressed. I do have a couple inquiries for you personally however. You think you’re thinking about doing a follow-up submitting about this? Will you be going to keep bringing up-to-date too?

    1. I certainly haven’t abandoned it — I’m no longer on the Silverlight team itself, but I’m still building things with Silverlight, and intend to continue to post as long as I’ve got new/interesting content. I’ve got another post in the works (code written — just need to document it and write about it), it’s just a question of time.

    1. The BusyIndicator is a control like any other and follows the same rules for layout. It is simply a ContentControl that covers up its content with the Busy display when IsBusy is true.

  23. Hi,
    In the comments I noticed you said this control eventually became the BusyIndicator in the SL 4 toolkit. I’m using the control, and I’ve found some odd behavior. I’m binding IsBusy to a property in my ViewModel, and the content it’s surrounding is in one tab. If i set it to true, then go to another tab, change the IsBusy property to false while the focus is on the second tab, and then go back to the previous tab, the progress indicator is go, but the content is still disabled. If you change tabs and go back again, everything is enabled. Thanks.

  24. It’s good to see the application of Silverlight in this way. What I like about Silverlight is that it has very rich support for reading data of numerous types from web services to RSS feeds to static XML files. It also has methods to facilitate the deserialization of XML or JSON into strongly types objects for simple manipulation and binding.

  25. Yep. There was something like this as part of the DomainDataSource in some of our earlier CTPs, but we ultimately decided to remove it in favor of something more general. I think it’s a great thing to have around — very useful! :)

  26. Bob,

    I am curious if you found a work around for your issue. I am encountering the same problem and would love to continue to use the ActivityControl if there is a way to get around this. Thanks.

  27. You’ve got a few options:
    -Build the Activity Control project and add a reference to the built dll in Visual Studio or Blend by right-clicking the list of references in the project view and choosing to add.
    -Add the Activity Control project to your solution and add a project reference to the control.

    Either of these should get you going with the control.

  28. More generic would be a great way to go. Something similar to the AJAX updatepanel. Basically, I’d love to be able to wrap whatever content inside of the Activity control and then activate/deactivate it as desired within the code behind. Perhaps I missed something, is there a way to do that now?

  29. You can certainly set IsActive on the control in code-behind to get this kind of behavior. In fact, a lot of what you see with the Activity control is based upon the updatepanel. What specifically are you trying to do?

Leave a Reply