windows 10)Problem description:
I have a TestCommand as below:
public class TestCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
}
}
and then I use it in a xaml file:
<Button Content="Test" Command="{StaticResource testCommand}" CommandParameter="abcdef"/>
Actual behavior:
At first time the CanExecute method is called, the parameter's value is null。
If I change the XAML property's order as below, the parameter's value is abcdef (that's ok):
<Button Content="Test" CommandParameter="abcdef" Command="{StaticResource testCommand}"/>
Expected behavior:
The parameter's value is not null at each time, regardless of the XAML order
Minimal repro:
I'm having uncomfortable flashbacks to when I first learned of this ☹️
I'm not sure anything can be done about this. XAML properties are assigned sequentially, and I doubt that's negotiable. This would probably require some kind of hack in the XAML reader.
@brandonhood, or the button could just defer the call between BeginInit and EndInit?
In my opinion, the XAML equals to the codes:
var button = new Button
{
Content = "Test",
Command = ...
CommandParameter = ...
};
and the CanExecute method will be called by some methods such as Load ...
The .NET framework version of the xaml parser honours the ISupportInitialize interface (which is implemented by FrameworkElement), so its actually more like this:
var button = new Button();
((ISupportInitialize)button).BeginInit();
button.Content = "Test";
button.Command = ...
button.CommandParameter = ...
((ISupportInitialize)button).EndInit();
@brian-reichle but that would have to be done for every control to which you can attach a command, otherwise you have only fixed it for an individual case and all other controls still behave differently
[edit] the implementation proposed in #1179 avoids this issue by implementing it in a central helper method PresentationFramework.dll!MS.Internal.Commands.CommandHelpers.CanExecuteCommandSource(System.Windows.Input.ICommandSource commandSource) so I retract that concern.
I agree that this behavior is incorrect. I think something like this should be pretty straightforward to do. FrameworkElement has an OnInitialized override that Button should be able to invoke the command from. However, we don't know who is currently depending on the current behavior, so this is something that we can look into fixing after the initial release of .NET Core 3.
This issue been around for a while. The easiest fix for now is to change the order so that CommandParameter appears before Command but order should not matter.
I would like to propose a solution for the Button and MenuItem cases mentioned in the linked Stack Overflow question above.
Looking at stack trace that leads to the bug for the Button case
StackTrace leading to ICommand.CanExecute Bug.txt
we see that the error occurs in the
PresentationFramework.dll!MS.Internal.Commands.CommandHelpers.CanExecuteCommandSource(System.Windows.Input.ICommandSource commandSource)
method
The idea is to delay the execution of this method until the WPF elements are initialized. See the above linked commit 0c6b967 for the details.
The attached test project can be used to verify the proposed solution.
@vatsan-madhavan not sure whats left to discuss, I think everyone agreed that its a bug. My concern about having to duplicate the code has been addressed by placing the code in a central helper method so it doesn't have to be placed in every site that has commands, I don't think the change is unreasonable.
As far as behavioral changes is happening, this just suppresses a call with incomplete arguments, the next query of CanExecute will have the correct parameters, most callers are going to ignore the first incomplete call anyways.
I suggest to reconsider the PR for post 3.0 RTM when behavioral changes are allowed again.
I think a better approach would be to first fix the behaviour of CommandParameter so that CanExecute is called whenever the parameter is changed. That should also solve the problems described here. Even if CanExecute would be called twice if parameters are in "wrong" order the end result would be the expected behaviour.
The behavior was fixed in silverlight but did never make it back to wpf (for backwards compatibility reasons i guess) so would really like to see it fixed for 3.1 so that CommanManager needs not be used for simple commands
I agree with @Daniel-Svensson.
There is no guarantee a caller will use the ISupportInitialize anyway, so it's not really solving the real problem. As a consumer, you should not only check whether the argument is null but also whether it's the type you expect.
@vflame I believe the XAML standard specifies the order actually matters.
@miloush this issue is about XAML attribute order and the WPF implementation of the XAML loader does use ISupportInitialize (the interface is implemented on FrameworkElement base class and will always be used when loading from XAML). So I don't think your argument against using ISupportInitialize is valid.
That said I don't mind the alternative solution suggested by @Daniel-Svensson
@weltkante XAML loader is not the only way how an element can be constructed. The problem with moving to ISupportInitialize is that then you can file similar bug for everything that does not use that interface, rather than fixing it once at the receiving side.
Either way, I think the compatibility favours Daniel's solution (I actually consider it a bug that changing command parameter does not update the CanExecute state). You will still get the first call with null parameter, and the handler is expected to be called multiple times (not deterministically), so adding more calls shouldn't be breaking.
This is truely ridiculous.
As @Daniel-Svensson said, the actual issue here is that ICommand.CanExecute is not reevaluated when the value bound to CommandParameter changes. Doing so would obviously be the correct behavior, since the command parameter is passed to CanExecute, so everyone expects this behavior intuitively.
This causes issues beyond this one, for example when using Button in ItemsControl-like containers that use virtualization internally (which is currently impossible, since the enabled/disabled state of the button may refer to an entirely different item than the one the user sees).
This affects WPF for .NET Framework as well, not only the new .NET Core variant.
Hopefully this link will help
'https://stackoverflow.com/questions/335849/wpf-commandparameter-is-null-first-time-canexecute-is-called'
Thanks "Ed Downs"
public static class ButtonHelper
{
public static DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached(
"CommandParameter",
typeof(object),
typeof(ButtonHelper),
new PropertyMetadata(CommandParameter_Changed));
private static void CommandParameter_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = d as ButtonBase;
if (target == null)
return;
target.CommandParameter = e.NewValue;
var temp = target.Command;
// Have to set it to null first or CanExecute won't be called.
target.Command = null;
target.Command = temp;
}
public static object GetCommandParameter(ButtonBase target)
{
return target.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(ButtonBase target, object value)
{
target.SetValue(CommandParameterProperty, value);
}
}
Can we have a fix for this broken behavior?
I remember running into this bug for over 8 years now and having to use a workaround. Putting Command before CommandParameter (due to alphabetic ordering) gives me headaches every time.
There should be a switch or something to apply this bug fix. Surely there will be workarounds from the last 14 years that will break.
Most helpful comment
I think a better approach would be to first fix the behaviour of CommandParameter so that CanExecute is called whenever the parameter is changed. That should also solve the problems described here. Even if CanExecute would be called twice if parameters are in "wrong" order the end result would be the expected behaviour.
The behavior was fixed in silverlight but did never make it back to wpf (for backwards compatibility reasons i guess) so would really like to see it fixed for 3.1 so that CommanManager needs not be used for simple commands