We are attempting to exploit the features of these two interfaces to get the benefits of push based updates from our models. Model might be updated due to any updates in the back-end system. We are trying to update these updates using these contract to the view model. Currently we use events based mechanism to propagate these changes to the view model which is another implementation of Observer pattern in .net.
Let's create a simple view with two Text Blocks. One TextBlock is to display the name of Student and other is to show it's Id. As you can see in the code below that they are bound to StudentName and StudentId properties of DataContext.
<Window x:Class="WpfApp_MVVM_ReactiveModels.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp_MVVM_ReactiveModels"
Title="MainWindow" Height="350" Width="690">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TextBlock Height="36" HorizontalAlignment="Left" Margin="272,46,0,0"
Name="textBlock1" VerticalAlignment="Top" Width="326"
Text="{Binding StudentName}" />
<TextBlock Height="32" HorizontalAlignment="Left" Margin="270,116,0,0"
Name="textBlock2" VerticalAlignment="Top" Width="330"
Text="{Binding StudentId}" />
</Grid>
</Window>
As shown in the above XAML code, the data context is assigned with a new instance of MainWindowViewModel. So this view model is expected to have the properties StudentName and StudentId so that the text blocks could bind their Text properties to them.
Let's define the expected view model now. We need to provide change notification support for any updates in StudentName and StudentId properties to the view. It is provided by implementing INotifyPropertyChange interface. It has the only requirement is to provide a PropertyChanged event. Now the view model needs to update itself based on the updates in the model. For that, it has to observe the changes in the I. In the constructor, It is subscribing using Subscribe() method available in the model. The only requirement to implement IObservable is to implement three methods OnNext, OnCompleted and OnError methods. OnNext provides the new member added the collection. It can very well provide the updated model. We are using it to provide the updated model instance (Student). Similarly, OnError can be used to communicate and Exception message the the observer. For this simple example, We are simple ignoring the implementation of OnCompleted and OnError methods. OnNext() gives us access to the updated Student object. We are using it to update the view model properties. The updates in the view model's properties are propagated to the view using WPF Data Binding system.
namespace WpfApp_MVVM_ReactiveModels
{
using System;
using System.ComponentModel;
class MainWindowViewModel : IObserver<Student>, INotifyPropertyChanged
{
//Model
Student Model;
//Constructor
public MainWindowViewModel()
{
Model = new Student();
this.StudentId = Model.StudentId;
this.StudentName = Model.StudentName;
//subscribe observer to the Model
Model.Subscribe(this);
}
#region Properties
string _studentName;
public string StudentName
{
get { return _studentName; }
set
{
_studentName = value;
onPropertyChanged("StudentName");
}
}
int _studentId;
public int StudentId
{
get { return _studentId; }
set
{
_studentId = value;
onPropertyChanged("StudentId");
}
}
#endregion Properties
#region IObserver implementation
public void OnCompleted()
{
//throw new NotImplementedException();
}
public void OnError(Exception error)
{
//throw new NotImplementedException();
}
public void OnNext(Student value)
{
//throw new NotImplementedException();
this.StudentId = value.StudentId;
this.StudentName = value.StudentName;
}
#endregion IObserver implementation
#region INotifyPropertyChanged implemention
public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged implemention
}
}
Now we define our model (Student). It has two properties StudentName and StudentId as expected by the above view model. In order to be observable, we are implementing IObservable interface for this. IObservable keeps a list of all observers so that they could be notified in case any updates. The only requirement of the contract is Subscribe method. This method is supposed to return an IDisposable. Disposing this should be unsubscribing this observer.
class Student : IObservable<Student>
{
public string StudentName { get; set; }
public int StudentId { get; set; }
List<IObserver<Student>> observers = new List<IObserver<Student>>();
Timer t = new Timer(2000);
public Student()
{
StudentId = 1;
StudentName = "Muhammad";
t.Elapsed += new ElapsedEventHandler(t_Elapsed);
t.Interval = 1000;
t.Start();
}
void t_Elapsed(object sender, ElapsedEventArgs e)
{
this.StudentId++;
if (StudentId != 20)
{
observers.ForEach((observer) => observer.OnNext(this));
}
else
{
observers.ForEach((observer) => observer.OnCompleted());
t.Stop();
}
}
//implementation of IObservable
public IDisposable Subscribe(IObserver<Student> observer)
{
if (!observers.Contains(observer))
{
observers.Add(observer);
}
return new Unsubscriber<Student>(observers, observer);
}
}
In order to simulate the updates, we are using a Timer to update the properties. In the Tick event handler we are updating StudentId. Until StudentId is lesser than 20, it keeps notifying the observers using OnNext() on the observers. After that it notifies them about the completion of all updates using OnCompleted() method.
We define the UnSubscriber as used in the above model. It is implementing an IDisposable. When it is being constructed, the observer is added to the _observers collection. As expected, it is removing the observer from the observer list in the model.
class Unsubscriber: IDisposable
{
private List<IObserver<T>> _observers;
private IObserver<T> _observer;
public Unsubscriber(List<IObserver<T>> observers, IObserver<T> observer)
{
this._observers = observers;
this._observer = observer;
}
public void Dispose()
{
if (_observer != null && _observers.Contains(_observer))
{
_observers.Remove(_observer);
}
}
}
When we run the application, it is shown as follows:
After OnCompleted(), the StudentId has the value 19. The view is finally updated as follows:
Note:
This is one of the times when you desperately need some feature but restricted due to its non-availability in the language. Unlike Java, C# does not support anonymous inner classes. It just supports anonymous types which can not implement an interface and can encapsulate no methods.
return new IDisposable()
{
//well java also has this limitation that an inner class can only access final members of enclosing method. In this case observer is not final :(. Let's assume it does. This would be the ideal implementation.
IObserver<Studen> localObserver = observer;
public void Dispose()
{
if (localObserver != null && observers.Contains(localObserver))
{
observers.Remove(localObserver);
}
}
}
As you can see that I have used observers collection of class containing this anonymous class which is a feature of Java anonymous inner classes. This is not supported by C#, Alas!
Timer event Handlers in other threads:
In this example we have used System.Timers.Timer. As we have discussed earlier that the event handlers for the Elapsed event of this timer is executed on ThreadPool thread.
http://shujaatsiddiqi.blogspot.com/2010/10/timers-for-net-applications.html
Because of this OnNext(), OnCompleted() and OnError() are also executed in ThreadPool thread.
Since UI related controls have thread affinity to UI thread then how come we don't have any exception? Basically this is because of implicit dispatching of updates to UI thread by the setters in INotifyPropertyChanged. No matter from what thread the updates take place, they are always successfully dispatched to UI thread.
Download:
1 comment:
Thank you for sharing your work
Post a Comment