6

I'm learning ICommands in WPF and I ran into a problem with some simple code. I have a Button with a Command. If I set the command parameter to a static value like this, CommandParameter="100", the value of the parameter argument in CanExecute is 100, however when I set the value of the command parameter via binding like this CommandParameter="{Binding}", the value of the parameter argument in CanExecute is null.

Here's my ICommand:

internal class MyCommand : ICommand
{
    public bool CanExecute(object parameter) //parameter is null
    {
        var datacontext = parameter as MyDataContext;
        if (datacontext == null)
            return false;

        return datacontext.IsChecked == true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        throw new NotImplementedException();
    }
}

Here's the XAML code. Notice that I'm setting the CommandParameter before setting the Command. I got that from here. Again, if I change the CommandParameter to somwthing like CommandParameter="100", the code acts as I would expect (i.e., the parameter is 100, not null).

<StackPanel Orientation="Vertical">
    <StackPanel.Resources>
        <cmd:MyCommand x:Key="kCmd" />
    </StackPanel.Resources>

    <CheckBox Content="Check this to enable button" IsChecked="{Binding IsChecked}" />
    <Button Content="Click" CommandParameter="{Binding}" 
            Command="{StaticResource kCmd}" />
</StackPanel>

Here's my MainWindow code-behind. Here, I'm setting the DataContext before calling InitializeComponent(). While debugging, I found that InitializeComponent() triggers a call to the ICommand's CanExecute(object).

public MainWindow()
{
    this.DataContext = new MyDataContext();
    InitializeComponent();
}

My MyDataContext class is pretty simple, so I left it out.

Community
  • 1
  • 1
user2023861
  • 8,030
  • 9
  • 57
  • 86
  • did you try setting `DataContext` after `InitializeComponent`? – dkozl Sep 29 '14 at 14:34
  • @dkozl, I tried that originally. The result is the same. – user2023861 Sep 29 '14 at 14:47
  • My best guess is that the timing is such that CanExecute is called before the DataContext is set. Are you able to test CanExecute after everything loads, such as when you click the Button? – Rachel Dec 02 '15 at 16:12
  • @Rachel, sorry but I don't have this code anymore. What I've been doing since I asked this question is to have the command be a member of MyDataContext. Then I reference that in the {Binding} rather than using a static reference. Finally, when I create the command in the MyDataContext constructor, I pass it a reference to MyDataContext. This obviates the need for using the CommandParameter member. – user2023861 Dec 02 '15 at 16:35
  • @user2023861 Ahh ok, sounds good. I didn't realize this question was so old when it got bumped to the front page yesterday :) – Rachel Dec 02 '15 at 19:53

2 Answers2

4

It is also a solution to force reevaluation of CanExecute from the on Loaded of a FrameworkElement event by raising the CanExecuteChanged event of the command. This approach can especially be used, when you are working with DataTemplates, and you are experiencing this problem.

Example:

 <DataTemplate x:Key="MyTemplate">
            <Grid Loaded="HandleLoaded">
 ...

And code behind:

 void HandleLoaded(object sender, RoutedEventArgs e)
 {
     var viewModel = this.DataContext as ViewModel;
     if (viewModel != null)
     {
         viewModel.DoItCommand.RaiseCanExecuteChanged();
     }
 }

Another possible solution, which may works, is to define the binding to the command itself as IsAsync=True. But this will result in some flickering. So it is maybe not the best choice.

Example: {Binding DoItCommand, IsAsync=True}

Christoph Meißner
  • 1,511
  • 2
  • 21
  • 40
1

Try raising the CanExecuteChanged-event of the MyCommand class after InitializeComponent() has finished. Probably, the CanExecute(object) of MyCommand is called to initialize the state of the button when rendering the first time, while the bindings have not all yet been initialized.

Wim.van.Gool
  • 1,290
  • 1
  • 10
  • 19
  • I tried that by giving the button a name and calling `this.btn.Command.CanExecute(this.DataContext)`. Two problems: (1) this seems to sidestep the problem as something is still wrong with the binding. (2) this doesn't seem like an MVVM solution. Giving the button a name and referencing it in the ViewModel introduces a tighter coupling between the ViewModel and the View. I want to avoid that. – user2023861 Sep 29 '14 at 14:59
  • Sorry, but I meant raising the MyCommand.CanExecuteChanged-event in your ViewModel - not explicitly (re)calling the Button's CanExecute()-method. The CanExecuteChanged-event will trigger the button to refresh it's state by calling CanExecute(object) on MyCommand, which by then will hopefully pass in the correct DataContext to your class. – Wim.van.Gool Sep 29 '14 at 15:06
  • 1
    Oops. I misread. I now call `(this.btn.Command as MyCommand).RaiseCanExecuteChanged();` in the MainWindow contructor. Once that's called, the parameter is what I'd expect. In the CanExecute(object), I hook in to the MydataContext.PropertyChanged handler and call `RaiseCanExecuteChanged()` appropriately. This all works now. Tell me, is this the preferred way to connect a implement an ICommand? The `RaiseCanExecuteChanged()` part seems like a lot. – user2023861 Sep 29 '14 at 15:31