Throttling data source update delays

With WPF 4.5, we can now control how the data source that is bound to a part of the UI, gets updated. The best example here is a slider bound to a value, which, for example, has to perform a calculation. With WPF 4.0, the property setter was called for every changed event that was launched by the binding in place and, if we didn't do anything to prevent the excessive calculations, we could end with a responsiveness problem. This could be even worse if some calculation was being performed in response, such as updating the total price.

Now, this doesn't happen, as we can control the delay after the property stops changing, before updating the source. This means that we can change a UI element and we can control how the bound property gets updated. Adding a delay to it will benefit our performance, so that thousands of updates cannot be thrown.

In the slider example, its PropertyChanged event was invoked many times for every movement. Now we can instruct it to get invoked a bit after the slider stops moving.

Getting ready

In order to use this recipe you should have Visual Studio 2012 installed.

How to do it...

Here we are going to see how to apply throttling properly to our bindings to make our application interface more responsive.

  1. First, open Visual Studio 2012 and create a new project. We will select the WPF Application template from the Visual C# category and name it WPFAutoDelayedUpdates.
  2. We will add the BaseClass.cs class from the Implementing asynchronous error handling with INotifyDataErrorInfo recipe, which implements the INotifyDataErrorInfo and the INotifyPropertyChanged interfaces for us.
  3. Add a class and name it BookModel.cs, adding the following code:
    class BookModel : BaseClass
    {
        private int bookRating;
    
        public int BookRating
        {
            get { return bookRating; }
        set {
                ValidateRating();
                bookRating = value;
                OnPropertyChanged("BookRating");
            }
        }
    
        private async Task ValidateRating()
        {
            //await Wait_a_bit();
            Thread.Sleep(200);
            Random rnd = new Random(DateTime.Now.Millisecond);
            int likeness = rnd.Next(0, 6);
            if (likeness > 2)
            {
                this.AddError("BookRating", "The rating is not valid", false);
            }
            else
            {
                this.RemoveError("BookRating", "The rating is not valid");
            }
        }
    }
  4. Go to the MainWindow.xaml.cs class and add the following code in the constructor:
    public MainWindow()
    {
        InitializeComponent();
    
        BookModel bm = new BookModel() { 
            BookRating = 50
        };
        this.DataContext = bm;
    }
  5. Continuing, we will open the MainWindow.xaml class and create the interface. Divide the grid into two columns, one for the labels and another for the displayed and editable values. Add the Slider control for the editable values. The XAML code should look like the following:
    <Window x:Class="WPFAutoDelayedUpdates.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="211:"/>
                <ColumnDefinition Width="299:"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="30"/>
                <RowDefinition Height="30"/>
                <RowDefinition Height="30"/>
                <RowDefinition Height="30"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Label Content="Select the book Rating" Grid.ColumnSpan="2" Margin="0,0,0.4,0.4"/>
            <Label Content="Without Data Source Update Delay: " Margin="0,30,1.2,0.8" Grid.RowSpan="2"/>
            <Label Content="With Data Source Update Delay: " Grid.Row="2" Margin="0,0.2,1.2,0.2"/>
            <Label Content="The bound value: " Grid.Row="3" />
            <TextBox TextWrapping="Wrap" Text="{Binding BookRating, Mode=TwoWay}" Grid.Column="1" Grid.Row="3" IsReadOnly="True" />
            <Slider x:Name="SliderNotDelayed" 
                    Value="{Binding BookRating, Mode=TwoWay}"
                    Grid.Column="1" Grid.Row="1" Margin="3" Maximum="100" />
            <Slider x:Name="SliderDelayed" 
                    Value="{Binding BookRating, Mode=TwoWay, Delay=200}"
                    Grid.Column="1" Grid.Row="2" Margin="3" Maximum="100" />
    
        </Grid>
    </Window>
  6. We will bind the Sliders and TextBox controls in TwoWay mode to the BookRating property. On the second binding, we specify the Delay keyword with a 200 milliseconds delay:
    <Slider x:Name="SliderDelayed" 
            Value="{Binding BookRating, Mode=TwoWay, Delay=200}"
            Grid.Column="1" Grid.Row="2" Margin="3" Maximum="100" />
  7. When we execute the application we will see the following UI:
    How to do it...
  8. If we slide the first Slider to one side or the other, we will see that the response is sluggish, since the value is updated for every movement, invoking the setter, which executes a costly operation (Thread.Sleep(200);). The second Slider has a much smoother response, because it only raises the setter 200 milliseconds after its value has finished changing.
  9. The power of this feature becomes crystal clear with this example. It will make life much easier for yourself and your users.

How it works...

We used a base class from the Implementing asynchronous error handling with INotifyDataErrorInfo recipe as a starting point, providing us with an INotifyPropertyChanged interface implementation as a base to work on our new code.

We implemented a class derived from our BaseClass and created only one property, BookRating, of integer value. When set, it raises its PropertyChanged event with the OnPropertyChanged("BookRating"); instruction.

Additionally, the setter is validated previously to a change, with a random validation method that we had set with a delay of 200 ms. With this we can simulate a long running process, a call to a service, or both.

We have to be careful with some of the issues involved: for example, if the running time of the process and validation is not fixed, a setter launched later that runs faster would execute the setter before the previous call. Additionally, there are some controls that are constantly launching events. The movement of the Slider control generates a constantly changing value, or MouseMove, which typically raises a lot of events.

These cases had to be handled previously with a technique called throttling, which was not easy to implement. The introduction of RX (Reactive Extensions) was an improvement, as we could do things such as:

Observable.FromEvent<PropertyChangedEventArgs>(x => this.PropertyChanged +=x, x => this.PropertyChanged -= x) 
    .Where(x => x.PropertyName == "NameOfSlider") 
    .Select(_ => this.NameOfSlider) 
    .Throttle(TimeSpan.FromMilliseconds(50)); 

Now, we can implement this in an even easier way.

Note

For more information on RX we recommend the official source at http://msdn.microsoft.com/en-us/data/gg577609.aspx.

Next, we created our UI, with two sliders and a textbox to display the value. We set one of the sliders with the delay attribute and the other without it:

<TextBox TextWrapping="Wrap" Text="{Binding BookRating, Mode=TwoWay}" Grid.Column="1" Grid.Row="3" IsReadOnly="True" />
<Slider x:Name="SliderNotDelayed" 
        Value="{Binding BookRating, Mode=TwoWay}"
        Grid.Column="1" Grid.Row="1" Margin="3" Maximum="100" />
<Slider x:Name="SliderDelayed" 
        Value="{Binding BookRating, Mode=TwoWay, Delay=200}"
        Grid.Column="1" Grid.Row="2" Margin="3"

This Delay variable is in fact the throttling delay. I recommend the curious readers to explore some more and remember this parameter as a throttling delay instead of a simple delay. Its meaning and function becomes clearer and it becomes easier to remember what it does.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset