Now we will start implementing the viewmodels for our application, I will start with the person viewmodel interface. The interface is defined as:
Next step is to create design data that can be used by Visual Studio WPF designer or Expression Blend. The design data is a class that implements IPersonViewModel interface and it is a simple class with some initial values set. The implementation of the design data class looks like:
Now we need to build the views. We have two different views for the
PersonViewModel, one will be to display/edit details and the other will be to show the person data in the list. We will start first with the list item and it will be called
PersonSummaryView. The XAML for the view will look like this:
<UserControl x:Class="AddressBook.Views.PersonSummaryView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:designData="clr-namespace:AddressBook.DesignData"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=designData:DPersonViewModel, IsDesignTimeCreatable=True}"
d:DesignHeight="55" d:DesignWidth="500" BorderBrush="DarkGray" BorderThickness="1" Background="#33FFFFFF">
<UserControl.Resources>
<ControlTemplate x:Key="LinkButton" TargetType="{x:Type Button}">
<TextBlock HorizontalAlignment="Center" Text="{TemplateBinding Content}" Cursor="Hand" VerticalAlignment="Center" TextDecorations="Underline" Foreground="#FF0025BA"/>
</ControlTemplate>
</UserControl.Resources>
<Grid Margin="5">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBlock Text="{Binding Name}" FontSize="13" FontWeight="Bold"/>
<TextBlock Text=" " FontSize="13" FontWeight="Bold"/>
<TextBlock Text="{Binding Surname}" FontSize="13" FontWeight="Bold"/>
</StackPanel>
<StackPanel Orientation="vertical" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,70,0" Width="130">
<TextBlock Text="{Binding Address1}" FontSize="8" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Address2}" FontSize="8" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Address3}" FontSize="8" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding City}" FontSize="8" HorizontalAlignment="Center"/>
</StackPanel>
<Button Template="{StaticResource LinkButton}" Command="{Binding EditCommand}" Content="Details" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,3,5,0" />
<Button Template="{StaticResource LinkButton}" Command="{Binding DeleteCommand}" Content="Delete" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,5,3" />
</Grid>
</UserControl>
And the control inside Visual Studio designer looks like:
and as we can see design data which are bound to the different controls and are displayed in the designer making styling the control very easy. The design data for the view is set using
d:DataContext="{d:DesignInstance Type=dd:DPersonViewModel, IsDesignTimeCreatable=True}" at the beginning.
Next, we design the view to display person's details. Again we will use the same design data to style our view. After styling, the XAML for the view looks like:
<UserControl x:Class="AddressBook.Views.PersonDetailsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:designData="clr-namespace:AddressBook.DesignData"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=designData:DPersonViewModel, IsDesignTimeCreatable=True }"
Height="260" Width="330">
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Name :" Grid.Column="0" Grid.Row="0" VerticalAlignment="Center" />
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="0" VerticalAlignment="Center" />
<TextBlock Text="Surname :" Grid.Column="0" Grid.Row="1" VerticalAlignment="Center" />
<TextBox Text="{Binding Surname, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="1" VerticalAlignment="Center" />
<TextBlock Text="Address1 :" Grid.Column="0" Grid.Row="2" VerticalAlignment="Center" />
<TextBox Text="{Binding Address1, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="2" VerticalAlignment="Center" />
<TextBlock Text="Address2 :" Grid.Column="0" Grid.Row="3" VerticalAlignment="Center" />
<TextBox Text="{Binding Address2, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="3" VerticalAlignment="Center" />
<TextBlock Text="Address3 :" Grid.Column="0" Grid.Row="4" VerticalAlignment="Center" />
<TextBox Text="{Binding Address3, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="4" VerticalAlignment="Center" />
<TextBlock Text="City :" Grid.Column="0" Grid.Row="5" VerticalAlignment="Center" />
<TextBox Text="{Binding City, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="5" VerticalAlignment="Center" />
<TextBlock Text="Age :" Grid.Column="0" Grid.Row="6" VerticalAlignment="Center" />
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="6" VerticalAlignment="Center" />
<Button Content="Save" Command="{Binding SaveCommand}" Grid.Column="1" Grid.Row="7" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="80" Margin="0,0,95,0" />
<Button Content="Cancel" Command="{Binding CloseCommand}" Grid.Column="1" Grid.Row="7" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="80" Margin="0,0,5,0" />
</Grid>
</UserControl>
And the control on the designer will look like:
The next step is to define the data model for the person. The model is used to retrieve the data from the repository (database) and send the data to repository. The data model will look like :
public class Person
{
public string Name { get; set; }
public string Surname { get; set; }
public int Age { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Address3 { get; set; }
public string City { get; set; }
}
Now, before we dive in and implement the person viewmodel, lets first describe the tasks that this viewmodel will handle. First, the viewmodel will take a data model and populate the properties with the model values. Then, when user clicks on
Details button on the summary view (
PersonSummeryView) the viewmodel should bring up a dialog with the details so the user can view/edit the details. If the user changes details on the details dialog and clicks on
Save button, then the model should be updated and the model should be saved into a repository. Otherwise if the user clicks on
Cancel button then all the changes will be reverted and the dialog will be closed.
Next, when user clicks on
Delete button on summary view the item should be deleted from the repository and the item should be removed from the list. Now, because the viewmodel does not have any knowledge about the container (in our case the list), it does not know how to delete itself an remove itself from the list. So instead of passing the parent viewmodel to it and tightly coupling them together, the viewmodel will publish a message to
MessageBus and whoever listens for the message has the knowledge necessary to delete the viewmodel and remove it from the list.
So the implementation of the viewmodel looks like:
public class PersonViewModel : ViewModelBase, IPersonViewModel, ICloseable
{
private Person _model;
private IPropertySubject<string> _name;
private IPropertySubject<string> _surname;
private IPropertySubject<int> _age;
private IPropertySubject<string> _address1;
private IPropertySubject<string> _address2;
private IPropertySubject<string> _address3;
private IPropertySubject<string> _city;
private ICommandObserver<Unit> _editCommand;
private ICommandObserver<Unit> _saveCommand;
private ICommandObserver<Unit> _deleteCommand;
private ICommandObserver<Unit> _closeCommand;
private readonly ISubject<bool> _closeSubject = new Subject<bool>();
public PersonViewModel(IPropertyProviderFactory propertyProviderFactory, IMessageBus messageBus, IDialogService dialogService, Person model)
{
_model = model;
var pp = propertyProviderFactory.Create<IPersonViewModel>(this);
// creating properties
_name = pp.CreateProperty(i => i.Name, model.Name);
_surname = pp.CreateProperty(i => i.Surname, model.Surname);
_age = pp.CreateProperty(i => i.Age, model.Age);
_address1 = pp.CreateProperty(i => i.Address1, model.Address1);
_address2 = pp.CreateProperty(i => i.Address2, model.Address2);
_address3 = pp.CreateProperty(i => i.Address3, model.Address3);
_city = pp.CreateProperty(i => i.City, model.City);
// creating commands
_editCommand = pp.CreateCommand<Unit>(i => i.EditCommand);
_saveCommand = pp.CreateCommand<Unit>(i => i.SaveCommand, !string.IsNullOrEmpty(model.Name) && !string.IsNullOrEmpty(model.Surname));
_deleteCommand = pp.CreateCommand<Unit>(i => i.DeleteCommand);
_closeCommand = pp.CreateCommand<Unit>(i => i.CloseCommand);
// Observing commands
ShouldDispose(_name.CombineLatest(_surname, (n,s) => !string.IsNullOrEmpty(n) && !string.IsNullOrEmpty(s)).Subscribe(_saveCommand.SetCanExecute));
ShouldDispose(_saveCommand.OnExecute.Subscribe(_ =>
{
UpdateModel();
_closeSubject.OnNext(true);
}));
ShouldDispose(_editCommand.OnExecute.Subscribe(_ => dialogService.ShowViewModel("Edit Person", this)));
ShouldDispose(_deleteCommand.OnExecute.Subscribe(_ => messageBus.Publish(new DeletePersonMessage(this))));
ShouldDispose(_closeCommand.OnExecute.Subscribe(_ =>
{
ResetData();
_closeSubject.OnNext(false);
}));
}
public string Name
{
get { return _name.Value; }
set { _name.Value = value; }
}
public string Surname
{
get { return _surname.Value; }
set { _surname.Value = value; }
}
public int Age
{
get { return _age.Value; }
set { _age.Value = value; }
}
public string Address1
{
get { return _address1.Value; }
set { _address1.Value = value; }
}
public string Address2
{
get { return _address2.Value; }
set { _address2.Value = value; }
}
public string Address3
{
get { return _address3.Value; }
set { _address3.Value = value; }
}
public string City
{
get { return _city.Value; }
set { _city.Value = value; }
}
public ICommand EditCommand
{
get { return _editCommand; }
}
public ICommand SaveCommand
{
get { return _saveCommand; }
}
public ICommand DeleteCommand
{
get { return _deleteCommand; }
}
public ICommand CloseCommand
{
get { return _closeCommand; }
}
private void UpdateModel()
{
_model = this.ToModel();
}
private void ResetData()
{
Name = _model.Name;
Surname = _model.Surname;
Surname = _model.Surname;
Age = _model.Age;
Address1 = _model.Address1;
Address2 = _model.Address2;
Address3 = _model.Address3;
City = _model.City;
}
IObservable<bool> ICloseable.Close
{
get { return _closeSubject.AsObservable(); }
}
}
As we can see, everything happens in a constructor, so if we start disecting the constructor we can see that initially we create the properties and we set the initial values from the data model:
// creating properties
_name = pp.CreateProperty(i => i.Name, model.Name);
_surname = pp.CreateProperty(i => i.Surname, model.Surname);
_age = pp.CreateProperty(i => i.Age, model.Age);
_address1 = pp.CreateProperty(i => i.Address1, model.Address1);
_address2 = pp.CreateProperty(i => i.Address2, model.Address2);
_address3 = pp.CreateProperty(i => i.Address3, model.Address3);
_city = pp.CreateProperty(i => i.City, model.City);
then we create the commands :
// creating commands
_editCommand = pp.CreateCommand<Unit>(i => i.EditCommand);
_saveCommand = pp.CreateCommand<Unit>(i => i.SaveCommand, !string.IsNullOrEmpty(model.Name) && !string.IsNullOrEmpty(model.Surname));
_deleteCommand = pp.CreateCommand<Unit>(i => i.DeleteCommand);
_closeCommand = pp.CreateCommand<Unit>(i => i.CloseCommand);
As we can see, save command initially will be enabled if the
Name and
Surname properties on the datamodel are not null or empty.
ShouldDispose(_name.CombineLatest(_surname, (n,s) => !string.IsNullOrEmpty(n) && !string.IsNullOrEmpty(s)).Subscribe(_saveCommand.SetCanExecute));
In the line above we combine
_name and
_surname observables using
CombineLatest() method, and then we subscribe to the result by providing save command's
SetCanExecute property (which is an observer). So anytime a new value is pushed on either name or surname observable, the provided function will get the values of both observables and will return another value (in our case it will produce a boolean value depending if the values of the name and surname are not null or empty). Then the result produced by the function will be pushed into an observable, which in turn will be pushed ino
SetCanExecute thus enabling/disabling the control depending on the result (Save button).
Next, on the following piece of code :
ShouldDispose(_saveCommand.OnExecute.Subscribe(_ =>
{
UpdateModel();
_closeSubject.OnNext(true);
}));
we subscribe to
_saveCommand.OnExecute and anytime the
SaveCommand command is executed (by clicking on
Save button on details view in our case) we update the data model and push a value to Close observable thus closing the dialog.
On the next line :
ShouldDispose(_editCommand.OnExecute.Subscribe(_ => dialogService.ShowViewModel("Edit Person", this)));
we subscribe to
_editCommand.OnExecute and whenever the edit command is executed (by clicking
Detailsbutton on summary view) then we, instead of creating the dialog ourselves in the viewmodel, we delegate it to the dialog service (by calling
ShowViewModel() method). In this way, as we said before when we write unit tests we can fake the dialog service and we are also adhering to SRP (Single Responsibility Principle) principle in our viewmodel.
then on the next line :
ShouldDispose(_deleteCommand.OnExecute.Subscribe(_ => messageBus.Publish(new DeletePersonMessage(this))));
we are subscribing to the delete command and whenever it gets executed (by clicking on
Delete button on the summary view) then we will publish a new
DeleteMessage on the message bus. So whoever is listening to this message can react on it.
And lastly, on the next block:
ShouldDispose(_closeCommand.OnExecute.Subscribe(_ =>
{
ResetData();
_closeSubject.OnNext(false);
}));
we are subscribing to
Close command, so whenever the user clicks on
Close button on the details view, then we are reverting property values and closing the dialog (by pushing a value to
Close observable defined in
ICloseable interface and implemented in this viewmodel).
The next step is to define and implement dialog service. The definition of the dialog service looks like :
public interface IDialogService
{
void ShowInfo(string title, string message);
MessageBoxResult ShowWarning(string title, string message);
void ShowError(string title, string message);
bool ShowViewModel(string title, ViewModelBase viewModel);
}
And the implementation looks like :
public class DialogService : IDialogService
{
public void ShowInfo(string title, string message)
{
MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Information);
}
public MessageBoxResult ShowWarning(string title, string message)
{
return MessageBox.Show(message, title, MessageBoxButton.OKCancel, MessageBoxImage.Warning);
}
public void ShowError(string title, string message)
{
MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Error);
}
public bool ShowViewModel(string title, ViewModelBase viewModel)
{
var win = new DialogWindow(viewModel) {Title = title};
win.ShowDialog();
return win.CloseResult;
}
}
Now we need to define and implement the repository that will hold person objects. The definition of the repository looks like:
public interface IPersonRepository
{
IObservable<IEnumerable<Person>> GetAllClients();
IObservable<bool> Save(Person person);
IObservable<bool> Delete(Person person);
}
As we can see from the definition for the interface, all the methods are declared as observables. This allows us to subscribe to the method and react when we get the data making the methods asynchronous.
For the purpose of this application we will only hold the data on the list in memory, but for a real application we would implement this interface so the data would be stored in a database.
The implementation of the repository interface looks like:
public class PersonRepository : IPersonRepository
{
private readonly List<Person> _clients;
public PersonRepository()
{
_clients = new List<Person>
{
new Person { Name = "Joe", Surname = "Blogs", Address1 = "Flat 2", Address2 = "19 Fleet Street", Address3 = "City", City = "London", Age = 21 },
new Person { Name = "Mary", Surname = "Jones", Address1 = "Flat 21", Address2 = "Rotherhithe Street", Address3 = "Calder Court", City = "New York", Age = 28 }
};
}
public IObservable<IEnumerable<Person>> GetAllClients()
{
return ToObservable(() =>
{
IEnumerable<Person> cl = _clients;
Thread.Sleep(1500);
return cl;
});
}
public IObservable<bool> Save(Person person)
{
return ToObservable(() =>
{
_clients.Add(person);
Thread.Sleep(100);
return true;
});
}
public IObservable<bool> Delete(Person person)
{
return ToObservable(() =>
{
var cl = _clients.FirstOrDefault(c => c.Name == person.Name && c.Surname == person.Surname);
if (cl != null)
{
_clients.Remove(person);
Thread.Sleep(800);
return true;
}
return false;
});
}
private IObservable<T> ToObservable<T>(Func<T> func)
{
return func.ToAsync()();
}
}
As we can see from the implementation of the private method
ToObservable<>(), RX has another cool feature that can convert a function to an async functions that returns an observable. To mimic a long running process we also have added a
Thread.Sleep for 1 second when we call
GetAllPersons() and
Save() methods.
The next step is to define main viewmodel. The main viewmodel will be responsible for getting all persons from the repository and displaying them in a list view, adding a new person, and deleting the appropriate person from the list. So the definition looks like :
public interface IMainViewModel
{
ObservableCollection<IPersonViewModel> Clients { get; }
ICommand AddNewClientCommand { get; }
bool IsBusy { get; }
}
And the implementation looks like :
public class MainViewModel : ViewModelBase, IMainViewModel
{
private readonly ICommandObserver<Unit> _addNewClientCommand;
private readonly IPropertySubject<bool> _isBusy;
public MainViewModel(IPropertyProviderFactory propertyProviderFactory, IMessageBus messageBus, IDialogService dialogService, IPersonRepository personRepository, IPersonViewModelFactory personViewModelFactory)
{
var pp = propertyProviderFactory.Create<IMainViewModel>(this);
//create properies
_isBusy = pp.CreateProperty(i => i.IsBusy, false);
// create commands
_addNewClientCommand = pp.CreateCommand<Unit>(i => i.AddNewClientCommand);
Clients = new ObservableCollection<IPersonViewModel>();
IsBusy = true;
ShouldDispose(personRepository.GetAllClients().ObserveOnDispatcher().Subscribe(c =>
{
foreach (var item in c.Select(personViewModelFactory.Create))
{
Clients.Add(item);
}
IsBusy = false;
}));
ShouldDispose(messageBus.Subscribe<DeletePersonMessage>(m =>
{
var msg = string.Format("Are you sure you want to delete {0} {1}?", m.Person.Name, m.Person.Surname);
if (dialogService.ShowWarning("Delte", msg) == MessageBoxResult.OK)
{
IsBusy = true;
ShouldDispose(personRepository.Delete(m.Person.ToModel())
.ObserveOnDispatcher()
.Subscribe(_ =>
{
Clients.Remove(m.Person);
IsBusy = false;
}));
}
}));
ShouldDispose(_addNewClientCommand.OnExecute.Subscribe(_ =>
{
var newClient = personViewModelFactory.Create(new Person());
if (dialogService.ShowViewModel("New Person", newClient as ViewModelBase))
{
IsBusy = true;
ShouldDispose(personRepository.Save(newClient.ToModel())
.ObserveOnDispatcher()
.Subscribe(u =>
{
Clients.Add(newClient);
IsBusy = false;
}));
}
}));
}
public ObservableCollection<IPersonViewModel> Clients { get; private set; }
public ICommand AddNewClientCommand
{
get { return _addNewClientCommand; }
}
public bool IsBusy
{
get { return _isBusy.Value; }
set { _isBusy.Value = value; }
}
}
So if we dissect the constructor again, we will see that at the beginning we create properties:
//create properies
_isBusy = pp.CreateProperty(i => i.IsBusy, false);
// create commands
_addNewClientCommand = pp.CreateCommand<Unit>(i => i.AddNewClientCommand);
Clients = new ObservableCollection<IPersonViewModel>();
On next block of code we retrieve all persons from the repository :
ShouldDispose(personRepository.GetAllClients().ObserveOnDispatcher().Subscribe(c =>
{
foreach (var item in c.Select(personViewModelFactory.Create))
{
Clients.Add(item);
}
IsBusy = false;
}));
Then, on the next block we subscribe to
DeleteMessage on message bus, and whenever delete message is published on the message bus, we react to it by deleting the selected person from the list:
ShouldDispose(messageBus.Subscribe
(m =>
{
var msg = string.Format("Are you sure you want to delete {0} {1}?", m.Person.Name, m.Person.Surname);
if (dialogService.ShowWarning("Delte", msg) == MessageBoxResult.OK)
{
IsBusy = true;
ShouldDispose(personRepository.Delete(m.Person.ToModel())
.ObserveOnDispatcher()
.Subscribe(_ =>
{
Clients.Remove(m.Person);
IsBusy = false;
}));
}
}));
And lastly, we subscribe to _addNewClient command, so whenever the user clicks on the button, we display the dialog for the user to enter the details, and once the details have been entered and the user clicks on Save button, then the new person is stored in the repository:
ShouldDispose(_addNewClientCommand.OnExecute.Subscribe(_ =>
{
var newClient = personViewModelFactory.Create(new Person());
if (dialogService.ShowViewModel("New Person", newClient as ViewModelBase))
{
IsBusy = true;
ShouldDispose(personRepository.Save(newClient.ToModel())
.ObserveOnDispatcher()
.Subscribe(u =>
{
Clients.Add(newClient);
IsBusy = false;
}));
}
}));
The last thing left to do in our app is to register the types with the IoC container, and set the
DataContext of the main window. We do this in code-behind in
App.xaml. Our code-behind code looks like:
public partial class App : Application
{
private IContainer _container;
private readonly ContainerBuilder _containerBuilder;
public App()
{
_containerBuilder = new ContainerBuilder();
// Register MVVM dependencies
(new RX_MVVM.Bootstrapper()).Register(_containerBuilder);
// Register Viewmodels
_containerBuilder.RegisterType<MainViewModel>().As<IMainViewModel>().SingleInstance();
_containerBuilder.RegisterType<PersonViewModelFactory>().As<IPersonViewModelFactory>().SingleInstance();
_containerBuilder.RegisterType<DialogService>().As<IDialogService>().SingleInstance();
_containerBuilder.RegisterType<PersonRepository>().As<IPersonRepository>().SingleInstance();
}
protected override void OnStartup(StartupEventArgs e)
{
try
{
ShutdownMode = ShutdownMode.OnMainWindowClose;
_container = _containerBuilder.Build();
var mainWidow = new MainWindow {DataContext = _container.Resolve<IMainViewModel>()};
MainWindow = mainWidow;
mainWidow.Show();
base.OnStartup(e);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
The application that was build as an example is very simple but it give you a glimpse of the features of RX. RX is a very powerful framework which can simplify a lot of things in the programming world especially asynchronous programming.