Click here to Skip to main content
15,886,805 members
Articles / Desktop Programming / WPF

DelegateWatchCommand – a DelegateCommand without CommandManager

Rate me:
Please Sign up or sign in to vote.
4.89/5 (11 votes)
6 Mar 2011CPOL20 min read 40.6K   462   17   11
A small library that allows for a simple and reliable raising of the CanExecuteChange event through property changes caused by user input or any async change. It can be used with both WPF and Silverlight.

Contents

  • The Background section formulates the problem.
  • Then we look at the demo application.
  • Then a "manual" command notification is described.
  • This is followed by a generic but rather verbose, "naive" notification implementation.
  • At last, the DelegateWatchCommand is presented that makes the code much better. If you have a little interest on general discussions and well known techniques, please jump directly to the section "DelegateWatchCommand".
  • IN the end, some extensions (also supported by the library) and other usage cases are discussed.

Background

Several variations of DelegateCommand for WPF and Silverlight are well known and used, I suppose, by all who follow the MVVM pattern. The interface ICommand has only three members: Execute, CanExecute, and CanExecuteChanged. With DelegateCommand, it is easy to define Execute and CanExecute as delegates. If the value of CanExecute depends on some properties that change, then each time they do, CanExecuteChanged must be fired so that CanExecute is evaluated anew. This change notification is not difficult to implement, but the straightforward way of doing this is firstly, boring and secondly, error prone.

Josh Smith has proposed in http://joshsmithonwpf.wordpress.com/2009/07/11/one-way-to-avoid-messy-propertychanged-event-handling/ to exploit the CommandManager. I assume that many of us use his RelayCommand. But this solution has still some drawbacks. The first one is that there is simply no CommandManager in Silverlight and no one will prompt to check CanExecute again. The second one is that the CommandManager is aware only of the user input. If an essential property changes due to an async event (e.g., due to data delivered through WCF), the CommandManager sleeps and can't awake your command.

