How to bind a Command to a ContextMenu from within an ItemTemplate?

淺唱寂寞╮ 提交于 2019-12-08 00:01:53

问题


I want to bind a certain command to a menuItem. The said menu item is part of a ContextMenu that is defined inside an ItemTemplate.

Right now, what I have compiles and runs, but the command is never called. In the past, I had used a similar pattern to hook commands to buttons defined in an ItemTemplate with success.

Anyone has any idea how I could accomplish this?

XAML:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Wpf_treeView" x:Name="window" x:Class="Wpf_treeView.MainWindow"
    Title="MainWindow" Height="350" Width="525">
  <Grid>
    <TreeView HorizontalAlignment="Left" Height="299" Margin="10,10,0,0" VerticalAlignment="Top" Width="228" ItemsSource="{Binding DataInfosView}" >
      <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
          <TextBlock DockPanel.Dock="Left" Text="{Binding InfoValue}" TextAlignment="Left" >
            <TextBlock.ContextMenu>
              <ContextMenu>
                <MenuItem Header="{Binding InfoValue}" IsEnabled="False"/>
                <MenuItem Header="Add child" Command="{Binding AddChildCmd, ElementName=window}"/>
              </ContextMenu>
            </TextBlock.ContextMenu>
          </TextBlock>
        </HierarchicalDataTemplate>
      </TreeView.ItemTemplate>
    </TreeView>
  </Grid>
</Window>

C#:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;

namespace Wpf_treeView
{
    public partial class MainWindow : Window
    {
        private static readonly Random rnd = new Random();
        private List<InfoData> m_InfoData = new List<InfoData>();

        public ListCollectionView DataInfosView { get; private set; }

        public static readonly DependencyProperty AddChildProperty =
            DependencyProperty.Register("AddChildCmd", 
                                        typeof(ICommand),
                                        typeof(MainWindow));

        public ICommand AddChildCmd
        {
            get { return (ICommand) GetValue(AddChildProperty); }
            set { SetValue(AddChildProperty, value); }
        }

        public MainWindow()
        {
            AddChildCmd = new RoutedCommand();
            CommandManager.RegisterClassCommandBinding(
                GetType(), 
                new CommandBinding(AddChildCmd, AddChild));

            m_InfoData.Add(new InfoData(4));
            m_InfoData.Add(new InfoData(1));
            m_InfoData.Add(new InfoData(5));
            m_InfoData[1].Children.Add(new InfoData(3));
            m_InfoData[1].Children[0].Children.Add(new InfoData(7));

            DataInfosView = new ListCollectionView(m_InfoData);
            DataContext = this;

            InitializeComponent();
        }

        private void AddChild(object sender, RoutedEventArgs e)
        {
            ExecutedRoutedEventArgs args = (ExecutedRoutedEventArgs)e;
            InfoData info = (InfoData)args.Parameter;
            info.Children.Add(new InfoData(rnd.Next(0, 11)));
        }
    }

    class InfoData : INotifyPropertyChanged
    {
        private int infoValue;

        public int InfoValue
        {
            get { return infoValue; }
            set
            {
                if (value != infoValue)
                {
                    infoValue = value;
                    OnPropertyChanged();
                }
            }
        }

        public List<InfoData> Children { get; private set; }

        public InfoData(int infoValue)
        {
            InfoValue = infoValue;
            Children = new List<InfoData>();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(
            [CallerMemberName] string propertyName = null)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

回答1:


Alright this should work:

<TextBlock DockPanel.Dock="Left"
                           Text="{Binding InfoValue}"
                           TextAlignment="Left"
                           Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
                    <TextBlock.ContextMenu>
                        <ContextMenu>
                            <MenuItem Header="{Binding InfoValue}"
                                      IsEnabled="False" />
                            <MenuItem Header="Add child"
                                      Command="{Binding Path=Parent.PlacementTarget.Tag.AddChildCmd, RelativeSource={RelativeSource Self}}" 
                                      CommandParameter="{Binding}" />
                        </ContextMenu>
                    </TextBlock.ContextMenu>
                </TextBlock>

The ContextMenu doesn't exist in the regular Visual Tree, so you aren't able to walk up the tree to get to the main data context. By using the Tag you are able to "pass in" the Main Window's data context to the context menu. For some more information on binding with context menu's see this answer as well as this one as they provide some good explanations as to what is going on



来源:https://stackoverflow.com/questions/36043659/how-to-bind-a-command-to-a-contextmenu-from-within-an-itemtemplate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!