Warning : This page has been marked as an archive because the author consider that its content is no longer relevant.
Introduction
One of the recurring aspects about MVVM programming is the use of messages. A message can be considered as a kind of an application wide event and therefore accessible from any part of it.
The idea with this pattern is to have a component which is referenced by any part of the application that wants to listen or to send messages. This component (which has given its name to the pattern) is commonly know as the mediator and allows us to create an architecture in which senders and listeners do not know each others in order to obtain a better modularity.
The nRoute framework includes a messenging framework that can send messages both synchronously and asynchronously. This part of nRoute is based on the Rx framework for its implementation. Those who have already used Rx will feel confortable.
One common problem when following the MVVM pattern is to open a ChildWindow from a view model and to get its results when it’s closed in the view model.
We will now see how to do this by using messages.
The context
We build an application that displays a list of contacts which we will be able to edit when clicking on a button. This button will open a ChildWindow displaying the fields to edit the contact. It will also be possible to validate or to cancel the changes.
Here are the screenshots of what is expected :
The view
Our main control is Home.xaml. This is the one showed on figure 1. The Xaml code of this control contains a ListBox templated to display the contacts and a Button bound to a command that opens the contact’s edition ChildWindow. Here’s the Xaml of this control :
<UserControl x:Class="nRoute_MVVM_ChildWindow.Views.Home"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:n="http://nRoute/schemas/2010/xaml"
mc:Ignorable="d">
<i:Interaction.Behaviors>
<n:BridgeViewModelBehavior />
</i:Interaction.Behaviors>
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="350" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="lstBox" ItemsSource="{Binding Contacts}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button
Grid.Row="1"
Content="Edit"
Command="{Binding EditContactCommand}"
CommandParameter="{Binding Path=SelectedItem, ElementName=lstBox}" />
</Grid>
</UserControl>
And its code-behind :
[nRoute.ViewModels.MapView(typeof(ViewModels.HomeViewModel))]
public partial class Home
{
public Home()
{
InitializeComponent();
}
}
The view-models
You can see here that thanks to the MapView attribute, the Home control is linked to the HomeViewModel. But before defining this view-model let’s fist define the model representing the contacts:
public class ContactViewModel : ViewModelBase, IEditableObject
{
private string _firstName;
private string _lastName;
private ContactViewModel _originalValue;
public long Id { get; set; }
public string FirstName
{
get
{
return _firstName;
}
set
{
if (_firstName != value)
{
_firstName = value;
NotifyPropertyChanged(() => FirstName);
}
}
}
public string LastName
{
get
{
return _lastName;
}
set
{
if (_lastName != value)
{
_lastName = value;
NotifyPropertyChanged(() => LastName);
}
}
}
public void BeginEdit()
{
_originalValue = new ContactViewModel
{
FirstName = FirstName,
LastName = LastName
};
}
public void CancelEdit()
{
if (_originalValue != null)
{
LastName = _originalValue.LastName;
FirstName = _originalValue.FirstName;
_originalValue = null;
}
}
public void EndEdit()
{
_originalValue = null;
}
}
The particularity of this view-model is that it implements the IEditableObject interface which you can find in the System.ComponentModel namespace. This interface defines three methods we’ll use to create the contact’s edition form of the ChildWindow :
- BeginEdit (sets the object in a edit state)
- CancelEdit (cancels all modifications and stops the edition)
- EndEdit (validates all modifications and stops the edition)
And now here’s the HomeViewModel :
public class HomeViewModel : ViewModelBase
{
private readonly ICommand _editCommand;
private readonly ObservableCollection<ContactViewModel> _contacts;
public HomeViewModel()
{
_contacts = new ObservableCollection<ContactViewModel>();
_editCommand = new ActionCommand<ContactViewModel>(BeginContactEdition);
for (int i = 0; i < 10; i++)
_contacts.Add(new ContactViewModel
{
Id = i,
FirstName = "FirstName" + i,
LastName = "LastName" + i
});
}
public IEnumerable<ContactViewModel> Contacts
{
get
{
return _contacts;
}
}
public ICommand EditContactCommand
{
get
{
return _editCommand;
}
}
private void BeginContactEdition(ContactViewModel contact)
{
}
}
In its current state, the view model generates a list of 10 elements and defines the EditContactCommand command that is called whenever the Edit button is clicked. Therefore, it is in the BeginContactEdition method that we want to open the ChildWindow passing it the current contact.
The ChildWindow
We will now add a ChildWindow to the project using Visual Studio’s template and modify it to make the Xaml be like this :
<controls:ChildWindow x:Class="nRoute_MVVM_ChildWindow.ChildWindows.ContactChildWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
Width="400" Height="300"
Title="ContactChildWindow">
<Grid x:Name="LayoutRoot" Margin="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="First name : " />
<TextBox Width="100" Text="{Binding FirstName, Mode=TwoWay}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Last name : " />
<TextBox Width="100" Text="{Binding LastName, Mode=TwoWay}" />
</StackPanel>
</StackPanel>
<Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
<Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
</Grid>
</controls:ChildWindow>
Creation of messages
The mediator pattern induces three things :
- We need a message to send
- We need something to send the message
- We need at least one control that listens to the message
The control sending the message must provide enough information inside the message in order to make it useful for the controls that listens to it.
In our application we will have two messages, one to trigger the contact’s edition and another one to inform that the contact’s edition is finished.
public class BeginContactEditionMessage
{
public ContactViewModel Contact { get; set; }
}
public class EndContactEditionMessage
{
public ContactViewModel Contact { get; set; }
public bool Success { get; set; }
}
Sending and receiving messages
Now that we have messages we need to send them. And as everyone should be expecting, we will start by the one that triggers the contact’s edition.
Let’s go back to the HomeViewModel class and add the following field :
private readonly IChannel<BeginContactEditionMessage> _beginContactEditionChannel;
We will initialize it inside the constructor with the default communication channel associated with the BeginContactEditionMessage message (private channels also exists but it is out of the scope of this article) :
_beginContactEditionChannel = Channel<BeginContactEditionMessage>.Public;
We now need to modify BeginContactEdition with the following code :
private void BeginContactEdition(ContactViewModel contact)
{
var message = new BeginContactEditionMessage
{
Contact = contact
};
contact.BeginEdit();
_beginContactEditionChannel.OnNext(message);
}
The OnNext method sends a message to all listeners (those who have already used Rx will feel confortable here). The message is now being sent but nothing listens to it.
The one that should listen to it is of course the ChildWindow. Let’s modify its code-behind a little to make it react to the messages sent by the view-model.
We will add two line to it’s constructor :
var channel = Channel<BeginContactEditionMessage>.Public;
_beginContactEditionChannelDisposable = channel.Subscribe(BeginContactEdition);
As in the view model, we get the public instance of the communication channel but this time we will not call OnNext (which stands for Send) but Subscribe (which stands for Listen). The Subscribe method takes an Action
For the moment, we will save this value in a field :
private readonly IDisposable _beginContactEditionChannelDisposable;
Then we will implement the BeginContactEdition method :
private void BeginContactEdition(BeginContactEditionMessage m)
{
DataContext = m.Contact;
Show();
}
In order to make things correctly we’ll make the ChildWindow implement IDisposable :
public void Dispose()
{
_beginContactEditionChannelDisposable.Dispose();
}
And that’s it ! Now, when we select a contact in the ListBox and then click on the button the ChildWindow opens with the selected contact in edition mode. But the view model is never notified when the contact’s edition is finished.
We will do the same thing for the EndContactEdition message that what we did the BeginContactEdition but reversing the roles of sender and listener.
In the ChildWindow’s code-behind we will replace the event handlers of the buttons OK and Cancel by the following code :
private void OKButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
PublishResult();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
PublishResult();
}
private void PublishResult()
{
var contact = (ContactViewModel)DataContext;
var channel = Channel<EndContactEditionMessage>.Public;
var message = new EndContactEditionMessage
{
Contact = contact,
Success = DialogResult.HasValue && DialogResult.Value
};
channel.OnNext(message);
}
Let’s go back to the view model and follow the same method for the EndContactEditionMessage message that the one use in the ChildWindow’s code-behind to receive the BeginContactEditionMessage.
Creation of the IDisposable field :
private readonly IDisposable _endContactEditionChannelDisposable;
Modification of the view model’s constructor :
_endContactEditionChannel = Channel<EndContactEditionMessage>.Public;
_endContactEditionChannelDisposable = _endContactEditionChannel.Subscribe(
m =>
{
if (m.Success)
m.Contact.EndEdit();
else
m.Contact.CancelEdit();
});
Make the view model implement the IDisposable interface :
public void Dispose()
{
_endContactEditionChannelDisposable.Dispose();
}
I hope that you’ll find this article useful.
As usual, you can download the sources of the articles on my skydrive.
Comments