Click here to Skip to main content
15,890,438 members
Articles / Desktop Programming / WPF

Yet Another Way for ViewModel - ViewModel Communications in MVVM - Part 1 of 2

Rate me:
Please Sign up or sign in to vote.
4.33/5 (2 votes)
11 Aug 2015CPOL14 min read 26.1K   625   6   4
Techniques for MultiBinding based ViewModel - ViewModel communications in the MVVM pattern.

Table of contents

Introduction

The article tries to fill up a well-known gap in the Model-View-ViewModel pattern - ViewModel-ViewModel communication. That is, more precisely, data exchange between different ViewModels. Solution for the problem is presented and implemented by means of Binding (more accurately - by means of MultiBinding). This is a most natural and standard way for MVVM because Binding underlies a better part of data exchange in MVVM: Binding is a lingua franca of MVVM. The solution is implemented in a transparent manner - only common and standard features of WPF are used.

Background

Pattern implementation for GUI design became especially successful with the beginning of .net technology. In the pre-WPF epoch Model-View-Controller and Model-View-Presenter patterns may be mentioned; in our WPF age a top ranking deservedly belongs to the Model-View-ViewModel pattern [1].

All these pre-WPF and WPF design patterns have a separate Presentation (GUI) part and a separate Control part that manages the GUI. Controller from MVC pattern, Presenter from MVP pattern, ViewModel from MVVM pattern may be reckon among such Control parts.

In the pre-WPF epoch most GUI management actions performed by Control parts (aka Control modules) of the patterns, were based on a relatively low-level architecture of events (or weak events). It was easy to organize communications between different Control modules in the same low-level way. By means of events different Control modules could exchange data and could answer properly on any other event firing. Very powerful hierarchical and network graph structures have been organized by the means of such Control-Control communications.

On the other hand, MVVM pattern is based on a data binding - high-level GUI management actions; a weak event engine is hidden in the depth of data binding. However, data binding cannot be implemented for communications between ViewModels because at least one co-participant of data binding should be an inheritor of DependencyObject class - and no ViewModel inherits from DependencyObject by definition. Several ideas were suggested for fill up this gap of MVVM design. Let us consider some of them.

  • Simple event-based communication (for example, [2]). One of ViewModels declares a static event and fires it on some conditions, for example when some property changes. Another ViewModel subscribes to this event. This approach has obvious disadvantages: any communication should be based on its own event and ViewModel will be overfilled with event handlers; one ViewModel "should know" name of another one and should have access to its property (though it is static) - design becomes closely coupled.
     
  • Mediator based communication. This approach was firstly implemented by Marlon Grech [3] for adaptation of MVC pattern for WPF. His brilliant work was later expanded for MVVM pattern by Sasha Barber [4]. After that the method was widely recognized, developed and implemented. This method again is based on an event engine (sometimes - weak event engine). It fully avoids close coupling by use of the Mediator feature - no ViewModel knows anything about the other ones. Even so, any ViewModel that tries to broadcast to other ones should register for the Mediator somehow - this adds additional code to ViewModel and slightly obscures true sense of ViewModels - View managing. Also and again this approach uses low-level communications in comparison with binding standard for MVVM.
     
  • Binding based communication. This approach is logically reasonable because it provides features homogeneity for all parts of the MVVM pattern: both View - ViewModel and ViewModel - ViewModel communications are based on the means of one and the same level of generality. One interesting implementation of such approach may be found in Tore Senneseth's [5] article. In the article Binding is maintained on the level of View - ViewModel communication, and after ViewModel's property changes, an intermediate DependencyObject enriched with Dependency Properties comes into play. One of its Dependency Properties transfers these changes by means of property changed callback to another Dependency Property which in turn is bound to the target ViewModel and so on. The idea of intermediate DependencyObject is great and it will be used (but in a different way) in the current article.

General requirements

  1. Communications between ViewModels should be uniform with other MVVM means, i.e. they should be based on Binding.
     
  2. Communication definitions should be declared in a XAML-based manner as a resource. These definitions should bring enough data to create communications on base of custom class or on base of existing class. This requirement is of high importance because it provides communications "calculation" on a preparatory stage of the project
     
  3. Such communications should be calculated, prepared and created as instances of the class on the preparatory stage of the program and should be activated and executed during program interaction with GUI as many times as needed.
     
  4. Activation of such communications may be carried out either during some property's changed value callback, or during some GUI interaction.
     
  5. Communication activation method should be defined in an abstract ViewModel's base class, should be suitable for any communication and should be called inside getters or setters of some ViewModel properties or inside ViewModel commands when needed. No other code intervention concerning to communications should be included into ViewModels. Any ViewModel should not inherit from any other class besides the abstract ViewModel base class.
     
  6. Communications as instances of a specified class should be stored in some repository for repeated use and to prevent the garbage collector from collecting them too early. Along with communications' instances some additional objects should (or may) be stored in such repository as well.

Implementation according to requirements

We will consider implementation of these requirements but in a slightly diffrent order.

XAML-based communication definition

A communication as data exchange is based on ViewModels and their properties. One should distinguish a Source ViewModel and a Source Property that will send data and a Target ViewModel and a Target Property that will receive data. Generally speaking, there may be more than one Target, but we will slightly touch this possibility in the end of the article only. 

Implementation is very straightforward:

