- Part 2 : http://shujaatsiddiqi.blogspot.com/2011/02/wpf-performance-improvement-for-mvvm.html
- Part 3: http://shujaatsiddiqi.blogspot.com/2011/03/wpf-performance-improvement-for-mvvm.html
In this example we are discussing the various performance improvement for a WPF Binding developed using MVVM. This is for enhancing user experience by increasing the responsiveness of the system. MVVM based application is highly dependent upon the WPF Binding system. I think if we use certain features of Binding system then we can achieve this.
<Window x:Class="WpfApplication_ASync_Binding.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication_ASync_Binding" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <local:MainWindowViewModel /> </Window.DataContext> <Grid> <TextBlock Height="56" HorizontalAlignment="Left" Margin="40,71,0,0" Name="label1" VerticalAlignment="Top" Width="434" Background="Aquamarine" > <TextBlock.Text> <Binding Path="StudentName" StringFormat="testtt:{0}" /> </TextBlock.Text> </TextBlock> </Grid> </Window>
We are not changing the code behind of the view so it should be as created by default. The view model:
namespace WpfApplication_ASync_Binding { using System.ComponentModel; class MainWindowViewModel : INotifyPropertyChanged { private Student _student; public MainWindowViewModel() { _student = new Student(); } private string _studentName; public string StudentName { get { return _student.StudentName; } set { _student.StudentName = value; OnPropertyChanged(StudentName); } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } }
The view model is using _student as model. This is an instance of Student class. As you can see StudentName is just a proxy property for the model’s property named StudentName. The definition of Student is as follows:
namespace WpfApplication_ASync_Binding { class Student { public string StudentName { get; set; } public Student() { StudentName = "Muhammad"; } } }
Let’s run this application. The output should appear as follows:
Now let’s change this a bit. Let’s consider Student is using some complex logic for its property StudentName. It might be using a web service which might be slow. Now since we are using proxy property for binding this delay would slow down the binding and the form would become idle. Let’s create this scenarios by just introducing a delay using Thread.Sleep() as follows:
namespace WpfApplication_ASync_Binding { class Student { private string _studentName; public string StudentName { get { //imaginary delay Thread.Sleep(10000); return _studentName; } set { _studentName = value; } } public Student() { StudentName = "Muhammad"; } } }
Now run the application. The same form would display but after a delay of atleast 10 seconds.
Basically when Binding system would access the getter for StudentName property in the DataContext. It would be delayed because the view model would be trying to get the Model’s property resulting in a delay of atleast 10 seconds (because of Thread.Sleep). So form instance is created. It assigns the DataContext as MainWindowViewModel in InitializeComponent. When it attempts to bind the Text property of TextBlock. It experiences this delay. So the delay is because of binding. If this binding becomes asynchronous then the performance could improve. You should be glad to learn that WPF Binding system supports making its binding asynchronous by providing IsAsync property in Binding markup extension. Let’s use this!
We update the view as follows:
<TextBlock.Text> <Binding Path="StudentName" StringFormat="testtt:{0}" IsAsync="True" /> </TextBlock.Text>
Now run this. The form appears as follows:
This would be shown like this for atleast 10 seconds. During this time, it starts a ThreadPool thread on the background to get the Binding source. As soon as the source property becomes available, the binding updates the view as follows:
Well this is good but user sees nothing on the screen for atleast 10 seconds. We might want to show something else to the user until the data becomes available as a fallback mechanism. WPF binding system supports that too. You can achieve that by setting FallBackValue propery of Binding as follows:
<TextBlock.Text> <Binding Path="StudentName" StringFormat="testtt:{0}" IsAsync="True" FallbackValue="Test Fallback value" /> </TextBlock.Text
Now run the application. The window is shown as follows:
This is shown like that for 10 seconds showing the Fallback value for Binding. You can see that StringFormat is not applied for FallbackValue. After 10 seconds, it updates itself as follows:
In order to have more sophisticated fallback mechanism, you might want to consider Priority Binding.
http://shujaatsiddiqi.blogspot.com/2010/09/wpf-priority-binding.html
The world is not simple. Let’s make it a little complex by updating the constructor of our model (Student) as follows:
public Student() { StudentName = "Muhammad"; Thread.Sleep(20000); }
This is to simulate the 20 seconds further delay in Binding. Let’s run the application. For 20 seconds, nothing appears on the screen. After that the window appears with the fallback value. The fallback value remains on the view for 10 seconds. After that the view is updated with the finalized value for StudentName with applied StringFormat. Now the main question is why this window appears after 20 seconds when binding is asynchronous. Basically Binding.IsAsync just makes the Binding asynchronous. If there is a delay in the construction of DataContext, this would still be on the UI thread hence synchronous. The construction of DataContext (MainWindowViewModel) is causing this delay because of delay of instantiation of the Student (model) in the constructor of view model [Thread.Sleep(20000)].
In order to improve this behavior we can update the way DataContext is constructed. We can construct the object using ObjectDataProvider. This is to utilize the asynchronous instantiation feature of ObjectDataProvider. We can cause asynchronous construction by setting IsAsynchronous for ObjectDataProvider. Let’s update the definition of the section of XAML for view where we are assigning DataContext as follows:
<Window.DataContext> <ObjectDataProvider ObjectType="{x:Type local:MainWindowViewModel}" IsAsynchronous="True" /> </Window.DataContext>
Now run the application. As we run the application there is no delay and the following screen appears:
This is maintained for 30 seconds. The 20 seconds is for the construction of DataContext (causing construction of model) and the other 10 seconds is for the delay in the getter for StudentName property from DataContext. Basically the Fallback value is maintained until the value is available no matter the delay is called by the DataContext construction or the available of its property.
10 + 20 = 30 seconds
Eventually the window is updated as follows:
Download: