Patch available: Automatically call RaiseCanExecuteChanged() when CommandParameter changes

Topics: Prism v1
Feb 17, 2009 at 12:13 PM
Hi,

We keep running up against an issue in WPF where changing the CommandParameter of an ICommandSource (e.g. Button or MenuItem) doesn't cause a call to CanExecute with the new parameter. Often we find that even if we never change the CommandParameter,  the Command gets set first so we only get a call to CanExecute with a null parameter. It's also very annoying if the CommandParameter comes from a binding.

I've written what I think is a fix in the form of an attached behavior that you can set on the Button, etc that will listen for changes to the CommandParameter and call RaiseCanExecuteChanged if the command is a DelegateCommand.

Usage is like:
    <Button Command="{Binding ...}" 
CommandParameter="{Binding ...}"
prismCommands:CommandParameterBehavior.IsCommandRequeriedOnChange="true" />
You can, of course, set this in an application wide style for the various controls you care about.

Would you be interested in including this upstream? I'll post the patch below.
N.B. To get it to work, I had to create an interface for the non-generic bits of DelegateCommand<T> (i.e. RaiseCanExecuteChanged).
Feb 17, 2009 at 12:20 PM
Edited Feb 17, 2009 at 12:24 PM
Edit:I've had to strip out all the xml comments, as they were messing up the formatting. If you want them, I have written some!

Firstly, I had to add an IDelegateCommand interface, and make DelegateCommand<T> inherit from that (not shown).
    public interface IDelegateCommand : ICommand, IActiveAware
{
void RaiseCanExecuteChanged();
}
The important class, though, is the one below with the attached behavior.

Note that I've tried to handle the Unloaded event sensibly to avoid the memory leak that would otherwise be caused by using PropertyDescriptor.AddValueChanged. I'm pretty sure that works, but I've not tested it in anything other than a trivial scenario.
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;

namespace Microsoft.Practices.Composite.Wpf.Commands
{
public static class CommandParameterBehavior
{
public static readonly DependencyProperty IsCommandRequeriedOnChangeProperty =
DependencyProperty.RegisterAttached("IsCommandRequeriedOnChange",
typeof(bool),
typeof(CommandParameterBehavior),
new UIPropertyMetadata(false, new PropertyChangedCallback(OnIsCommandRequeriedOnChangeChanged)));

public static bool GetIsCommandRequeriedOnChange(DependencyObject target)
{
return (bool)target.GetValue(IsCommandRequeriedOnChangeProperty);
}

public static void SetIsCommandRequeriedOnChange(DependencyObject target, bool value)
{
target.SetValue(IsCommandRequeriedOnChangeProperty, value);
}

private static void OnIsCommandRequeriedOnChangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ICommandSource))
return;

if (!(d is FrameworkElement || d is FrameworkContentElement))
return;

if ((bool)e.NewValue)
{
HookCommandParameterChanged(d);
}
else
{
UnhookCommandParameterChanged(d);
}

UpdateCommandState(d);
}

private static PropertyDescriptor GetCommandParameterPropertyDescriptor(object source)
{
return TypeDescriptor.GetProperties(source.GetType())["CommandParameter"];
}

private static void HookCommandParameterChanged(object source)
{
var propertyDescriptor = GetCommandParameterPropertyDescriptor(source);
propertyDescriptor.AddValueChanged(source, OnCommandParameterChanged);

// N.B. Using PropertyDescriptor.AddValueChanged will cause "source" to never be garbage collected,
// so we need to hook the Unloaded event and call RemoveValueChanged there.
HookUnloaded(source);
}

private static void UnhookCommandParameterChanged(object source)
{
var propertyDescriptor = GetCommandParameterPropertyDescriptor(source);
propertyDescriptor.RemoveValueChanged(source, OnCommandParameterChanged);

UnhookUnloaded(source);
}

private static void HookUnloaded(object source)
{
var fe = source as FrameworkElement;
if (fe != null)
{
fe.Unloaded += OnUnloaded;
}

var fce = source as FrameworkContentElement;
if (fce != null)
{
fce.Unloaded += OnUnloaded;
}
}

private static void UnhookUnloaded(object source)
{
var fe = source as FrameworkElement;
if (fe != null)
{
fe.Unloaded -= OnUnloaded;
}

var fce = source as FrameworkContentElement;
if (fce != null)
{
fce.Unloaded -= OnUnloaded;
}
}

static void OnUnloaded(object sender, RoutedEventArgs e)
{
UnhookCommandParameterChanged(sender);
}

static void OnCommandParameterChanged(object sender, EventArgs ea)
{
UpdateCommandState(sender);
}

private static void UpdateCommandState(object target)
{
var commandSource = target as ICommandSource;

if (commandSource == null)
return;

var rc = commandSource.Command as RoutedCommand;
if (rc != null)
{
CommandManager.InvalidateRequerySuggested();
}

var dc = commandSource.Command as IDelegateCommand;
if (dc != null)
{
dc.RaiseCanExecuteChanged();
}

}
}
}
Apr 8, 2010 at 7:08 PM

There's a relatively simple way to "fix" this problem with DelegateCommand, though it requires updating the DelegateCommand source and re-compiling the Microsoft.Practices.Composite.Presentation.dll.

1)  Download the Prism 1.2 source code and open the CompositeApplicationLibrary_Desktop.sln.  In here is a Composite.Presentation.Desktop project that contains the DelegateCommand source.

2) Under the public event EventHandler CanExecuteChanged, modify to read as follows:

    public event EventHandler CanExecuteChanged
    {
         add
         {
              WeakEventHandlerManager.AddWeakReferenceHandler( ref _canExecuteChangedHandlers, value, 2 );
              // add this line
              CommandManager.RequerySuggested += value;
         }
         remove
         {
              WeakEventHandlerManager.RemoveWeakReferenceHandler( _canExecuteChangedHandlers, value );
              // add this line
              CommandManager.RequerySuggested -= value;
         }
    }

3) Under protected virtual void OnCanExecuteChanged(), modify it as follows:

    protected virtual void OnCanExecuteChanged()
    {
         // add this line
         CommandManager.InvalidateRequerySuggested();
         WeakEventHandlerManager.CallWeakReferenceHandlers( this, _canExecuteChangedHandlers );
    }

4) Recompile the solution, then navigate to either the Debug or Release folder where the compiled DLLs live.  Copy the Microsoft.Practices.Composite.Presentation.dll and .pdb (if you wish) to where you references your external assemblies, and then recompile your application to pull the new versions.

After this, CanExecute should be fired every time the UI renders elements bound to the DelegateCommand in question.

Take care,
Joe

refereejoe at gmail

Apr 9, 2010 at 10:13 AM

Thanks Joe. I notice that most other ICommand implementations do effectively the same thing (i.e. forward CanExecuteChanged through to CommandManager.RequerySuggested).

I think if you're doing that, there's no need to leave the existing implementation (using _canExecuteChangedHandlers) in place, as it will be handled by the CommandManager. With both implementations in place the handlers will get called twice when you call OnCanExecuteChanged, won't they?

The downside of this approach, IIUC, is that the CanExecuteChanged handlers will get called (more or less) every time the UI renders any element bound to any command, though, won't they? Some of our CanExecute methods are a bit slow (very bad design ,I know. I promise I'll fix them soon!) so calling them more often than necessary could be "unfortunate".

BTW: As it turns out, my implementation above wasn't good enough, as the Loaded & Unloaded events don't come in pairs (the Loaded event is often fired more than once), so you can end up with multiple event subscriptions (e.g. for MenuItems, IIRC). If anyone is interested I can post an updated version. Given the amount of code necessary to do it my way, I'm tempted to switch to the CommandManager.InvalidateRequerySuggested solution. :-)

Oct 8, 2013 at 1:12 PM
The sad thing here is to change the PRISM code ourselves... It would be nice to have a patch or version with CommandManager...
Nov 21, 2013 at 4:20 PM
Would love to see this released as a patch so I don't have to keep rewriting this bit of code in all my projects.
Jan 13 at 6:26 PM
Edited Jan 13 at 6:52 PM
Running into the same problem. I'm not sure I understand this architecture. I have some generic commands (Open, Close, Properties) associated with MenuItem commands in a ContextMenu. I'm using the CompositeCommand pattern of PRISM to implement. So that I don't have to create 1,000 different ContextMenus on the line items I show in my grid, I just create a single ContextMenu and use the DataContext of each of the line items to provide a context for the ContextMenu. However, when I change the CommandParameter on the menu item to be the new DataContext of the current line item, the CanExecuteChanged event is not invoked. What am I missing here? How do you construct a complex, multi-module application if you need access to the DelegateCommand of every command you create?
Jan 13 at 6:33 PM
I think one should think carefully before choose PRISM as their framework.
I'm stuck with it, and if I was in the project from the beginning I had chose Calibur.Micro. Prism not even get updates. Caliburn is always lauching new versions.
Jan 14 at 6:45 PM
Hello DRairey1,

I am afraid I don't completely understand the scenario where you are experiencing this issue. The items list that are shown in the grid, would they be the names of different Views to navigate to?
It would be helpful if you describe us the behaviour you are trying to accomplish. Also, you could send us a code sample of your app so we could reproduce and debug the issue to provide you better support.

Please notice that this discussion thread is related to Prism v1, which is not available anymore (The current version since February 2012 is Prism v4.1). Therefore, I would suggest you to open a new discussion in order to mantain and motivate a clean and ordered forum, unless your issue directly relates to the one discussed above.

Regards,
Gabriel Ostrowsky.
https://blogs.southworks.net/gostrowsky
Jan 15 at 3:54 PM
GOstrowsky wrote:
I am afraid I don't completely understand the scenario where you are experiencing this issue. The items list that are shown in the grid, would they be the names of different Views to navigate to?
It would be helpful if you describe us the behaviour you are trying to accomplish. Also, you could send us a code sample of your app so we could reproduce and debug the issue to provide you better support.
I actually hit this again the other day (when I was working in a new codebase without my workaround). My scenario was this:
  • There is a TextBox on a form
  • The TextBox has a custom ContextMenu, with an extra MenuItem of the form "Copy Special"
  • The "Copy Special" command must be disabled unless the text displayed in the TextBox (not just the bound value) is "valid"
My first attempt was to bind the Command property of the MenuItem to a DelegateCommand exposed by the VM, and bind the CommandParameter to the Text property of the TextBox. This does not work as the CanExecute handler doesn't get called by WPF when the CommandParameter changes; the MenuItem stays in the same state forever, depending on the validity of the text the first time the context menu is opened.

This wasn't too hard to work-around, as I only had one property to worry about, but imagine I was using the same Command & CommandParameter binding in a DataTemplate.
Please notice that this discussion thread is related to Prism v1, which is not available anymore (The current version since February 2012 is Prism v4.1). Therefore, I would suggest you to open a new discussion in order to mantain and motivate a clean and ordered forum, unless your issue directly relates to the one discussed above.
AFAICT there would be no difference in behaviour between Prism v1 & v4.1 here (although I've not tested). I can't see anything in the code that has changed. Starting a new thread seems a bit pointless, as that would actually hide the fact that I posted a workaround.
Jan 15 at 4:03 PM
Edited Jan 15 at 4:58 PM
GOstrowsky, thanks for your reply.

The issue is exactly the one posted by SWYTHAN. I've got a relatively large, composite application that uses PRISM extensively, so it will take me a little time to remove just the ContextMenu part for a reproduction. Since my post Monday, I've reworked the logic to use a View Model to generate the context menu but the original problem remains. The code that creates the context menu looks like this:
                    <Grid>
                        <Grid.ContextMenu>
                            <ContextMenu ItemsSource="{Binding ListViewMenuItems}"/>
                        </Grid.ContextMenu>
                    </Grid>
The DataTemplate used to create the menu items looks like this:
        <Style.Resources>
            <DataTemplate DataType="{x:Type local:MenuItemViewModel}">
                <MenuItem Header="{Binding Header}"
                          Icon="{Binding Icon}"
                          CommandParameter="{Binding CommandParameter}"
                          Command="{Binding Command}"/>
            </DataTemplate>
        </Style.Resources>
and, finally, the View Model for the menu items looks like this:
    public class MenuItemViewModel : NotificationObject
    {
        private Object headerField;
        private Object iconField;
        private ICommand commandField;
        private Object commandParameterField;

        public Object Header
        {
            get
            {
                return this.headerField;
            }
            set
            {
                if (this.headerField != value)
                {
                    this.headerField = value;
                    this.RaisePropertyChanged("Header");
                }
            }
        }

        public Object Icon
        {
            get
            {
                return this.iconField;
            }
            set
            {
                if (this.iconField != value)
                {
                    this.iconField = value;
                    this.RaisePropertyChanged("Icon");
                }
            }
        }

        public ICommand Command
        {
            get
            {
                return this.commandField;
            }
            set
            {
                if (this.commandField != value)
                {
                    this.commandField = value;
                    this.RaisePropertyChanged("Command");
                }
            }
        }

        public Object CommandParameter
        {
            get
            {
                return this.commandParameterField;
            }
            set
            {
                if (this.commandParameterField != value)
                {
                    this.commandParameterField = value;
                    this.RaisePropertyChanged("CommandParameter");
                }
            }
        }
    }
And the implementation of the MenuItemViewModel property looks like like this:
        public override ObservableCollection<Object> ListViewMenuItems
        {
            get
            {
                var list = new ObservableCollection<Object>();
                list.Add(new MenuItemViewModel
                {
                    Header = "Delete",
                    Command = GlobalCommands.Delete,
                    CommandParameter = this
                });
                return list;
            }
        }
Note that the delete command is a CompositeCommand and the CommandParameter is set to the ViewModel that I want to be passed to the command handler to determine if it can be deleted. The CommandParameter will always be set and will always point to a valid view model.

The first time, and then randomly after the first time, the DelegateCommand CanExecute handler gets a null when it is called:
            this.deleteCommandField = new DelegateCommand<IExplorerItem>(this.DeleteItem, this.CanDeleteItem);
            GlobalCommands.Delete.RegisterCommand(this.deleteCommandField);
        private Boolean CanDeleteItem(IExplorerItem explorerItem)
        {
            CustomerNode customerNode = explorerItem as CustomerNode;
            if (customerNode != null)
            {
                return customerNode.Children.Count == 0;
            }
            return false;
        }
Developer
Jan 17 at 5:04 PM
Hi everybody,

The main issue behind the problem being discussed here is that the command parameter is not part of the Command implementation itself. As it can be seen, when you usually set the CommandParameter binding, you are doing it in the Button (or the corresponding UI element) not in the command. Also, the command does not have access to the parameter, it only recieves it when the Execute and CanExecute parameters are invoked. Therefore, the command is not aware of when the parameter is changed.

To check the state of the command the corresponding elements need to re-query the command by executing the CanExecute method with the command parameter. This is done automatically when the CanExecuteChanged event is raised, but as mentioned above, the command is not aware of changes in the command parameter and cannot raise the event by its own. It needs for something else to raise it.

Raising the CanExecutedChanged event in a DelegateCommand can be done simply by invoking the RaiseCanExecuteChanged method. This method should be called manually each time the command parameter is changed. If the command parameter is changed in the view model, you can invoke this method after changing it. In case you want to update the state of a CompositeCommand the aforementioned method is not available, but invoking it in any of its child commands should raise the event in the composite one. If the command parameter is changed by sources outside the view model or by elements of the view, you could react to those events by using interactions.

For example, in the following code snippet the command parameter is the text of the TextBox. Each time the text is changed the RaiseCanExecuteChanged is invoked.
    <StackPanel>
        <TextBox Name="TextBox">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="TextChanged" SourceName="TextBox">
                    <ie:CallMethodAction MethodName="RaiseCanExecuteChanged" TargetObject="{Binding Command, ElementName=GreenButton}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
            
        </TextBox>
        <TextBlock TextWrapping="Wrap">This button can be executed only when the text above is equal to 'Green'.</TextBlock>
        <Button Name="GreenButton" Command="{Binding GreenCommand}" CommandParameter="{Binding Text, ElementName=TextBox}" >
            Green Button
        </Button>
    </StackPanel>
Something I want to point out is that, as far as I know, there is currently no active work item in the issue tracker section regarding this. Usually, the team takes the issues / suggestions from there, chose the ones with the most votes and analyze them to see if it make sense to address those work items in future releases of the library.

If you are interesting in changing the current behavior of the DelegateCommand / CompositeCommand classes, please create a work item in the issue tracker section explaining your scenario, and the possible changes you might want to add to the library.

I hope this helps,

Damian Cherubini
http://blogs.southworks.net/dcherubini
Jan 17 at 9:01 PM
Edited Jan 19 at 12:40 AM
Damian,

Thanks for a description of the root of the problem. I've used .NET Reflector to dig a little further. This root of the issue is the MenuItem (or Button) control implementation. The 'CanExecute' on the ICommand interface is only called when the Command is set. In spite of other posts, I've not found that the order of the Command and CommandParameter in the XAML makes a difference (if it does, it's a random difference at best).

The problem with your suggestion is that CompositeCommands are built for composite applications. The menu item that invokes the CompositeCommand (e.g. 'Save All') may not reside in the module where the 'Save' DelegateCommand is implemented and where the 'RaiseCanExecuteChanged()' method is found. So how do I force the 'Save All' command to re-evaluate after the initial binding if I don't have access to the DelegateCommand (because it's in a dynamically loaded module)?
Jan 20 at 5:32 PM
Hi DRAirey1,

One possible approach you could achieve is by using EventAggregator for communication between CompositeCommand and every corresponding ViewModel´s DelegateCommand. This way, you would publish a Save event for example, from the CompositeCommand, and each of the related ViewModels would subscribe to it in order to handle the event.

Therefore, each eventHandler would call the RaiseCanExecuteChanged() and the condition would get reevaluated:
public class ViewModel()
{
     ...
     SaveEvent saveEvent = eventAggregator.GetEvent<SaveEvent>();
     saveEvent.subscribe(SaveEventHandler, ThreadOption.UIThread);
}

public void SaveEventHandler()
{
      SaveCommand.RaiseCanExecuteChanged();
}
I hope this helps,
Regards.

Gabriel Ostrowsky
https://blogs.southworks.net/gostrowsky