Click here to Skip to main content
13,897,583 members
Click here to Skip to main content
Add your own
alternative version

Stats

21.8K views
252 downloads
6 bookmarked
Posted 16 May 2015
Licenced CPOL

MultiBinding for WPF Command Combining

, 16 May 2015
Rate this:
Please Sign up or sign in to vote.
Techniques for joint execution of a group of commands after single user interface action.

Download MultiCommandButton.zip - 52.5K

Introduction

The article suggests a method of joint sequential execution of a group of commands after a single user interface action, e.g. after pressing a button . The method is based in a very natural way on MultiBinding - a common and useful WPF technique. Its implementation does not require the developing of any new pattern or class. Quite the contrary it adopts some well known MultiBinding features for achieve the goal.

I apologize for some annoying candy-box beauties that appear in the GUI - sometimes life looks a bit brighter with quirks.

Background

During an interaction with a user, common WPF controls allow to invoke some commands for accomplish this interaction with some system work. In most scenarios such controls have a possibility for invoke a single command only. There exists a special control's property Command that provides the command invocation .

Throughout development of my projects, I have faced a need to invoke several different commands after a single GUI action. For example, pressing of a button should bring into play some functionality of the project and should make valuable changes in the GUI simultaneously . Of course, such actions are very different in their nature and should not be mixed together in one module. This is especially true for such technologies as MVVM is.

Few years ago Josh Smith implemented a <a href="http://www.codeproject.com/Articles/25808/Aggregating-WPF-Commands-with-CommandGroup">CommandGroup </a>interface<a href="http://www.codeproject.com/Articles/25808/Aggregating-WPF-Commands-with-CommandGroup"> </a>- a perfect method for joint command combining and I have used it consistently. The method does not use MVVM technology.

Among advantages of MVVM technology, there is a requirement that all View actions would be carried out on the basis of a ViewModel. By default ViewModel evaluates the DataContext of the user's control and thus the source of the command may be omitted in XAML definitions of Binding. This is not possible for CommandGroup - Member property used there requires explicit source reference.

Besides the Command property most controls have a CommandParameter property. This property is useful when a command needs additional information for its execution. CommandGroup does not allow such command parameters.

I decided to provide controls' with support of multiple commands in a different way - to use MultiBinding.  MultiBinding is a very common WPF way for bind multiple items to a single destination property. This will allow both use of default DataContext and use of parameters.

Implementation from a Brief Glance

The Demo solution illustrates the technique of command combining: one button click invokes execution of several commands; below they will be referred as partial commands.

Each project in the Demo solution is implemented as MVVM pattern: MainVindow.xaml and clean code MainVindow.cs behind it are used as View; ViewModel class is distributed between 3 files - ViewModel.cs, ViewModel.Commands.cs and ViewModel.Properties.cs. The Model does not present.

I decided to implement 4 partial commands. For fun they are named after quarters: NorthActionCommand, WestActionCommand, SouthActionCommand and EastActionCommand. The commands follow naming rules of ICommand interface and are implemented as instances of the well known class <font face="Courier New">RelayCommand</font>. Execution of any command generates appropriate message in a TextBlock

Invocation of these 4 partial commands is performed by binding of Button's property Command. But MultiBinding instead of simple Binding is implemented. This leads to sequential execution of a gang group of four  partial commands.

Commands are executed with befitting delays to demonstrate that execution sequence takes place. This is achieved by Task representation of asynchronous execution.

The messages that the commands display in the TextBlock are generated by corresponding properties. These properties are bound to TextBlock's Run.Text property in a common INotifyPropertyChanged manner.

There are 4 projects in the provided solution: first one is a dll that defines annoying GUI beauties - it does not deserve any consideration. The three other projects demonstrate MultiCommand without parameters, MultiCommand with one parameter and MultiCommand with multiple parameters.

In my projects, I try consistently to get rid of "magic" numbers and strings. This is especially important for naming of properties that follow INotifyPropertyChanged interface during the raising of PropertyChanged event. Constant strings in such cases are replaced with lambda expressions of form ( ) => Property. This mechanism is well known.

Organization of the View - MainWindow.xaml

The View of each project is based on MainWindow class. It evaluates its DataContext property with ViewModel class.

View defines a Grid in which all View features  - TextBlock and Button are located.

Below - the truncated definition of the TextBlock. Look at Run.Text dependency properties. They are bound to appropriate ViewModel properties - report properties - that provide display of corresponding command messages. This binding is made in a common manner of INotifyPropertyChanged interface.

An excerpt snippet from file MainWindow.xaml (MultiCommandButtonNoParams project)

<TextBlock ...>
	<TextBlock.Inlines>
		<Run Text="{Binding Path=NorthCommandManifest}" .../>
		<LineBreak/>
		<Run Text="{Binding Path=WestCommandManifest}" .../>
		<LineBreak/>
		<Run Text="{Binding Path=SouthCommandManifest}" .../>
		<LineBreak/>
		<Run Text="{Binding Path=EastCommandManifest}" .../>
	</TextBlock.Inlines>
</TextBlock>

On the other hand, Button control is not organized in a common way. Button's Command property is bound by means of MultiBinding to the set of 4 partial commands. With the exception of MultiBinding nature, this Binding follows common Command Binding principles. Each partial Binding of the MultiBinding attaches corresponding partial command to the Path property of the Binding.

An excerpt snippet from file MainWindow.xaml (MultiCommandButtonNoParams project)

<Button Grid.Column="1" ... Focusable="False">
	<Button.Command>
		<!--
		Multicommand construction that consists of a set of sequentially executed commands.
		Each command sends a message about execution to the TextBlock defined above.
		-->
		<MultiBinding Converter="{StaticResource multiCommandConverter}" >
			<Binding Path="NorthActionCommand"/>
			<Binding Path="WestActionCommand"/>
			<Binding Path="SouthActionCommand"/>
			<Binding Path="EastActionCommand"/>
		</MultiBinding>
	</Button.Command>
</Button>

 

The MultiCommand Engine

As noted above each partial command is implemented as a RelayCommand.

A snippet from RelayCommand.cs (MultiCommandButtonNoParams project) file shows constructor

public RelayCommand( Action<object> execute, Predicate<object> canExecute )
{
	if ( execute == null )
	{
		_execute = ( p ) => { };
		_canExecute = ( p ) => true;
	}
	_execute = execute;
	_canExecute = canExecute;
}

The constructor looks straightforwardly and requires 2 parameters - first one for the delegate that provides execution routine and the second one - for the delegate that approves command execution. In this demo solution any error processing is not provided.

We will discuss the making of partial commands later.

MultiCommand is organized in the same manner as partial commands are. But it is based on MultiBinding. As usual for <font face="Courier New">MultiBinding</font>, the Devil is in the details - i.e. in the implementation of <font face="Courier New">IMultivalueConverter</font>. The implementation is made in <font face="Courier New">MultiCommandConverter </font>class. MultiCommandConverter class follows IMultiValueConverter interface and has to implement two obligatory methods - Convert and ConvertBack. ConvertBack method is not important for our aims and simply returns null.

The Convert method, on the other hand,  performs all the work that MultiCommandConverter should do - it creates and returns a generalized RelayCommand that invoke 4 partial commands.

Convert method - a snippet from MultiCommandConverter.cs (MultiCommandButtonNoParams project) file 

public object Convert( object[ ] value, Type targetType,
			object parameter, CultureInfo culture )
{
	_value.AddRange( value );
	return new RelayCommand( GetCompoundExecute( ), GetCompoundCanExecute( ) );
}

The method gets its first parameter value with the help of the Binding of all partial commands. The value is an array of partial commands. First of all Convert stores this array in a private variable to make it available during Convert execution - not in BAML compilation only.

After that Convert builds a new RelayCommand that should represent MultiCommand itself. This RelayCommand is based on private methods GetCompoundExecute and GetCompoundCanExecute that return required delegates. Both private methods (in the snippet below) work in the same manner - they return lambda-expressions that provide upon invoke a loop execution of Execute and CanExecute methods of each partial command respectivly.

A snippet from MultiCommandConverter.cs file (MultiCommandButtonNoParams project)

private Action<object> GetCompoundExecute( )
{
	return ( parameter ) =>
	{
		foreach ( RelayCommand command in _value )
		{
			if ( command != default( RelayCommand ) )
				command.Execute( parameter );
		}
	};
}

private Predicate<object> GetCompoundCanExecute( )
{
	return ( parameter ) =>
	{
		bool res = true;
		foreach ( RelayCommand command in _value )
			if ( command != default( RelayCommand ) )
				res &= command.CanExecute( parameter );
		return res;
	}
}

A RelayCommand generated by MultiCommandConverter is invoked as a simple Button's command after the Button click. And then it rolls out for proper execution of all partial commands.

Special attention should be given to execution of commands with parameters. This will be discussed later.

The Structure of Partial Commands

Each partial command does a very simple job - it announces the fact of its execution. But for clarity I wanted to show these announcements in GUI not at once but with befitting delay one from the other. Simple use of Thread.Sleep did not work. Asynch and Await are inavailable due to .Net 4.0 restriction. The simplest thing I decided to use was Task asynchronous proceeding. With this assumption typical execute action looks as follows.

A snippet from ViewModel.Commands.cs (MultiCommandButtonNoParams project) file

private void NorthAction( object parameter )
{
	string name = MethodBase.GetCurrentMethod( ).Name;
	Task.Factory.StartNew( ( ) => { Thread.Sleep( ir_delay1000 ); } ).
		ContinueWith( t => { NorthCommandManifest = name + sr_executed; } );
}

In the above snippet the Task is created and started with the StartNew method. It performs required delay. After that the Task continues with ContinueWith method that provides an evaluation of appropriate property. This evaluation makes changes in the appropriate Run of the TextBlock by means of property changed notification (as was mentioned above). At last TextBlock shows the message successfully.

Each partial command uses its own delay - NorthAction uses 1000 ms, WestAction - 2000 ms, SouthAction - 3000 ms and EastAction - 4000 ms. This is important for later consideration.

All CanExecute methods return true for brevity.

The structure of report properies

These properties follow rules of INotifyPropertChanged, i.e. after setting new value they raise PropertyChanged event that updates Binding. Below - the snippet of one of these properties - a NorthCommandManifest property.

A snippet from ViewModel.Properties.cs (MultiCommandButtonNoParams project) file

public string NorthCommandManifest
{
	get { return _northCommandManifest; }

	set
	{
		if ( value != _northCommandManifest )
		{
			_northCommandManifest = value;
			RaisePropertyChanged( ( ) => NorthCommandManifest );
		}
	}
}

Other manifest properties are defined similarly.

Execution of MultiCommandButtonNoParams Project

As supposed, pressing the button causes reporting of 4 messages. Each message appears with some delay after the previous one (you should believe me so far).

 

Description and Execution of MultiCommandButtonOneParam Project

Now let us consider one parameter MultiCommand implementation. Let us show in GUI a delay in ms between reporting of execution of each command.

This is a task of MultiCommandButtonOneParam project.

First of all let us add a CommandParameter property to the Button in the View:

An excerpt snippet from file MainWindow.xaml (MultiCommandButtonOneParam project); change is in bold

<Button Grid.Column="1" ... Focusable="False">
	<Button.Command>
		<!--
		Multicommand construction that consists of a set of sequentially executed commands.
		The execution is done in the order of commands. Each command sends a message about 
		execution to the TextBlock defined above.
		-->
		<MultiBinding Converter="{StaticResource multiCommandConverter}" >
			<Binding Path="NorthActionCommand"/>
			<Binding Path="WestActionCommand"/>
			<Binding Path="SouthActionCommand"/>
			<Binding Path="EastActionCommand"/>
		</MultiBinding>
	</Button.Command>
	<Button.CommandParameter>
		<Binding Path=Delay/>
	</Button.CommandParameter>
</Button>

As you can guess, Delay is the name of a property that coerces each command to be delayed on appropriate number of milliseconds. Delay obtains its initial value of 0 during initialization.

A snippet from file ViewModel.Commands.cs ( MultiCommandButtonOneParam project); Delay property.

private int _delay = 0;
public int Delay
{
	get { return _delay; }

	set
	{
		_delay = value;
		RaisePropertyChanged( ( ) => Delay );
	}
}

Before beginning of each TaskDelay obtains the value to be used in the Task. This Delay value is used in the following Task.Factory.StartNew for proper delay effect. 

A snippet from file ViewModel.Commands.xaml (MultiCommandButtonOneParam project); _execute delegate value for WestActionCommand.

