Search code examples
c#wpfmvvmdatatemplatecommandbinding

How to raise a command from a DataTemplate and using command parameter?


I have multiple DataTemplates with a TextBox inside and they are selected with a DataTemplateSelector. The TextBoxes represent nodes within a tree. When I click on a TextBox at runtime, the command ZoomPlusButtonCommand should be fired. That works. But I want to know, which TextBox in my ObservableCollection has fired this command. Every node has a id, so I want this id as command parameter. How can I do that?

XAML:

<DataTemplate x:Key="RootNode" DataType="{x:Type vm:TreeNodeViewModel}">
    <TextBox Tag="{Binding NodeTag}" Text="{Binding Node.Description}" Margin="{Binding NodeMargin}">
        <TextBox.Style>
            <Style>
                <Setter Property="TextBox.Background" Value="#FFF6212D"/>
                ...
                <Style.Triggers>
                    <Trigger Property="TextBox.IsMouseOver" Value="True">
                        <Setter Property="TextBox.BorderBrush" Value="AliceBlue"/>
                    </Trigger>
                    ...
                </Style.Triggers>
            </Style>
        </TextBox.Style>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="PreviewMouseDown">
                <i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}, Path=DataContext.ZoomPlusButtonCommand}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </TextBox>
</DataTemplate>

MainViewModel:

public class MainViewModel : ViewModelBase
    {
        // list of all existing nodes in the tree editor
        public ObservableCollection<TreeNodeViewModel> Nodes { get; set; }

        // commands
        private ZoomPlusButtonCommand _zoomPlusButtonCommand;
        public ZoomPlusButtonCommand ZoomPlusButtonCommand
        {
            get
            {
                return _zoomPlusButtonCommand;
            }
            set
            {
                _zoomPlusButtonCommand = value;
            }
        }

        public MainViewModel()
        {
            Nodes = new ObservableCollection<TreeNodeViewModel>();
            Nodes.Add(new TreeNodeViewModel(4,0,"TEST1",new Thickness(0, 100, 0, 0)));
            Nodes.Add(new TreeNodeViewModel(56, 1, "TEST2", new Thickness(100, 150, 0, 0)));
            Nodes.Add(new TreeNodeViewModel(56, 2, "TEST3", new Thickness(200, 200, 0, 0)));

            // initialize commands
            _zoomPlusButtonCommand = new ZoomPlusButtonCommand(this);
        }
    }

ViewModel of the TextBox (Node):

public class TreeNodeViewModel : ViewModelBase
{
    private TreeNode _node;
    public TreeNode Node
    {
        get
        {
            return _node;
        }
        set
        {
            _node = value;
            RaisePropertyChanged(nameof(Node));
        }
    }

    private string _nodeTag;
    public string NodeTag
    {
        get
        {
            return _nodeTag;
        }
        set
        {
            _nodeTag = value;
            RaisePropertyChanged(nameof(NodeTag));
        }
    }

    private Thickness _nodeMargin;
    public Thickness NodeMargin
    {
        get
        {
            return _nodeMargin;
        }
        set
        {
            _nodeMargin = value;
            RaisePropertyChanged(nameof(NodeMargin));
        }
    }

    public TreeNodeViewModel(int id, int level, string tag, Thickness margin)
    {
        Node = new TreeNode();
        Node.Id = id;
        Node.Level = level;
        Node.Description = "Knoten";

        NodeTag = tag;
        NodeMargin = margin;
    }
}

Command:

public class ZoomPlusButtonCommand : ICommand
{
    private MainViewModel mainViewModel;

    public ZoomPlusButtonCommand(MainViewModel vm)
    {
        mainViewModel = vm;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        if (mainViewModel.ComboBoxZoomSelectedIndex - 1 >= 0)
        {
            mainViewModel.ComboBoxZoomSelectedIndex -= 1;
        }
    }

    public event EventHandler CanExecuteChanged
    {
        // only implemented to suppress the compiler warning that this event will never be used
        add { }
        remove { }
    }
}

Solution

  • assuming your ICommand implementation (the ZoomPlusButtonCommand class) can handle command parameters, you can alter the code like this:

    XAML

    <DataTemplate x:Key="RootNode" DataType="{x:Type vm:TreeNodeViewModel}">
        <TextBox Tag="{Binding NodeTag}" Text="{Binding Node.Description}" Margin="{Binding NodeMargin}">
            <TextBox.Style>
                <Style>
                    <Setter Property="TextBox.Background" Value="#FFF6212D"/>
                    ...
                    <Style.Triggers>
                        <Trigger Property="TextBox.IsMouseOver" Value="True">
                            <Setter Property="TextBox.BorderBrush" Value="AliceBlue"/>
                        </Trigger>
                        ...
                    </Style.Triggers>
                </Style>
            </TextBox.Style>
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="PreviewMouseDown">
                    <i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}, Path=DataContext.ZoomPlusButtonCommand}" CommandParameter="{Binding NodeTag}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </TextBox>
    </DataTemplate>
    

    MainViewModel

    public class MainViewModel : ViewModelBase
    {
        // list of all existing nodes in the tree editor
        public ObservableCollection<TreeNodeViewModel> Nodes { get; set; }
    
        // commands
        private ZoomPlusButtonCommand<string> _zoomPlusButtonCommand;
        public ZoomPlusButtonCommand<string> ZoomPlusButtonCommand
        {
            get
            {
                return _zoomPlusButtonCommand;
            }
            set
            {
                _zoomPlusButtonCommand = value;
            }
        }
    
        public MainViewModel()
        {
            Nodes = new ObservableCollection<TreeNodeViewModel>();
            Nodes.Add(new TreeNodeViewModel(4,0,"TEST1",new Thickness(0, 100, 0, 0)));
            Nodes.Add(new TreeNodeViewModel(56, 1, "TEST2", new Thickness(100, 150, 0, 0)));
            Nodes.Add(new TreeNodeViewModel(56, 2, "TEST3", new Thickness(200, 200, 0, 0)));
    
            // initialize commands
            _zoomPlusButtonCommand = new ZoomPlusButtonCommand<string>(this);
        }
    }