Prism avoids using CommandManager (http://compositewpf.codeplex.com/Thread/View.aspx?ThreadId=226136) and the opinion of Karl Shifflett should be considered.

This article proposes another-way-to-avoid-messy-code when notifying a DelegateCommand.

The basic assumption is that all objects involved in notification implement the INotifyPropertyChanged interface (INotifyCollectionChanged is also supported).

Demo Application

The demo application is very simple and only demonstrates the usage of the library compared with other implementations. Here is a snapshot:

DemoWindow.png

The UI is rather unimpressive and WinForms like. The following diagram shows the classes behind the UI:

Classes.png

These objects are our sandbox where we can play with different commands. There is a root object of type MasterVM that has a property named and typed Model. The Model object has a property named and typed SubModel. An instance of MasterVM is created when the demo program starts and is assigned to the DataContext of the window.

The goal of the game is to write a command that increments the property Result of the MasterVM. It is allowed to increment the result only when a certain condition (specified below) holds. The commands must respect this condition in their CanExecute. The condition depends on the properties of the Model and SubModel objects. Two string and two int properties that affect the condition are bound to TextBoxes and can be changed by the user. The boolean property ToggledProp is also essential but doesn't depend on user input - it is toggled by a timer each second (the property is bound to a CheckBox with the appropriate label but the CheckBox is disabled).

Further, the rules of the game say that the object SubModel doesn't exist permanently. The user can create or delete it with the button at the top of the window. Two properties that the condition depends on are located in the SubModel. So, the commands that we are going to write must cope with the properties of dynamically created and nulled objects.

First, we will go to the sandbox corner that is surrounded by a blue border (in the demo app window and in the object diagram). Inside of the green border at the bottom of the window, older girls and boys play with Collections. We will get there near the end of the article.

Here is the condition that defines the "executability" of the commands:

  • Model.IntProp must be greater than SubModel.AnotherInt AND
  • Model.StringProp must be longer than SubModel.AnotherString AND
  • ToggledProp must be true.

Different Kinds of Commands Bound to Different Buttons

All commands bound to the buttons in the blue border have the same Execute delegate:

C#
_ => this.Result++

The CanAlwaysCommand is unconditional. Its CanExecute delegate is not set and this means that it is always true. The button "Unconditional" is bound to this command and is always enabled. This command violates the rules of the game and will never win. Obviously, this command needs no notifications of changed property values.

All other commands delegate their CanExecute to the same CanIncrement() <code>method. Here is it:

C#
private bool CanIncrement( object parameter ) {
    return this.Model.SubModel != null
    && this.Model.IntProp > this.Model.SubModel.AnotherInt
    && this.Model.StringProp.Length > this.Model.SubModel.AnotherString.Length
    && this.Model.ToggledProp;
}

The Model property is assigned in the constructor and never changes, so it is OK not to check this.Model for not null.

RelayCommand

The button "Relay" refers to a RelayCommand as it was described by Josh Smith in http://joshsmithonwpf.wordpress.com/2008/06/17/allowing-commandmanager-to-query-your-icommand-objects/ and has become a classic implementation. It hooks the CommandManager's RequerySuggested event and prompts the button to check CanExecute anew each time the user clicks something somewhere, including changing the properties that are essential for the method CanIcnrement. But the ToggledProp property changes without user interaction and the "Relay" button can't react to its change. Its IsEnabled property is calculated at the moment when you change something manually. Depending on the ToggledProp value at this moment, the button's IsEnabled is set to true or false and doesn't change as long as you don't click something again.

Classical DelegateCommand

The ManualCommand is a simple DelegateCommand that doesn't hook RequerySuggested (like in Prism http://compositewpf.codeplex.com/releases/view/55580). Its method RaiseCanExecuteChanged() must be called explicitly whenever an essential property changes. This needs a number of code lines (too many lines as for a simple and transparent "mission" of these lines), and the code is error prone since it depends on property names defined as strings. If a property is renamed, all occurrences of the string must be checked. If your property has a name that is heavily used in many classes (say, "Name" or "Parent"), you must carefully distinguish between the strings that must and must not be changed.

So, I don't suggest programming this way; we are simply going to see what should be done "manually", and later, we'll try doing the same, but in a better way.

Since CanIncrement depends on properties of the Model, we must hook the PropertyChanged event of this.Model in MasterVM (in an absolutely standard way; you write this code every day and I list it here only to remind you how boring it is). Obviously, it would be not a good idea to notify the command directly from the setters of the Model's properties - the Model must not depend on the ViewModel in any way, it must not contain any code that knows something about the ViewModel. And even if we forget about MVVM, dispersing the notification code over nearly all objects has nothing to do with good design.

C#
// ------------- Model --------------- //
private Model _model;
public Model Model {
    get { return _model; }
    private set {
        if( _model != value ) {
            if( _model != null )
                _model.PropertyChanged -= 
                        new PropertyChangedEventHandler( _model_PropertyChanged 
            _model = value;
            if( _model != null )
                _model.PropertyChanged += 
                    new PropertyChangedEventHandler( _model_PropertyChanged );
            this.AddSubmodelCommand.RaiseCanExecuteChanged(); 
        }
    }
}

Please note that RaiseCanExecuteChanged() is called whenever the Model changes, although CanIncrement() doesn't depend explicitly on this.Model. We need this, since if a new model instance is assigned, it has its own property values and CanIncrement() must be re-evaluated. Here is the event handler:

C#
void _model_PropertyChanged( object sender, PropertyChangedEventArgs e ) {
    if( e.PropertyName == "IntProp" || e.PropertyName == "StringProp" 
            || e.PropertyName == "ToggledProp" )
        this.ManualCommand.RaiseCanExecuteChanged();
    else if( e.PropertyName == "SubModel" )
        this.SubmodelCopy = this.Model.SubModel;
}

The command is notified each time an essential property changes.

As you can see, the property SubModel is handled differently. We must hook up its PropertyChanged and handle it the same way as the Model's event. We could do this directly in the same place where SubmodelCopy gets its value:

C#
void _model_PropertyChanged( object sender, PropertyChangedEventArgs e ) {
      . . .
        // this is WRONG !!!
    else if( e.PropertyName == "SubModel" )
        if( this.Model.Submodel != null )
            this.Model.Submodel.PropertyChanged += 
                    new PropertyChangedEventHandler( _submodel_PropertyChanged );

This code has one problem: we can hook the event, but we can't unhook it. At the moment the code is notified that this.Model.SubModel is changed, the old value is already gone. So, we must have our own copy of SubModel in the ViewModel. This is the reason for creating an extra property SubmodelCopy. The property looks exactly like the property Model. It attaches/detaches an event handler that observes the essential properties of the current SubModel instance and notifies the command. This property can be private - no one else but the notification code is interested in it.

Being equipped with this code, the ManualCommand works properly. If you start the demo and click the "Create Submodel" button, the button "Manual" bound to the command will be enabled/disabled each second and respects all other values in CanIncrement (the initial values of all properties in the demo program satisfy the CanIncrement condition).

When we look at the code, we can see that to notify a command:

  • we need a copy of each sub-object involved (being more precise: each object whose properties affect the CanExecute condition),
  • we must hook/unhook PropertyChanged, and
  • we must know the names of the properties.

So, let's try and write a program that makes this in a generic way.

NaiveCommand (an Attempt at Generic Notification)

To observe the result of an expression that looks like x.P1.P2. ... Pm, we'll write a class that observes one property of one object (and call the class PropertyWatch, the word "observer" is already heavily used), and we'll link as many instances as we need in a chain (PropertyWatchChain class). Each "watch" in a chain (but the last one) supplies its successor with the object to be observed. The last object in a chain raises an event if its observed property changes. An attached event handler calls RaiseCanExecuteChanged() of the command we want to notify. This is our plan for this section.

The essential properties and fields of the PropertyWatch class are:

  • INotifyPropertyChanged Source { get; set; } - the object to be observed;
  • string PropertyName{ get; } - the name of the property to be observed;
  • Func<INotifyPropertyChanged, INotifyPropertyChanged> _getter; - a delegate that extracts the value of the observed property (a private field set in the constructor);
  • IPropertyWatch Next { get; set; } - the next element in the chain.

The last element in a chain is of type PropertyWatchTail. It doesn't need the _getter and Next members, but it needs a pointer (called Parent) to the PropertyWatchChain it belongs to. Through this pointer, it can raise the event. Both classes PropertyWatch and PropertyWatchTail implement the IPropertyWatch interface. The property Next is typed to this interface and accepts any of these two classes.

For instance, in the demo program, commands must be notified when this.Model.SubModel.AnotherInt changes. So, the objects should build the following structure:

WatchChain.png

The classes are simple, and there is nearly nothing that should be commented. Still, please note that all observed objects must implement INotifyPropertyChanged. If a PropertyWatch object gets a new value assigned to its Source property, it must call the _getter to obtain the value of the observed property and push the value to its successor. These assignments are denoted with the vertical lines in the picture. If the Source property is set to null (so that there is nothing to observe and the getter can't be called), the object must still push null to its successor. If a PropertyWatchTail instance gets a null as the object to observe, it must raise the event WatchedPropertyChanged of PropertyWatchChain.

So far so good. But if you look at the program that creates the "chains", you'll see how horrible it is (the function Cons() in the snippet prepends an element to a chain):

C#
// These 5 variables need not be declared as class members, 
//   the objects assigned to them will survive (would not be eaten by GC) 
//   even if they were not because they hang on the RaiseCanExecuteChanged event 
//   of the command. 
// These variables can be though helpful for debugging 
//   and to see what really happens. 

private PropertyWatchChain w2;
private PropertyWatchChain w3;
private PropertyWatchChain w4;
private PropertyWatchChain w5;
private PropertyWatchChain w6;

private void BuildNaiveCommand() {
    // CanExecute() depends on the values of six properties: 
    //   this.Model.SubModel
    //   this.Model.IntProp 
    //   this.Model.SubModel.AnotherInt
    //   this.Model.StringProp 
    //   this.Model.SubModel.AnotherString 
    //   this.Model.ToggledProp
    // We need only five PropertyWatchChain instances because the first path 
    //   is a sub-path of the other ones.

    w2 = new PropertyWatchChain( new PropertyWatchTail( "IntProp" ) )
        .Cons( new PropertyWatch( "Model", x => ((MasterVM)x).Model ) );
    w2.Head.Source = this;
    w2.WatchedPropertyChanged 
                    += new EventHandler<EventArgs>( w_WatchedPropertyChanged );
    w3 = new PropertyWatchChain( new PropertyWatchTail( "AnotherInt" ) )
        .Cons( new PropertyWatch( "SubModel", x => ((Model)x).SubModel ) )
        .Cons( new PropertyWatch( "Model", x => ((MasterVM)x).Model ) );
    w3.Head.Source = this;
    w3.WatchedPropertyChanged += 
                     new EventHandler<EventArgs>( w_WatchedPropertyChanged );

    // watch the string, not its length (this is enough, since strings are immutable 
    //   and the length cant change when the string doesnt):
    w4 = new PropertyWatchChain( new PropertyWatchTail( "StringProp" ) )
        .Cons( new PropertyWatch( "Model", x => ((MasterVM)x).Model ) );
    w4.Head.Source = this;
    w4.WatchedPropertyChanged 
                     += new EventHandler<EventArgs>( w_WatchedPropertyChanged );

    // again, watch a string:
    w5 = new PropertyWatchChain( new PropertyWatchTail( "AnotherString" ) )
        .Cons( new PropertyWatch( "SubModel", x => ((Model)x).SubModel ) )
        .Cons( new PropertyWatch( "Model", x => ((MasterVM)x).Model ) );
    w5.Head.Source = this;
    w5.WatchedPropertyChanged 
                     += new EventHandler<EventArgs>( w_WatchedPropertyChanged );


    w6 = new PropertyWatchChain( new PropertyWatchTail( "ToggledProp" ) )
        .Cons( new PropertyWatch( "Model", x => ((MasterVM)x).Model ) );
    w6.Head.Source = this;
    w6.WatchedPropertyChanged 
                     += new EventHandler<EventArgs>( w_WatchedPropertyChanged );

    // This program is ugly!  
    // Please, never write such programs and better even don't read them. 
}

void w_WatchedPropertyChanged( object sender, EventArgs e ) {
    this.NaiveCommand.RaiseCanExecuteChanged();
}

The take away of the section about the NaiveCommand is that:

  • It is not difficult to implement a generic notification of commands;
  • The definitions of what should be watched are concentrated in one place and not dispersed through the whole program;
  • These definitions are not transparent, it's not possible to understand at a glance what properties of what objects are observed;
  • The definitions are error prone since the property names are not checked by the compiler.

We can fix the last mentioned flaw if we use the following constructor:

C#
public PropertyWatch( 
          Expression<Func<INotifyPropertyChanged, INotifyPropertyChanged>> getterExpr ) 
{
    _propName = PropertySelectorUtils.GetPropertyName( getterExpr );

    if( getterExpr == null )
        throw new ArgumentNullException( "getterExpr" );
    _getter = getterExpr.Compile();
}

But we'll better make two steps in this direction and define the whole chain of watchers with one lambda expression.

But first, a little discussion. The PropertyWatch class treats all observed objects assigned to its Source property as INotifyPropertyChanged. Due to this simplification, all getters passed as the getterExpr parameter must first cast their arguments. So, the getter for the first watcher in each chain is x => ((MasterVM)x).Model. It is possible to write a generic typed version of PropertyWatch that has two type arguments: T1 is the type of the observed object, and T2 is the type of the observed property and thus the type of the successor's observed object. For the last element in a chain, we need a generic type PropertyWatch with only one type parameter. PropertyWatch<T1,T2> inherits from PropertyWatch<T1>. The type of Next in PropertyWatch<T1,T2> will be PropertyWatch<T2> so that any PropertyWatch<T2,T3> as an intermediate element or PropertyWatch<T2> as the last chain element are acceptable as the successor's type. I don't believe that all these complications pay. Avoiding a pair of type casts per user interaction is not a reasonable goal, I suppose. But it is possible, possible. If you mind.

DelegateWatchCommand

This is a simple DelegateCommand extended by a list of PropertyWatchChains that are built in the constructor out of lambda expressions. Well, there is nothing I can add to the description. Let us look at how a command instance is defined in the demo program.

C#
private DelegateWatchCommand<MasterVM> _watchCommand;
public DelegateWatchCommand<MasterVM> WatchCommand {
    get {
        if( _watchCommand == null )
            _watchCommand = new DelegateWatchCommand<MasterVM>(
                _ => this.Result++,    // Execute
                this.CanIncrement,     // CanExecute
                this,                  // the root object of watch chains
                // properties to watch
                x => x.Model.SubModel.AnotherString,
                x => x.Model.SubModel.AnotherInt
                x => x.Model.IntProp,
                x => x.Model.StringProp,
                x => x.Model.ToggledProp
                );
        return _watchCommand;
    }
}

The real text differs a little from this snippet, since it (I mean the demo code ) uses/illustrates an extension described in the next section. Please note that the type parameter of the command's class is not the type of the command parameter as in some implementations of DelegateCommand (more on the type parameter later).

As for me, this command looks much better. One glance is enough to see what properties its CanExecute depends on. The lambda expressions are checked by the compiler. No messy code and the command is still notified of all essential changes including those implied by async events.

The class DelegateWatchCommand inherits from DelegateCommand that handles Execute and CanExecute delegates in a well-known way, and restricts the type parameter:

C#
public class DelegateWatchCommand<T>: DelegateCommand 
                        where T: class, INotifyPropertyChanged {
             . . .

Let' take a look at the most interesting constructor and its helper method.

C#
public DelegateWatchCommand( Action<object> execute, Func<object, bool> canExecute, 
                             T source, params Expression<Func<T, object>>[] exprList )
    : base( execute, canExecute ) 
{ 
    _chains = exprList.Select( e => this.MakePropertyWatchChain( e ) ).ToList();
    this.Source = source;
}

The helper method creates one chain out of one lambda expression and hooks its event.

C#
private PropertyWatchChain MakePropertyWatchChain( Expression<Func<T, object>> expr ) {
      var ret = PropertyWatchChain.FromLambda( expr );
      ret.WatchedPropertyChanged 
                   += new EventHandler<EventArgs>( ret_WatchedPropertyChanged );
    return ret;
}

Now a few words about the type parameter and the parameter "source" in the constructor. The constructor assumes that all PropertyWatch chains start with the same object instance, and this instance should be passed to the constructor as the third parameter or assigned later to the property Source of the command. This restriction (only one root instance) seems to be feasible due to the following. The chains must lead to all essential and changeable properties that affect CanExecute() of the command. If CanExecute() manages somehow to access these values, then we can do this, also starting from the same point -- from the instance, where the CanExecute() lives. So, the type parameter is the type, where CanExecute() is implemented and the parameter "source" is "this" of the CanExecute(), in most cases.

Alternatively, in the demo program, we could use this.Model as the starting point. If you look at the lambda expressions, you will see that they all start with this.Model. So we could use Model as the type parameter and pass this.Model as the third argument. The value can be null at the beginning (the watchers will be constructed but in a hibernated form -- they will never raise the event until something different than null is assigned). We must take care and assign this.Model to the command's Source property each time a new value is assigned to this.Model. Actually, this means that we notify the command "manually" about the changes of the first object in all chains and let it observe the rest automatically, so a step back to a messy implementation. As noted previously in the demo program, the Model is created in the constructor and never changes. So in this special case, starting chains with this.Model<code> could be reasonable.

If you follow the destiny of the lambda expressions in the DelegateWatchCommand constructor, you will see that where the static method PropertyWatchChain.FromLambda() is called, there is a kind of constructor of the PropertyWatchChain class. The reason not to use a "real" constructor is that the PropertyWatchChain class needs no type parameters while the FromLambda does. The method uses the Visitor class to handle the expressions. The Visitor class follows (no wonder) the Visitor pattern. It must deal with a rather restricted sort of expressions. They consist solely of property accessors and end with a parameter. Type conversions may occur, but they are simply skipped (we have agreed to treat all types as INotifyPropertyChanged). For the first property accessor, an instance of the PropertyWatchTail class is created (a lambda expression tree reads from right to left). Each further property results in a PropertyWatch instance that is prepended to the result chain when recursion in the Visitor returns.

Extension One. Chains Joined to Trees

Often several property watch chains have a common beginning. This allows to save typing and to introduce some structure if we join (some) chains and represent them as a tree. Please note, lambda expressions used to build PropertyWatch chains are never executed (as a whole). So we can insert additional "pseudo functions" that don't denote any meaningful computations and only give auxiliary information to the Visitor.

So, we define an extension method:

C#
public static object MultiWatch<T>( this T source, 
                                    params Expression<Func<T, object>>[] branches ) 
    where T: class, INotifyPropertyChanged 
{
    throw new InvalidOperationException( "This method is not intended to be called" );
}

We can use the method this way:

C#
_watchCommand = new DelegateWatchCommand<MasterVM>(
    _ => this.Result++,
    this.CanIncrement,
    this,
    x => x.Model.SubModel.MultiWatch( 
        y => y.AnotherString,
        y => y.AnotherInt
    ),
    x => x.Model.IntProp,
    x => x.Model.StringProp,
    x => x.Model.ToggledProp
);

Does it pay? It depends. If the common part is longer than in the demo, this feature can be useful.

The implementation behind it is as follows. The root chain that observes x.Model.SubModel ends with an instance of a new class PropertyWatchMulti instead of a PropertyWatchTail. This class contains "continuation chains" and has two purposes:

  • it pushes the observed object to all of the child chains and
  • it listens to their WatchedPropertyChanged transferring the signal to its own PropertyWatchChain instance.

Of course, the Visitor class must be changed. If it finds a MethodCallExpression and the method is MultiWatch, it generates an instance of the PropertyWatchMulti class and processes recursively each "continuation" expression.

Extension Two. Collections

Until now, we have handled only the classes that implement INotifyPropertyChanged. There is another notification interface that is commonly used -- IObservableCollection. It would be useful to support this interface too. For instance, a collection contains Points and a command must be executable if there is a point with a negative X value, or if all points have X greater than Y, or if all collection members are "valid", whatever does it mean in a specific program.

So we need a new class WatchCollection. It observes membership changes and can watch one or more property paths in each collection element. This class is somehow similar to PropertyWatchMulti because it has multiple successors (one for each collection element) and, maybe, multiple chains for each element. Here is the idea of how the class does its work. When an instance of the class is constructed, a "pattern" chain is created. It is then cloned for each new collection member. The property Source is not copied by cloning because each copy gets its own source (the new collection element to be observed). If multiple chains must be watched in each element, the pattern starts with a nearly dummy chain that consists only of a PropertyWatchMulti.

In a lambda expression, we will denote watching collection elements with another extension method that will never be called:

C#
public static object CollectionWatch<T>( this ObservableCollection<T> source, 
params Expression<Func<T, object>>[] branches ) 
{
  throw new InvalidOperationException( "This method is not intended to be called" );
}

The first parameter is a lambda expression that starts in the root object and leads to the collection whose elements should be observed. The following parameters are lambda expressions that start in a collection element and end with the properties that should be observed.

The Visitor is changed once more, of course. This change needs Reflection because the type parameter of the WatchCollection instance that should be generated is not known statically.

Now let's return to the demo program. The controls at the bottom of the window illustrate a case of a command that must be enabled when a certain condition on the properties of collection elements holds. Let's look at the contents of the green border. The MasterVM class has a property DemoCollection of type ObservableCollection<DemoCollectionElement>. The DemoCollectionElement class has only one integer property X and still must implement INotifyPropertyChanged. The collection members are displayed in the ListBox to the left. Two buttons above allow for:

  • adding items with a random value of X in the range 0 to 10 and
  • removing all elements.

The "Add Item" button allows to add not more than four items. So are the rules of the game.

And then the rules say that we must write a command that is enabled if the average of all Xs in the collection is greater than 9. The command property in the demo is called CollectionDependingCommand and is bound to the button labeled "Depends on Average". The command must observe changes inside of the collection elements. The action of the command increments the field "Result" on the top of the window to produce some visible effect of clicking the button. The constructor and the CanExecute of the command are listed below and illustrate the usage of CollectionWatch:

C#
public class MasterVM: NotifyPropertyChangedBase {
. . . 
// --------------- CollectionDependingCommand --------------- //
private DelegateWatchCommand<MasterVM> _collectionDependingCmd;
public ICommand CollectionDependingCommand {
    get {
        if( _collectionDependingCmd == null )
            _collectionDependingCmd = new DelegateWatchCommand<MasterVM>(
                _ => this.Result++, 
                this.IsAverageOK, 
                this,
                x => x.DemoCollection.CollectionWatch( y => y.X ) // watch X in elements
                );
        return _collectionDependingCmd;
    }
}

private bool IsAverageOK( object dummy ) {
    if( this.DemoCollection.Count == 0 ) // can't compute Average if there are no items
        return false;
    return this.DemoCollection.Average( x => x.X ) > 9;
}

The condition (average > 9) can hardly be satisfied with random numbers less than 10. But if you select an item in the list and edit its value in the TextBox "Selected Item", the button can be enabled. This shows that the condition is evaluated anew if the X property of any element changes. If you insert only one element and change its value to 10, the button will be enabled. Adding one more element will nearly always result in an average that is less than 9 and the button will be disabled. This shows that the command is aware of collection membership changes. Well, that is it.

Other Usages

The demo program illustrates another possible usage of the property chain observer. The PropertyWatchChain class can be thought of as a quarter of a poor man's binding. It is a half, because it is OneWay, and it is a quarter, because it observes changes, but assigns nothing.

The class PropertyWatchTrigger allows for triggering an action when a property change takes place. If this action assigns the changed value, you have something like a simple one way binding. Of course, the action can prescribe any computation, not necessarily an assignment. In the demo program, the field "Average" at the right hand side of the green border must be updated each time a member of the DemoCollection changes its X (or if a member is added or deleted). The following lines define the trigger that makes this job:

C#
_triggerAverage = new PropertyWatchTrigger(
    PropertyWatchChain.FromLambda<MasterVM>( 
        x => x.DemoCollection.CollectionWatch( y => y.X ) ), // watch X in all elements
        () => this.Average = this.DemoCollection.Count == 0 
          ? 0.0 
          : this.DemoCollection.Average( e => e.X ), // compute Average, if smth changes
        this );

The first argument defines the watch and the second argument defines the action. The third argument is the root of the observed property chain. The root object can be left null and assigned later.

Incorporating the ICommand Parameter Type

The implementation of the commands in the library completely ignores the ICommand parameter simply passing it to the delegates. As you may see, the DelegateWatchCommand inherits from the DelegateCommand and cares only about notifications. It should be easy to implement in the same way as a version parameterized additionally with the ICommand parameter type, that inherits from a Prism-like DelegateCommand.

Practically

The demo program and the library are written in VS 2010 Express.

I suppose you can use the compiled DLL directly. The library and all classes are really small, so it may be better though to include sources in your library (where all your basic classes are located) and avoid one more DLL, one more reference, etc.

Points of Interest

Classes that notify commands of changes in property values are dynamically generated from lambda expressions.

Additional information needed to generate these classes in some special cases is represented by "pseudo functions" that are included in lambdas but are never called.

History

  • Initial version: 07 March 2011.

License

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


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Geir Sagberg25-Jun-12 5:06
Geir Sagberg25-Jun-12 5:06 
GeneralNullReferenceException in some cases Pin
concueror2-May-11 8:05
concueror2-May-11 8:05 
GeneralRe: NullReferenceException in some cases [modified] Pin
Dmitri Raiko2-May-11 11:46
Dmitri Raiko2-May-11 11:46 
GeneralMy vote of 5 Pin
halotron13-Apr-11 0:13
halotron13-Apr-11 0:13 
GeneralRe: My vote of 5 Pin
Dmitri Raiko20-Apr-11 10:41
Dmitri Raiko20-Apr-11 10:41 
GeneralHa this is superbly funny, but... [modified] Pin
Sacha Barber6-Mar-11 21:58
Sacha Barber6-Mar-11 21:58 
GeneralRe: Ha this is superbly funny, but... Pin
Dmitri Raiko7-Mar-11 9:06
Dmitri Raiko7-Mar-11 9:06 
GeneralRe: Ha this is superbly funny, but... Pin
Sacha Barber7-Mar-11 9:15
Sacha Barber7-Mar-11 9:15 
GeneralMy vote of 5 Pin
deBaires6-Mar-11 20:57
deBaires6-Mar-11 20:57 
GeneralRe: My vote of 5 Pin
Dmitri Raiko7-Mar-11 8:58
Dmitri Raiko7-Mar-11 8:58 
QuestionMVVM still? Pin
Paul Selormey6-Mar-11 16:59
Paul Selormey6-Mar-11 16:59 
Just wondering...how many articles and libraries will it take to get the MVVM right?

Best regards,
Paul.
Jesus Christ is LOVE! Please tell somebody.

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.