OpenLightGroup Blog

rss

Blogs from OpenLightGroup.net


Very Simple Example of ICommand CanExecute Method and CanExecuteChanged Event

Michael Washington said I should blog about this, so if it’s too mundane, blame him. I added some animation stuff to hopefully make it a little more interesting.

Consider a simulation where the ViewModel controls the onscreen action. On the following screen, clicking the Start Button will cause the space ship to fly around the the screen. But the Start Button should only be enabled if the ViewModel actually has access to the space ship and the Speed is valid.

image

Try It Out:

http://richardwaddell.adefwebserver.com/CanExecuteChangedSite/CanExecuteChangedExampleTestPage.html

Download Code:

http://richardwaddell.adefwebserver.com/CanExecuteChangedExample.zip

Acknowledgements:

John Papa for DelegateCommand

The Details

ICommand  provides an interface that can easily be bound to by the View to execute code in the ViewModel. In addition to the Execute() method you might expect, it also calls for a CanExecute() method. CanExecute() returns true if Execute() can currently be ‘executed’ safely. If you bind a Button.Command property to an ICommand implementation, the Button is automatically enabled or disabled according to the value returned by CanExecute(). But CanExecute() is a method, not a property, so if conditions change so CanExecute() would return true rather than false, or vice-versa, Button must know to call CanExecute() to find the new value.

That’s where CanExecuteChanged comes in. Button, through its base class, ButtonBase, is automatically subscribed to the CanExecuteChanged event when the Button.Command property is bound to the ICommand. As you might expect, when ButtonBase catches the CanExecuteChanged Event it responds by calling the CanExecute() method and sets the Button.Enabled state accordingly. So in your code, whenever a condition or property changes that affect what CanExecute() will return, the CanExecuteChanged event should be raised.

I’ll point out here that CanStartAnimation(), which is the method that you supply when you create StartAnimationCommand , will be called by CanExecute() to determine what value to return. In this simple example, CanStartAnimation() returns true if the ViewModel has access to the spaceShip FrameworkElement. To clarify a little futher  StartAnimationCommand is an ICommand property on MainPageViewModel. Specifically it is an instance of DelegateCommand,, a class generously provided by John Papa, which of course implements ICommand. The beauty of DelegateCommand is that it lets you define the methods to be called by CanExecute() and Execute().

CanExecute() doesn’t merely return the result from CanStartAnimation(). As you can see in the DelegateCommand source for CanExecute(), it determines whether the bool from CanStartAnimation() – which it just sees as a function pointer named canExecute that accepts an object and returns the bool in question, is different from the bool it got before. If so, it raises the CanExecuteChanged event.

public class DelegateCommand : ICommand
{
    Func<object, bool> canExecute;
    Action<object> executeAction;
    bool canExecuteCache;
    public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecute)
    {
        this.executeAction = executeAction;
        this.canExecute = canExecute;
    }
    #region ICommand Members
    public bool CanExecute(object parameter)
    {
        bool temp = canExecute(parameter);
        if (canExecuteCache != temp)
        {
            canExecuteCache = temp;
            if (CanExecuteChanged != null)
            {
                CanExecuteChanged(this, new EventArgs());
            }
        }
        return canExecuteCache;
    }
    public event EventHandler CanExecuteChanged;
    public void Execute(object parameter)
    {
        executeAction(parameter);
    }
    #endregion
}

So for the StartAnimationCommand ICommand implementation, if anything changes that will affect what CanStartAnimation() will return, simply call CanStartAnimation(), as you can see in LoadWorldElements() below:

