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>
<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>
<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 Task
, Delay
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 Command
s.
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)
<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.