private void WestAction (object parameter )
{
	Delay = ir_delay2000;

	Stopwatch w = new Stopwatch( );
	string name = MethodBase.GetCurrentMethod( ).Name;
	Task.Factory.StartNew( ( ) => { w.Start( ); Thread.Sleep( (int )parameter ); } ).
			ContinueWith( t=>
				{
					w.Stop( );
					WestCommandManifest = string.Format( name + sr_wasted,
						( int )w.ElapsedMilliseconds );
				};
}

Now let us run MultiCommandButtonOneParam project.

Wow! Delay does not work - commands only spend little time for their own affairs. It seems that MultiCommand obtains its parameter with its initial value of 0 (from initial Delay) and uses it for all partial Commands.

That's correct. Button has bound its parameter to Delay before execution of the MultiCommand. It had its initial value of 0 at that time. After that MultiCommandConverter begins to work and it uses the parameter value that was bound before the commencement of its work. New changes do not have any effect even with property changed notification magic.

In order to implement different delays for different partial commands more than one parameter should be used.

 

Description and Execution of MutiCommandButtonMultiParam Project

Multiparameter command binding is a well known issue. Reference may be found here. Here is definition in a View - in MainWindow.xaml file.

A snippet from MainWindow.xaml file (MultiCommandButtonMultiParam project)

<!--
Multiparameter construction that consists of a set of bound parameters.
MultiValueConverter builds an IEnumerable aggregate from these parameters
and passes it to commands' MultiValueConverter
-->
<Button.CommandParameter>
	<MultiBinding Converter="{StaticResource multiParameterConverter}"/>
		<Binding Path="NorthDelay"/>
		<Binding Path="WestDelay"/>
		<Binding Path="SouthDelay"/>
		<Binding Path="EastDelay"/>
	</MultiBinding>
</Button.CommandParameter>

The variables bound to parameters are readonly properties and should be bound before MultiCommand binding.

As usual, IMultiValueConverter implementation is central to this MultiBinding.

A snippet from MultiParameterConverter.cs file (MultiCommandButtonMultiParam project) - Convert method of the converter

public object Convert( object[ ] value, Type targetType,
	object parameter, CulturInfo cultue )
{
	return new List<object>( value );
}

The converter returns a List<object> of parameters only. This returned value is passed to the MultiCommandConverter for use in MultiCommand's _execute. I have chosen a sequential way: next partial command uses next partial parameter. Of course, more complicated ways may be implemented here for use of partial parameters by partial commands.

A snippet from MultiCommandConverter.cs file (MultiCommandMultiParam project) - GetCompoundExecute and GetCompoundCanExecute methods

private Action<object> GetCompoundExecute( )
{
	return ( parameter ) =>
	{
		var items = ( ( List<object> )_value ).Zip( ( List<object> )parameter, ( v, p ) =>
			new { First = v, Second = p } );
		foreach ( var item in items )
			if ( item.First != default( RelayCommand ) )
				( ( RelayCommand )item.First ).Execute( item.Second );
	};
}

private Predicate<object> GetCompoundCanExecute( )
{
	return ( parameter ) =>
	{
		bool res = true;
		var items = ( ( List<object> )_value ).Zip( ( List<object> )parameter, ( v, p ) =>
			new { First = v, Second = p } );
		foreach ( var item in items )
			if ( item.First != default( RelayCommand ) )
				res &= ( ( RelayCommand )item.First ).CanExecute( item.Second );
		return res;
	}
}	

Now the last thing remains to be shown - methods that partial commands' _execute delegates subscribe on. Here is one of them - the NortAction method.

A snippet from ViewModel.Commands.cs (MultiCommandMultiParam project)

private void NorthAction( object parameter )
{
	StopWatch w = new StopWatch( );
	string name = MethodBase.GetCurrentMethod( ).Name;
	Task.Factory.StartNew( ( ) => { w.Start( ); Thread.Sleep( ( int )parameter ); } ).
			ContinueWith( t =>
				{
					w.Stop( );
					NortCommandManifest = string.Format( name+ sr_wasted, 
								( int )w.ElapsedMilliseconds );
				}
			);
}

Let us run MultiCommandMultiParam project:

Now it looks OK.

Conclusion and Possible Drawbacks

I have never checked timing losses of MultiCommand. It is possible that they would prevent the use of MultiCommand in time critical applications. 

Interesting aspect of implementation of concurrent execution of partial commands was beyond the scope of this article.

License

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

Share

About the Author

fan of Microsoft technology and Microsoft programming style

You may also be interested in...

Pro

Comments and Discussions

 
Questionlink broken Pin
kaija24-Oct-17 0:33
memberkaija24-Oct-17 0:33 
QuestionLink broken Pin
Member 1273507515-Jul-17 8:33
memberMember 1273507515-Jul-17 8:33 
QuestionRepaired Pin
Leonid Osmolovski18-May-15 3:21
memberLeonid Osmolovski18-May-15 3:21 
Questionsource code file link broken Pin
Tridip Bhattacharjee17-May-15 21:21
professionalTridip Bhattacharjee17-May-15 21:21 
AnswerRe: source code file link broken Pin
Leonid Osmolovski18-May-15 3:20
memberLeonid Osmolovski18-May-15 3:20 
GeneralRe: source code file link broken Pin
Tridip Bhattacharjee18-May-15 21:35
professionalTridip Bhattacharjee18-May-15 21:35 
GeneralRe: source code file link broken Pin
Member 1096422419-May-15 3:28
memberMember 1096422419-May-15 3:28 
GeneralRe: source code file link broken Pin
Leonid Osmolovski19-May-15 18:40
memberLeonid Osmolovski19-May-15 18:40 
GeneralRe: source code file link broken Pin
Tridip Bhattacharjee19-May-15 21:29
professionalTridip Bhattacharjee19-May-15 21:29 

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.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web05 | 2.8.190306.1 | Last Updated 17 May 2015
Article Copyright 2015 by Leonid Osmolovski
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid