A place for spare thoughts

10/08/2011

WPF: Using commands with CommandParameter without CommandManager

Filed under: wpf — Ivan Danilov @ 15:33

Almost always business commands are implemented without RoutedCommand usage. There’re good reasons: business commands are not ‘application-wide’ in general. It is precise action that should be done when user clicked a button, tapped a list or something alike. So, common approach is to implement ICommand interface and CommandManager for CanExecuteChanged event implementation like below:

public event EventHandler CanExecuteChanged
{
    add { CommandManager.RequerySuggested += value; }
    remove { CommandManager.RequerySuggested -= value; }
}

Well, this approach has its downsides.

First, CommandManager knows only about the UI. If you have some external source of information, e.g. direct network connection or something alike, – you should be able to tell command ‘Your state changed! Raise your event!’ manually. It can be done with CommandManager.InvalidateRequerySuggested() method, but with updating of all commands in your app! Which sometimes is somewhat slower than required.

CommandManager is leaky in .NET 3.5. In .NET 4.0 some problems were fixed, but still there’s some criticism about it as a design concept. One could introduce memory leak and don’t notice it at all. Especially in the presence of this problem in data binding. I have also some examples that led to long-and-painful debug sessions but unfortunately can’t disclose them for legal reasons. And it would be too much work to rewrite some part just to make a demo…

At last I’m very suspicious about any static global thing that many pieces in the application subscribed to without strict control and review. WeakReference is harder to understand than normal, strong reference; delegate is harder than reference and WeakDelegate is the hardest of all.

But if you implement CanExecuteChanged event in other way – you have a problem. Many classes relies on the fact that you’re using CommandManager. It was stated in the criticism referenced above btw. You want an example? No problem. Lets take probably the most used class in WPF: Button. It assumes that if Binding changed – CommandManager would re-query commands’ statuses without additional actions. And so, if you have CommandParameter bound to some source and command not subscribed to CommandManager – this command will not be re-queried. It seems very natural for me to assume command status could have changed if parameter is changed. Even if parameter is not bound. But command relies on CommandManager. WTF?!

Well, there’s a solution for the problem. Button and other inheritors of ButtonBase doesn’t override property metadata for CommandParameter dependency property. It is our chance… 🙂

Here I present my implementation of the ICommand interface implementation:

public class GenericRelayCommand<T> : GenericRelayCommand
{
    public GenericRelayCommand(Action<T> execute)
        : base(o => execute((T) o))
    {
    }

    public GenericRelayCommand(Action<T> execute, Func<T, bool> canExecute)
        : base(o => execute((T) o), o => canExecute((T) o))
    {
    }
}

public class GenericRelayCommand : ICommand
{
    static GenericRelayCommand()
    {
        ButtonBase.CommandParameterProperty.OverrideMetadata(typeof(Button), new FrameworkPropertyMetadata(null, CommandParameterChangedCallback));
        ButtonBase.CommandParameterProperty.OverrideMetadata(typeof(DataGridColumnHeader), new FrameworkPropertyMetadata(null, CommandParameterChangedCallback));
        ButtonBase.CommandParameterProperty.OverrideMetadata(typeof(DataGridRowHeader), new FrameworkPropertyMetadata(null, CommandParameterChangedCallback));
        ButtonBase.CommandParameterProperty.OverrideMetadata(typeof(CalendarButton), new FrameworkPropertyMetadata(null, CommandParameterChangedCallback));
        ButtonBase.CommandParameterProperty.OverrideMetadata(typeof(CalendarDayButton), new FrameworkPropertyMetadata(null, CommandParameterChangedCallback));
    }

    private static void CommandParameterChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var command = d.GetValue(ButtonBase.CommandProperty) as GenericRelayCommand;
        if (command != null)
            command.RaiseCanExecuteChanged();
    }

    private readonly Func<object, bool> _canExecute;
    private readonly Action<object> _execute;

    public GenericRelayCommand(Action<object> execute)
        : this(execute, null)
    {
    }

    public GenericRelayCommand(Action<object> execute, Func<object, bool> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null || _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    public event EventHandler CanExecuteChanged = delegate { };

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged(this, EventArgs.Empty);
    }
}

Some generic syntax sugar is for free. Most interesting lines are in static constructor (lines 18-22) and in changing handler method (lines 25-30).

Note that despite code works, this code is not a recommended approach. Moreover it is recommended to avoid this practice:

Calls to OverrideMetadata must be performed within the static constructors of the type that provides itself as the forType parameter of OverrideMetadata. If you attempt to change metadata once instances of the owner type exist, this will not raise exceptions, but will result in inconsistent behaviors in the property system. Also, metadata can only be overridden once per type. Subsequent attempts to override metadata on the same type will raise an exception.

Nevertheless I’ve decided to stick with this approach now because in .NET 3.5 and .NET 4.0 these properties are not overridden and I hope it will be so in next version. Or (even better) this problem will be fixed.

Maybe it would be better (at least safer for sure) to subclass Button and use my own button with overridden metadata safely… but my feelings are bad about inventing the wheel. Even if it is subclassing the wheel.

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

Create a free website or blog at WordPress.com.