0

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();
    }
}
Jakob Busk Sørensen
  • 5,599
  • 7
  • 44
  • 96
  • Do you tie the ViewModel to the View ? By setting the `DataContext` of the view ? – Titian Cernicova-Dragomir Jan 09 '18 at 12:56
  • @Noceo Where do you expect to see your message? If you write this line to a file, does it work? – chester89 Jan 09 '18 at 12:57
  • @chester89 I would expect to see it in the output window (it is merely for testing). – Jakob Busk Sørensen Jan 09 '18 at 12:58
  • @TitianCernicova-Dragomir the view model is tied using the auto hook-up from earlier in the tutorial. This seems to works fine, since everything from the view model is shown in my view. – Jakob Busk Sørensen Jan 09 '18 at 12:59
  • Using your code if I hook it up manually it works – Titian Cernicova-Dragomir Jan 09 '18 at 13:00
  • 1
    Try `Debug.WriteLine` instead, or just set a breakpoint. I'd also recommend to write `CanExecuteChanged?.Invoke(this, EventArgs.Empty);` and get rid of the `delegate {}` initialization. – Clemens Jan 09 '18 at 13:00
  • @Clemens I have tried with a break point, but it is never reached. Also tried with Debug.WriteLine, also no result. – Jakob Busk Sørensen Jan 09 '18 at 13:02
  • See the update. The missing action was caused by using the command in a `DataTemplate` – Jakob Busk Sørensen Jan 09 '18 at 13:14
  • 1
    It sounds more like it was caused by `ItemsControl`, which will introduce a new `DataContext` for each item. – grek40 Jan 09 '18 at 13:16
  • @grek40 that was indeed the case. Never thought of it that way. I am still lost on how to have a button with a command in each row for an `ItemsControl`, but maybe I will get to that at some point... – Jakob Busk Sørensen Jan 09 '18 at 13:21
  • 1
    This https://stackoverflow.com/questions/3404707/access-parent-datacontext-from-datatemplate is an answer to the magic google search "WPF ItemsControl Binding Parent" ;) Note the linked question might be confusing, but the accepted answer is quite clear – grek40 Jan 09 '18 at 13:22
  • Side note, calling FakeCommand.RaiseCanExecuteChanged(); in the constructor is useless, as databinding is happening long after the vm is instantiated. –  Jan 09 '18 at 17:26

1 Answers1

2

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)

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357