Andrew Whiddett on Asynchronous Databinding, the Dispatcher class and using DispatcherPriority.Background

Andrew Whiddett is a developer at REZN8, an amazing interactive design firm.  REZN8 built the Netflix demo that was shown at PDC05.  Andrew doesn't have a blog and asked me to be a proxy for a code sample he wrote, which I was more than obliged to do!  He's a great developer and, until he gets his own blog, I'll be proxying any information he wants to share.  The code sample itself is posted here and his mini-article is posted below:

*******************************************************************************************

The WPF samples are all very nice, but they typically are relatively simple implementations to show off a particular API set. We all know that real applications are more complex, and in most cases; getting data takes a period of time, which we need to account for in the application design.

 

The WPF binding system too so powerful to be ignored, in that it allows a complete separation of the UI Design from the business object implementation; this gives the application a level of abstraction that has never been available before, but must be implemented with real world issues; such as return data performance.

 

Here is an example that shows how to deal with some of these real world binding issues.

 

Let’s say there is a CLR based data bound object that you are trying to implement. For example a collection of FOO objects, e.g.

 

  • An Object called FOO that contains a bunch of properties that we want to bind (in this case it just contains a description)
  • A Collection of FOO objects; for example to populate a list box etc
  • A Factory to get the reference to the Collection of FOO objects

 

Here is a starting point

using System;

using System.Collections.Generic;

using System.Text;

using System.Data;

using System.Data.Common;

using System.Configuration;

using System.ComponentModel;

using System.Collections.ObjectModel;

using System.Threading;

using System.Windows;

using System.Windows.Threading;

 

namespace SampleMTApp

{

    public class FOO : INotifyPropertyChanged

    {

        #region Private Data

 

        private string _description = "";

 

        #endregion

 

        #region Constructors

 

        public FOO(string sDescription)

        {

            Description = sDescription;

        }

 

        #endregion

 

        #region Property Handlers

 

        public event PropertyChangedEventHandler PropertyChanged;

 

        protected void NotifyPropertyChanged(string propName)

        {

            if (PropertyChanged != null)

            {

                PropertyChanged(this, new PropertyChangedEventArgs(propName));

            }

        }

 

        #endregion

 

        #region Public Properties

 

        public string Description

        {

            get

            {

                return _description;

            }

            set

            {

                _description = value;

                NotifyPropertyChanged("Description");

            }

        }

 

        #endregion

 

    }

 

      public class MyDataObjects : ObservableCollection<FOO>

      {

        public MyDataObjects()

        {

            FillTheData();

        }

 

        protected void FillTheData()

        {

            for (int iLoop = 0; iLoop < 1000; iLoop++)

            {

                this.Add(new FOO("First String"));

            }

        }

      }

 

    public class Factory

    {

        public MyDataObjects GetMyDataObjects

        {

            get

            {

                return new MyDataObjects();

            }

        }

    }

}

 

 

 

Which we put into a test frame e.g.

 

<?Mapping XmlNamespace="SampleMTApp" ClrNamespace="SampleMTApp"?>

<Grid

      xmlns="https://schemas.microsoft.com/winfx/avalon/2005"

      xmlns:x="https://schemas.microsoft.com/winfx/xaml/2005"

      xmlns:c="https://schemas.microsoft.com/winfx/2005/06/markup-compatibility"

      xmlns:d="https://schemas.microsoft.com/expression/interactivedesigner/2005"

      c:Ignorable="d"

      Background="#FFFFFFFF"

      x:Name="DocumentRoot"

      x:Class="SampleMTApp.Scene1"

      Width="640" Height="480" xmlns:SampleMTApp="SampleMTApp">

 

      <Grid.Resources>

            <Storyboard x:Key="OnLoaded"/>

            <ObjectDataProvider x:Key="FactoryDS" d:IsDataSource="True" ObjectType="{x:Type SampleMTApp:Factory}"/>

            <DataTemplate x:Key="FOOTemplate1">

                  <StackPanel>

                        <TextBlock Text="{Binding Description}"/>

                  </StackPanel>

            </DataTemplate>

      </Grid.Resources>

 

      <Grid.Triggers>

            <EventTrigger RoutedEvent="FrameworkElement.Loaded">

                  <EventTrigger.Actions>

                        <BeginStoryboard x:Name="OnLoaded_BeginStoryboard" Storyboard="{DynamicResource OnLoaded}"/>

                  </EventTrigger.Actions>

            </EventTrigger>

      </Grid.Triggers>

     

      <Grid.ColumnDefinitions>

            <ColumnDefinition/>

      </Grid.ColumnDefinitions>

      <Grid.RowDefinitions>

            <RowDefinition/>

      </Grid.RowDefinitions>

      <ListBox Width="Auto" Height="Auto" MinWidth="0" MinHeight="0" Margin="232,183.6,208,96.4" x:Name="ListBox" ItemsSource="{Binding GetMyDataObjects, Mode=Default, Source={StaticResource FactoryDS}}" ItemTemplate="{DynamicResource FOOTemplate1}" RenderTransformOrigin="0.5,0.5"/>

</Grid>

 

No problem, right ?

Well, the initial load performance is not too great, but this follows the pattern implemented in most of the WPF examples..

Now add in a sleep : why you might ask; well really we are getting this data from somehere else; a database via a WCF call. In the real world, it might take some time, or even never make it at all.

So, the new fill routine looks something like:

protected void FillTheData()

{

for (int iLoop = 0; iLoop < 1000; iLoop++)

{

this.Add(new FOO("First String"));

Thread.Sleep(500);

}

}

What happens when you run this version ? The application takes FOREVER to load up.. Seriously, cancel the app; it will take nearly 8 minutes to display. Realy what we wanted to happen was for the data to flow in as it comes in over the wire rather than blocking the UI… Well that’s what threads are for after all…

So, lets kick off the FillTheData on a thread, that way we can stop blocking the UI threads…

 

In this case we can do this in the constructor, e.g.

 

        public MyDataObjects()

        {

            Thread th = new Thread(new ThreadStart(this.FillTheData));

            th.Start();

        }

 

 

If you run it now, what happens is you get an exception:-

 

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

 

What this means is the collection has to be updated on the UI thread; not so nice; actually at this point I almost gave up trying to use the native data binding; but I really wanted to use it as it makes it so much easier for the UI designers to implement the design, and I really did not want to have to hand code it… well it took a bit of digging, but there is a solution: Kick the ObservableCollection.Add back to the UI (Dispatcher) thread…

 

      

So, in this example we create a method that populates the collection on a callback (the AddAFoo method), and we use BeginInvoke to Invoke a operation (to the callback) on the dispatcher thread.

// NOTE: This needs to run on the dispatcher thread...

protected object AddAFOO(Object data)

{

FOO myNewFoo = data as FOO;

if(myNewFoo != null) this.Add(myNewFoo);

return null;

}

protected void FillTheData()

{

for (int iLoop = 0; iLoop < 1000; iLoop++)

{

Application app = System.Windows.Application.Current;

if (app != null)

{

app.Dispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(AddAFOO), new FOO("First String"));

Thread.Sleep(500);

}

}

}

}

 

So, run this version, and sure enough, the data trickles it goes; which really leaverages the power of the databind, without the UI designer having to worry about the implimentation at all.