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:

[caption id="attachment_26" align="aligncenter" width="576" caption="The application at startup looks empty and unresponsive until data is loaded."]image_21[/caption]

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:

[caption id="attachment_27" align="aligncenter" width="576" caption="Between page changes or other reloads of the data, there is little indication that work is going on in the background and that any changes to the data in the DataGrid are likely to disappear from view momentarily."]image_41[/caption]

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:

[caption id="attachment_28" align="aligncenter" width="576" caption="With the Activity control, activity UI ("Loading" in this case) covers the controls whose data is changing, and the user can see that those controls are busy with some other operation."]image_61[/caption]

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!