Introduction
The Model-View-ViewModel pattern is really nice since it clearly defines the responsibility of the View, the ViewModel and the Model. There should be no direct contact between the View and the ViewModel and via the databinding mechanism, this is easily possible.
However, I ran into a problem when a confirmation was required from the user before removing an entity chosen by the user. The command responsible for the removal behavior was triggered from within the View and executed from within the ViewModel. In my opinion, the ViewModel should not directly use any presentation functionality (e.g. showing a MessageBox
).
In my quest to find a reasonable solution on the web, I ran into the following solutions:
- Just show the messagebox from the ViewModel.
- Use a solution that adds extra code to the View which reacts to a trigger from the ViewModel
. - Bind the View to a message property and have it show a messagebox when this changes.
- Use one of the available MVVM frameworks and use the solution they provide.
Solution 1 & 2 where directly discarded, solution 3 was getting close however I found it quite limiting to react to a change to a message. Solution 4 was not an option for me since I am not really fond of the MVVM frameworks I reviewed.
Because I was unable to find a solution matching my requirements, I came up with the solution mentioned in this article.
The Solution
Using the Behavior class located in the System.Windows.Interactivity
assembly, it is possible to hook in between the View and ViewModel without requiring direct contact between the View and ViewModel.
Extending this Behavior class with functionality that will register the behavior instance centrally based on a provided identifier will ensure that both the View and ViewModel can use it without any direct reference.
This resulted in the following design:
Using the Code
InteractionBehaviorCollection
To be able to register the behaviors centrally, the singleton InteractionBehaviorCollection is used. Based on the identifier of the behavior, it will store the instance of the behavior so that it can be retrieved when required.
InteractionBehaviorBase
To extend the Behavior class, a derived class is created, this is called InteractionBehaviorBase. When the interaction behavior is attached (used in a View), it will register itself with the
InteractionBehaviorCollection. When the interaction behavior is detached, it will also unregister itself again.
MessageBoxInteractionBehavior
This class contains the required functionality to show a regular MessageBox when required. When a custom dialog is required, another specific InteractionBehavior can be created which will be responsible for showing the custom dialog when required by a ViewModel.
This class provides 3 properties which enables us to customize the message box. This includes the Caption, the Message and the possible buttons as they will be shown in the message box.
There is one method, the method Execute which will show the message box and which passes the result onto the custom code for processing. This processing will be done at the ViewModel so that the logic can react on the client action.
public override void Execute(
Action<Object> interactionFinished,
params object[] args)
{
if ((interactionFinished != null) &&
!string.IsNullOrEmpty(Caption) &&
!string.IsNullOrEmpty(Message))
{
string caption = string.Format(Caption, args);
string message = string.Format(Message, args);
interactionFinished(MessageBox.Show(message, caption, Buttons,
MessageBoxImage.Information));
}
}
The View, MainWindow.xaml
In the XAML of the view, the behavior is defined, this also includes the properties. It is possible to include formatting characters so that custom information can be passed when the behavior is triggered from the
ViewModel. The identifier is required so that the ViewModel can identify this specific behavior.
<i:Interaction.Behaviors>
<local:MessageBoxInteractionBehavior
Caption="This contains the caption"
Message="This contains the message, parameters can also be added (For example the current date/time: {0})"
Buttons="YesNo"
Identifier="ThisIdentifiesThisSpecificCombination"/>
</i:Interaction.Behaviors>
ViewModelBase
This is the base for the specific view model as it is used in the attached example. It contains the method
ExecuteInteraction which allows to trigger the interaction behavior with the corresponding identifier.
IInteractionBehavior interactionBehavior =
InteractionBehaviorCollection.Instance.Get(identifier);
if (interactionBehavior != null)
{
try
{
interactionBehavior.Execute(interactionFinished, args);
result = true;
}
catch (Exception)
{
}
}
TestViewModel
The view model used for this test application. It provides a command to trigger the interaction behavior, it is used via the property TestInteractionBehaviorCommand. The actual triggering of the message box from within the view model is located in the method TestInteractionBehavior.
private void TestInteractionBehavior()
{
ExecuteInteraction("ThisIdentifiesThisSpecificCombination", (result) =>
{
MessageBoxResult messageBoxResult = (MessageBoxResult)result;
if (messageBoxResult == System.Windows.MessageBoxResult.Yes)
{
}
}, DateTime.Now);
}
Calling ExecuteInteraction will trigger the interaction behavior which matches the identifier "ThisIdentifiesThisSpecificCombination". The result of the message box is received and can be used to handle the required client action. It is possible to pass parameters so that messages can be customized, e.g., display the name of the entity that is being removed. For this example, I stick with the current date/time.
The following message box is shown when
ExecuteInteraction is called.
Points of Interest
This example uses the default message box to interact with the user. Based on the information in this example, it should be no problem to exchange this with a custom view which better suits your needs.
History
-
9 Jan 2014: First version