C#
public class CommunicationDef
{
    public string SourceViewModel { get; set; }
    public string SourceProperty { get; set; }
    public string TargetViewModel { get; set; }
    public string TargetProperty { get; set; }
}
Sn. 1: communication definition - file CommunicationDef.cs
C#
using System.Windows.Markup;
namespace VMCommunications.Definitions
{
	[ContentProperty("CommunicationDefs")]
	public class CommunicationHolder
	{
		private List<CommunicationDef> _communicationsDef = new List<CommunicationDef>( );
		public List<CommunicationDef> CommunicationDefs
		{
			get { return _communicationDefs; }
			set { _communicationDefs.AddRange( value );
		}
	}
}
Sn. 2: collection of communication definitions - file CommunicationHolder.cs
XML
<def:CommunicationHolder x:key="vmCommunications">
	<def:CommunicationDef SourceViewModel="ViewModel1" 
	                      SourceProperty="Exchange11" 
	                      TargetViewModel="ViewModel2" 
	                      TargetProperty="Exchange21">
	...
</def:CommunicationHolder>
Sn. 3: communication definition sample in a ResourceDictionary - file CommunicationsSample.xaml

Requirement #2 is fulfilled.

Binding-based communication engine

Explanations

Data exchange between ViewModels is a purpose of Communication. Programmatically, it is BindingExpression that maintains data exchange in WPF.

Notwithstanding, BindingExpression has a strict constraint: its Target object should inherit from some DependencyObject. According to the requirement #5, no ViewModel should inherit from any other class except the abstract ViewModelBase which in turn may inherit from IViewModel (or directly from INotifyPropertyChanged) only. It seems there is no place for DepenedencyObject!

Instead, MultiBindingExpression comes into consideration. Any MultiBindingExpression has a single Target object which should inherit from some DependencyObject. Also any MultiBindingExpression is based on a collection of BindingExpressions, each BindingExpression in the collection being based on its own Source object.

Both of our ViewModels - Source and Target - are suitable to be Sources of these BindingExpressions. One ViewModel should send a data to a Target object of MultiBindingExpression and its BindingMode should be OneWay, and the other one should receive the data from the Target object of MultiBindingExpression in a OneWayToSource mode.
The Target object of the MultiBindingExpression may be defined as an artificial DependencyObject and should play a role of Splice between both ViewModels.

For not to be confused let's distinguish terms. In the level of Communication we will call a Source ViewModel a LogicalSource ViewModel, a Target ViewModel - a LogicalTarget ViewModel, and a Target of the MultiBindingExpression we will logically call LogicalSplice. Being considered as (deep) members of MultiBindingExpression, Source and Target ViewModels will be referred as PhysicalSources, and the Target of the MultiBindingExpression will be referred as PhysicalTarget.

Unfortunately, MultiBindingExpression is a sealed class and we are not able to derive the desired Communication class from it :( . So MultiBindingExpression will play the role of Communication as it is. Simplified scheme of Communication's parts is presented in the Figure #1.

Fig 1. Communication (MultiBindingExpression) construction
Strict definitions

I like extension style coding, so a significant part of code will be represented in this way.

C#
private static Binding CreateBinding( string path, BindingMode mode, string className )
{
    // BindingSetMode and BindingSetSourcce should be found in ExtensionsHelper
    return ( new Binding( path ) ).BindingSetMode( mode ).BindingSetSource( className );
}

private static Binding SourceViewModelBinding( CommunicationDef communication )
{
    // source of first binding, that is source of ViewModel communication
    return CreateBinding( communication.SourceProperty, BindingMode.OneWay, communication.SourceViewModel );
}

private static Binding TargetViewModelBinding( CommunicationDef communication )
{
    // source of second binding, that is target of ViewModel communication
    return CreateBinding( communication.TargetProperty, BindingMode.OneWayToSource, 
                          communication.TargetViewModel );
}

public static MultiBindingExpression CreateBindingExpression(CommunicationDef communication)
{
    // all extension methods should be found in ExtensionHelper
    return 
    BindingOperations.SetBinding(
	    CommunicationsSpliceRepository.Resolve( Tuple.Create( communication.SourceViewModel,
	                                        communication.SourceProperty ) ),
	     CommunicationsSplice.SpliceProperty,
		  ( new MultiBinding( ) ).
		   AddBinding( SourceViewModelBinding( communication ) ).
		    AddBinding( TargetViewModelBinding( communication ) ).
		     SetConverter( new CommunicationsConverter( ) ).
		      SetUpdateSourceTrigger( UpdateSourceTrigger.Explicit ).
		       SetBindingMode( BindingMode.TwoWay )
	) as MultiBindingExpression;
}
Sn. 4: creation of MultiBindingExpression for predefined CommunicationDef - file CommunicationsGenerator.cs:

BindingOperations.SetBinding takes three parameters to create MultiBindingExpression. First parameter is a PhysicalTarget; this is CommunicationsSplice object that we will discuss later; in snippet 4 it is resolved from its repository.

Second parameter is a Target DependencyProperty of the PhysicalTarget, i.e. CommunicationSplice.SpliceProperty.

Third parameter is a new MultiBinding object. Let's consider its creation.

On the first stage ( AddBindind( . . . )<font face="Courier New">MultiBinding</font> is equipped with the Bindings. By means of SourceViewModelBinding and TargetViewModelBinding methods these Bindings are constructed based on SourceProperty and TargetProperty names from CommunicationDef. After that the Bindings are provided with binding modes: <font face="Courier New">OneWay </font>for LogicalSource, and <font face="Courier New">OneWayToSource </font>- for LogicalTarget - and Binding.Source properties are evaluated with corresponding ViewModel objects. These ViewModel objects are resolved from the Repository by names from CommunicationDef ( Repository politics will be discussed later ).

On the second stage an important CommunicationConverter derived from IMultiValueConverter is set for the MultiBinding ( SetConverter( . . . ) ). Let us consider CommunicationConverter in details

C#
public object Convert( object[ ] value, Type targetType,
	object parameter, CultureInfo culture )
{
	return value[ 0 ];
}

public object[ ] ConvertBack( object value, Type[ ] targetType,
	object parameter, CultureInfo culture )
{
	return new object[ ] { Binding.DoNothing, value };
}
Sn. 5: Convert and ConvertBack methods of the CommunicationConverter - file CommunicationsConverter.cs

Convert method returns value from the first of the sources of MultiBinding - LogicalSource - for further sending to MultiBindingExpression's Target - PhysicalTarget. ConvertBack method takes the value from PhysicalTarget and "broadcasts" it among MultiBinding Sources. During ConvertBack first PhysicalSource ( it is a LogicalSource ) should receive nothing - and it receives Binding.DoNothing. At the same time second PhysicalSource (which is our LogicalTarget) should receive a sending value - from PhysicalTarget - and it receives the desired value. Convert will be activated by call to MultiBindingExpression.UpdateTarget() and ConvertBack - by call to MultiBindingExpression.UpdateSource().

On the third stage of MultiBinding construction, an UpdateSourceTrigger property is set to Explicit value. This will provide explicit activation of MultiBindingExpression from ViewModels.
Finally, a MultiBinding's BindingMode is set to TwoWay mode for all direction updates.

Requirements #1 and #3 are fulfilled.

Repository policy

Organization of some kind of repository for pre-created and reusable objects is an important feature of our approach.
MultiBindingExpressions (Communications) are created in the beginning of the program and are used many times for data exchange. They are stored in BindingExpressionsRepository and use Tuple<CommunicationDef.SourceViewModel, CommunicationDef.SourceProperty> as a key.

Views and ViewModels are pre created in this application also; they are stored in ViewRepository because they, in particular, participate many times in Communication process during program execution. Late creation of Views and ViewModels is possible too and will be discussed in the Part 2 of the article. ViewRepository uses CommunicationDef.SourceViewModel as a key.

Repository for CommunicationSplices is very important not only because the latter are used in Communications, but also because garbage collector considers them not to be accessed in future after several Communication activations, and "eats" them. Additional reference to CommunicationSplice being kept in the repository, saves it. CommunicationSpliceRepository uses
Tuple<CommunicationDef.SourceViewModel, CommunicationDef.SourceProperty> as a key

Repositories are organized in ConcurrentDictionaries (not for concurrency only, but because of some useful methods).
Repositories partially follow to Locator service paradigm and expose Register and Resolve methods. Special attention is given to clearing of repositories.
There is nothing particularly difficult in these methods and I refer to source code.

Requirement #6 is fulfilled.

Communication activation method

For MultiBinding, by setting its parameter UpdateSourceTrigger to Explicit, we are enforced to make an explicit call for TargetUpdate( ) and SourceUpdate( ) methods to activate MultiBindingExpression. The MultiBindingExpression is stored in the repository and ActivateVMCommunication method successfully performs this action.
Inside this method, a call to MultiBindingExpression's UpdateTarget( ) provides data transfer from LogicalSource to LogicalSplice. A following call to MultiBindingExpression's UpdateSource( ) provides data transfer from LogicalSplice to LogicalTarget - according to actions provided by CommunicationsConverter.

C#
protected void ActivateVMCommunication<T>( string sourceName, Expression<Func<T>> propertyExpression )
{
    Tuple<string, string> key = Tuple.Create( sourceName, 
                                              NameTypeHelper.NameInfer( propertyExpression ) );
    MultiBindingExpression mbe = BindingExpressionRepository.Resolve( key );
    if ( mbe != default( BindingExpressionBase ) ) 
    { 
        mbe.UpdateTarget( ); 
        mbe.UpdateSource( ); 
    }
}
Sn. 6: ActivateVMCommunication method - file ViewModelBase.cs; pay attention to BindingExpressionRepository key

Call for ActivateVMCommunication method may be performed either from the property setter (after PropertyChange event activation):

C#
public string Exchange11
{
    get { return _exchange1; }
    set
    {
        if ( value != _exchange1 )
        {
            _exchange1 = value;

            // communicate with communication target
            ActivateVMCommunication( GetType( ).Name, ( ) => Exchange11 );
        }
    }
}
Sn. 7: Call for ActivateVMCommunication method from property's setter - file ViewModel1.cs

or from a command execution method:

C#
CommunicateCommand = new RelayCommand( CommunicateAction, CanCommunicateAction );

...

private void CommunicateAction( object parameter )
{
	// according to communications definitions sends text back to ViewModel1
	ActivateVMCommunication( GetType( ).Name, ( ) => Exchange21 );
}
Sn. 8: Call for ActivateVMCommunication method from command execution - file ViewModel2.cs

Requirements #4 and #5 are fulfilled.

Example: VMCommunications project

The illustrative VMCommunications project is simple. It consists of two Views ( and of two ViewModels, corresponding to these Views ).

First task - typing data in View1 and simultaneous sending data from ViewModel1 to ViewModel2

Among other controls, View1 contains a TextBox:

XML
<TextBox TextAlignment="Left" TextWrapping="Wrap" Background="Ivory" ...>
    ...
	<TextBox.Text>
		<Binding Path="View1Text1" UpdateSourceTrigger="PropertyChanged"/>
	</TextBox.Text>
</TextBox>
Sn. 9: TextBox in View1 - file View1.cs

UpdateSourceTrigger parameter is set to PropertyChanged value for immediate mapping of the typed text to property View1Text1 of ViewModel1. Assignment of data to the property Exchange11 is made in the property View1Text1.

C#
public string View1Text1
{
    . . . .

    set
    {
        if ( value != _view1Text1 )
        {
            _view1Text1 = value;

            // send value to communication property
            Exchange11 = _view1Text1;
        }
    }
}
Sn. 10: Setter of property View1Text1 - file ViewModel1.cs

Property Exchange11 communicates with the ViewModel2.Exchange21 (as mentioned in snippet #3). Setter of Exchange11 (see snippet 7) stirs up the communication with every change of its value, and this new value immediately appears in Exchange21. 

Property Exchange21 in turn makes its work - resends data to the property ViewModel2.View2Text which is bound to the TextBlock of the View2; View2Text raises PropertyChanged event and data appears in the TextBlock - synchronously with typing in the View1.TextBox.

Fig. 2: Text typed in TextBox of View1 immediately appears in TextBlock of View2.

It is necessary to emphasize one very important thing. Pay attention to the fact that in the communications ViewModel - ViewModel and in the Binding with Views different properties are involved. ViewModel1.View1Text1 and ViewModel2.View2Text are busy with Binding to Views while Exchange11 and Exchange21 are busy with communications. One can assume that this is redundant - e.g., properties View1Text1 and View2Text may play both roles - communicate to each other and bind to Text properties for show data. But it is not so!

The thing is that properties bound to View follow the PropertyChanged paradigm - they fire PropertyChanged event in their setters - when property's value changes. PropertyChanged engine provides subscribing to the event for all Bindings based on some property. And if a property takes part in the ViewModel - ViewModel communication, then it participates in one of the PhysicalSource ParentBindings of the communication - side by side with Binding to DependencyProperty of View. Whenever a property is changed, Communication's Binding is activated together with Binding to View. That is an undesirable behavior.

So most ViewModel - ViewModel communications should be based on properties that do not participate in View - ViewModel connections. The need of additional properties may be considered as a drawback of the proposed methodology. Even so, apology may be found in the informal rule: "one Property - one or less Binding".

Second task - sending a piece of text back from ViewModel2 to ViewModel1

Let's show a sending of a piece of text from one ViewModel to another one. For fun let's take text which was formed in ViewModel2 and send it back to the ViewModel1 and further to the TextBlock of the View1 so that each word is reversed but general order of words is kept safe and sound.
Data transformation should be applied to the sending text.

Data transformation (together with data providing) is a mission of the Model. It is not a proper way to include Model as a member (or local variable) into the ViewModel. However, I will postpone discussion about a way of communications with Model to future.

PseudoModel is a class that carries the desired transformation. Static method PseudoModel.ReverseWords from file PseudoModel.cs implements the transformation. Call to ReverseWords is made in a getter of ViewModel2.Exchange21 property. Communication sends reversed text to ViewModel1.

C#
public string Exchange21
{
    get 
    {
        return PseudoModel.ReverseWords( View2Text );
    }

    . . . .
}
Sn. 11: Getter of property Exchange21 - file ViewModel2.cs

Activation of this communication is made by the command CommunicateCommand which in turn is activated by pressing a button "Return a reversed words text...".

Figure 3 shows the result of communication action.

Fig. 3: Reversed piece of text is successfully sent to ViewModel1.

Last words and future considerations

I promised to say few words about broadcasting in communication - a situation when a value from source ViewModel is sent to more than one target ViewModel. This requires changes in communication definitions - the use of collection of TargetViewModels and TargetProperties, in CommunicationConverter - different way of Convert and ConvertBack actions, and some additional checking. All of this may be considered in the future.

To be more specific, in the second part of the article we will consider a late creation of Views and ViewModels in ViewModel - ViewModel communication. Also some performance evaluation will be proceed.

References

1. Vice R., Siddiqi M.S. MVVM Survival Guide for Enterprise Architectures in Silverlight and WPF. – PACT publishing, 2012.

2. Gilbert D. Simple Inter-ViewModel Communication in WPF.

3. Grech M. More than Just MVC for WPF.

4. Barber S. MVVM Mediator Pattern.

5. Senneseth T. Object-To-Object Binding / ViewModel-To-ViewModel Binding.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Israel Israel
fan of Microsoft technology and Microsoft programming style

Comments and Discussions

 
Questionfrom where to download extensionHelper . Pin
mani shanker8-Feb-17 19:25
mani shanker8-Feb-17 19:25 
AnswerFormat issues.. Pin
Afzaal Ahmad Zeeshan12-Aug-15 4:03
professionalAfzaal Ahmad Zeeshan12-Aug-15 4:03 
GeneralRe: Format issues.. Pin
Leonid Osmolovski12-Aug-15 4:25
Leonid Osmolovski12-Aug-15 4:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.