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.
Here we are going to see how to apply throttling properly to our bindings to make our application interface more responsive.
WPFAutoDelayedUpdates
.BaseClass.cs
class from the Implementing asynchronous error handling with INotifyDataErrorInfo recipe, which implements the INotifyDataErrorInfo
and the INotifyPropertyChanged
interfaces for us.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"); } } }
MainWindow.xaml.cs
class and add the following code in the constructor:public MainWindow() { InitializeComponent(); BookModel bm = new BookModel() { BookRating = 50 }; this.DataContext = bm; }
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>
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" />
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.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.
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.