Search code examples
c#wpfmvvmicommand

ICommand implemtation fails silently


Update

The button I was trying to use, is inside a <DataTemplate>, which apparently caused the issue. Once I tried the code on a button outside the the <ItemsControl> area, it works. Can anyone tell me, why it does not work in a repeated button like <ItemsControl> and <DataTemplate>?

I am trying to implement an MVVM communication pattern, based on an article from TutorialsPoints.com. I have modified the code slightly, but over all it is still very similar to the code in the article. What I want to do is, to write a line in the console once a button is clicked.

With my implementation (see code below) nothing happens when I click the button. I have also tried adding a break point in the OnClick() function to see if that is run, this is not the case. However a break point in the constructor of MyICommand() shows that the class is actually initialized. What am I doing wrong then?

The Button

<Button Content="Do stuff!"
        Command="{Binding FakeCommand}"
        Cursor="Hand"
        Background="Red" 
        Foreground="White" 
        BorderThickness="0" 
        Padding="10 0 10 0"  />

The View Model

public class AgreementViewModel : INotifyPropertyChanged
{
    public MyICommand FakeCommand { get; set; }

    public AgreementViewModel ()
    {
        LoadAgreements();
        FakeCommand = new MyICommand(OnClick, CanClick);
        FakeCommand.RaiseCanExecuteChanged();
    }

    private void OnClick()
    {
        Console.WriteLine("Something was clicked...");
    }

    private bool CanClick()
    {
        return true;
    }
}

The Implementation of ICommand

public class MyICommand : ICommand
{
    Action _TargetExecuteMethod;
    Func<bool> _TargetCanExecuteMethod;

    public event EventHandler CanExecuteChanged = delegate {};

    public MyICommand(Action executeMethod)
    {
        _TargetExecuteMethod = executeMethod;
    }

    public MyICommand(Action executeMethod, Func<bool> canExecuteMethod)
    {
        _TargetExecuteMethod = executeMethod;
        _TargetCanExecuteMethod = canExecuteMethod;
    }

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

    bool ICommand.CanExecute(object parameter)
    {
        if (_TargetCanExecuteMethod != null)
        {
            return _TargetCanExecuteMethod();
        }

        if (_TargetExecuteMethod != null)
        {
            return true;
        }

        return false;
    }

    void ICommand.Execute(object parameter)
    {
        _TargetExecuteMethod?.Invoke();
    }
}

Solution

  • If you have an ItemsControl (as you mention in the updated version) then the DataContext for each instantiation of the DataTemplate will each item of the source collection used in the ItemsSource. To bind to a command in the parent view model you could use ElementName to get to the ItemsControl

        <ItemsControl ItemsSource="{Binding Data}" x:Name="root">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button Content="Do stuff!"
                        Command="{Binding DataContext.FakeCommand,  ElementName=root}"
                        Cursor="Hand"
                        Background="Red" 
                        Foreground="White" 
                        BorderThickness="0" 
                        Padding="10 0 10 0"  />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    

    An alternative if you don't want to use names would be to use RelativeSource to get to the items control:

    Command="{Binding DataContext.FakeCommand,  RelativeSource={RelativeSource AncestorType=ItemsControl}}"
    

    Note that in both cases the data context will be the ItemsControl, so you need to do DataContext.FakeCommand, DataContext refers here to the data context of ItemsControl

    You might also need the item the command was invoked for since it can be invoked for any item in the source collection. To do that you can add a a CommandParameter={Binding}, and the parameter passed the command will be the item (your implementation does not pass the parameter to the delegate, but it could)