namespace CanExecuteChangedExample
{
    public class MainPageViewModel
    {
        private FrameworkElement _SpaceShip = null;
        #region WorldCanvas
        private Canvas _WorldCanvas;
        private Canvas WorldCanvas
        {
            set
            {
                if (this._WorldCanvas != value)
                {
                    this._WorldCanvas = value;
                    LoadWorldElements();
                }
            }
        }
        #endregion
        public MainPageViewModel()
        {
            StartAnimationCommand = new DelegateCommand(StartAnimation, CanStartAnimation);
            SetWorldCanvasCommand = new DelegateCommand(SetWorldCanvas, CanSetWorldCanvas);
        }
        public ICommand SetWorldCanvasCommand { get; set; }
        public void SetWorldCanvas(object param)
        {
            WorldCanvas = param as Canvas;
        }
        private bool CanSetWorldCanvas(object param)
        {
            return param is Canvas;
        }
            
        public ICommand StartAnimationCommand { get; set; }
        public void StartAnimation(object param)
        {
            RunAnimation();
        }
        private bool CanStartAnimation(object param)
        {
            return _SpaceShip != null;
        }
        private void LoadWorldElements()
        {
            _SpaceShip =
                (from child in _WorldCanvas.Children
                 let element = child as FrameworkElement
                 where element.Name == "spaceShip"
                 select element).FirstOrDefault();
            
            // Call DelegateCommand.CanExecute to raise the CanExecuteChangedEvent
            StartAnimationCommand.CanExecute(null);
        }
        private void RunAnimation()
        {
            // Animate the LayoutPath
            Storyboard sb = new Storyboard();
            DoubleAnimation XAnimation = new DoubleAnimation();
            DoubleAnimation YAnimation = new DoubleAnimation();
            sb.Children.Add(XAnimation);
            sb.Children.Add(YAnimation);
            sb.Duration = new Duration(TimeSpan.FromSeconds(3));
            XAnimation.Duration = sb.Duration;
            YAnimation.Duration = sb.Duration;
            XAnimation.To = _WorldCanvas.ActualWidth - _SpaceShip.ActualWidth;
            YAnimation.To = Convert.ToDouble(_SpaceShip.GetValue(Canvas.TopProperty)) + (_SpaceShip.ActualHeight * 2);
            Storyboard.SetTarget(sb, _SpaceShip);
            Storyboard.SetTargetProperty(XAnimation, new PropertyPath("(Canvas.Left)"));
            Storyboard.SetTargetProperty(YAnimation, new PropertyPath("(Canvas.Top)"));
            sb.Begin();
        }
    }
}

That covers the basics. I might point out that the parameter to CanExecute() is null in this case because CanExecute() is only interested in _SpaceShip. But in other scenarios CanExecute() might require a parameter. For instance the Button CommandParameter property can be bound:

image

If you specify an argument here, you’ll need to pass the same argument to CanExecute() when you’re trying to trigger the CanExecuteChanged event. Actually I’ll go ahead and show an example by adding a textbox where the user can enter the speed and bind the Text property to a Speed string property in MainPageViewModel. If the entered value is other than a positive integer, CanStartAnimation() returns false and the button is disabled.

        private bool CanStartAnimation(object param)
        {
            string sSpeed = param as String;
            if (String.IsNullOrWhiteSpace(sSpeed))
                return false;
            int nSpeed  = 0;
            if (Int32.TryParse(sSpeed, out nSpeed) == false)
                return false;
            if (nSpeed < 1)
                return false;
            return _SpaceShip != null;
        }

And of course when Speed changes we call CanExecute()  which will call CanStartAnimation() which will evaluate the property we just changed, although we are passing it as an argument.

Which brings up a good point. We could use the property Speed directly as we did the variable _SpaceShip, and much as it pains me, it probably does make more sense to use the property which, no matter what you call it, is a global variable, rather than passing it as an argument. Why? Because now everyplace that calls CanStartAnimation() has to pass the same argument that Button (or any other FrameworkElement that binds to the ICommand) does.

If there were no argument, then there could be no ambiguity – which stems from the fact in some cases it’s only being called to trigger the CanExecuteChanged event and in others it’s being called by ButtonBase to actually get the return value. If you change what Button passes, you have to remember to change everyplace that was passing the old value to trigger CanExecuteChanged.

If instead you change a property (global variable) that affects what CanExecute() will return you only have to remove the CanExecuteChanged-triggering-call to CanExecute() from the property setter for the one you are no longer using and add it to the new one. And you have to have a property (global variable) for the UI to bind to, so using it as an argument is redundant. Accordingly, in a ViewModel-centric world it makes sense for the UI (the perceived world) to affect the properties in the ViewModel (the real world) and for events in the UI to trigger actions against those properties. It took me awhile to come around to that point of view, ‘cause I do love my arguments – even when I call them parameters.

But still, I do need an example:

        #region Speed
        private string _Speed = "300";
        public string Speed
        {
            get { return this._Speed; }
            set
            {
                if (this._Speed != value)
                {
                    this._Speed = value;
                    this.NotifyPropertyChanged("Speed");
                    StartAnimationCommand.CanExecute(value);
                }
            }
        }
        #endregion

Here are  the  changes to RunAnimation().

        private void RunAnimation(int speed)
        {
            Point currentPosition = SpaceShipPosition;
            if (_sbFlight != null)
                _sbFlight.Stop();
            SpaceShipPosition = currentPosition;
            ...
            Double distance = ComputeDistance(SpaceShipPosition, new Point(targetX, targetY));
            _sbFlight.Duration = new Duration(TimeSpan.FromSeconds(distance / speed));
            ...
            _sbFlight.Completed += (sender, eventArgs) =>
            {
                if (Continuous && CanStartAnimation(Speed))
                    StartAnimation(Speed);
            };
            _sbFlight.Begin();
        }
        private Point SpaceShipPosition
        {
            get 
            { 
                if (_SpaceShip != null)
                    return new Point(Convert.ToDouble(_SpaceShip.GetValue(Canvas.LeftProperty)), Convert.ToDouble(_SpaceShip.GetValue(Canvas.TopProperty)));
                else
                    return new Point(0, 0);
            }
            set
            {
                if (_SpaceShip != null)
                {
                    _SpaceShip.SetValue(Canvas.LeftProperty, value.X);
                    _SpaceShip.SetValue(Canvas.TopProperty, value.Y);
                }
            }
        }

Notice I changed the StoryBoard to a member variable so I could stop the running animation before starting the next one. If you don’t, things get weird after you click start several times in a row quickly.

I decided to cut to the chase before laying out the step by step. If you’re interested the rest covers the back story up to the point where I first need to raise the CanExecuteChanged event.

The Basic Project

Select MainPageViewModel to be the DataContext for LayoutRoot (or UserControl).

image

image

Add an InvokeCommandAction to LayoutRoot to be triggered by the LayoutRoot.Loaded event:

image

The Command is SetWorldCanvas():

image

The parameter is LayoutRoot itself:

image

image

Bind the Button to StartAnimationCommand:

image

But the button appears disabled:

image

That’s because ButtonBase calls CanStartAnimation() before LayoutRoot.Loaded calls SetWorldCanvas(). The WorldCanvas property setter calls LoadWorldElements(), which sets _SpaceShip, which will cause CanExecute() to enable Button. But in spite of that the sequence of the calls at startup leaves Button disabled.

image

image

        private void LoadWorldElements()
        {
            _SpaceShip =
                (from child in _WorldCanvas.Children
                 let element = child as FrameworkElement
                 where element.Name == "spaceShip"
                 select element).FirstOrDefault();
            
            // Call DelegateCommand.CanExecute to raise the CanExecuteChangedEvent
            StartAnimationCommand.CanExecute(null);
        }

Add the line StartAnimationCommand.CanExecute() to LoadWorldElements() and voila.

image





Comments are closed.
Showing 1 Comment
Avatar  Michael Washington 7 years ago

Thanks for posting this. After you showed me this, I used this method to make the Upload button appear in this example:&lt;br&gt;&lt;br&gt;&lt;a href=&quot;http://www.codeproject.com/KB/silverlight/SimpleUploadVM.aspx&quot; rel=&quot;nofollow&quot;&gt;www.codeproject.com/KB/silverlight/SimpleUploadVM.aspx&lt;/a&gt;