Как добавить ContextMenu в WPF DataGridColumn в MVVM?

У меня есть сложная проблема, связанная с ContextMenu в WPF DataGridColumn. Я не знаю, сталкивался ли кто-то с этой проблемой, но я буду очень признателен, если кто-то может мне помочь!

Начнем с моих занятий

public class Person
{
    public string Type { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public int Age { get; set; }
}

public class Menu
{
    public string Name { get; set; }
    public int Code { get; set; }
    public ObservableCollection<Menu> listMenu { get; set; }
}

Теперь моя ViewModel

 public class MyViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Person> DataPersons = new ObservableCollection<Person>();
    private ObservableCollection<Menu> DataMenu = new ObservableCollection<Menu>();

    public ObservableCollection<Person> listDataPersons { get; set; }
    public ObservableCollection<Menu> listDataMenu { get; set; }

    public MyViewModel()
    {
        //initialization
        InitData();
    }

    private void InitData()
    {
        listDataPersons = new ObservableCollection<Person>();
        listDataMenu = new ObservableCollection<Menu>();

        DataPersons.Add(new Person() { Type = "Friend", Name = "Doe", Surname = "John", Age = 42});
        DataPersons.Add(new Person() { Type = "Friend", Name = "Smith", Surname = "Jack", Age = 42});

        DataMenu.Add(new Menu() { Name = "Principal", Code = 1});
        DataMenu.Add(new Menu() { Name = "Secondary", Code = 2});
        DataMenu.Add(new Menu() { Name = "Associated", Code = 3});

        DataMenu[2].listMenu = new ObservableCollection<Menu>();
        DataMenu[2].listMenu.Add(new Menu() { Name = "Associated 1", Code = 31 });

        listDataPersons = DataPersons;
        listDataMenu = DataMenu;
    }}

Вот мой вид и его код

<DataGrid ItemsSource="{Binding listDataPersons}" AutoGenerateColumns="False">
    <DataGrid.ContextMenu>
        <ContextMenu ItemsSource="{Binding listDataMenu}"/>
    </DataGrid.ContextMenu>
    <DataGrid.Columns>                
        <DataGridTemplateColumn IsReadOnly="True" Width="*">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}" Width="80" >
                    <TextBlock.ContextMenu>
                        <ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, Path=DataContext.listDataMenu}"/>
                    </TextBlock.ContextMenu>
                    </TextBlock>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

код позади

public partial class MyView : UserControl
{
    public MyView()
    {
        InitializeComponent();

        this.DataContext = new MyViewModel();
    }
}

В этом примере я хотел иметь динамическое ContextMenu в DataGridColumn. Сначала я поставил ContextMenu во все DataGrid, и все работает нормально. Но в моем случае мне нужно ContextMenu только при щелчке правой кнопкой мыши в ячейках, а не во всем DataGrid. Поэтому я попытался отредактировать DataTemplate DataGridColumn с помощью TextBox, у которого есть ContextMenu. К сожалению, когда я щелкаю правой кнопкой мыши в TextBox, ItemsSource ContextMenu кажутся пустыми. Однако, когда я щелкаю правой кнопкой мыши за пределами TextBox в DataGrid, ContextMenu DataGrid привязывается правильно.

Я думал, что это может быть проблема DataContext, потому что ContextMenu и DataGrid не имеют одинакового визуального дерева, поэтому я добавил RelativeSource в привязку ItemsSource ContextMenu, но безрезультатно!!!

Есть идеи?


person Hass_NB    schedule 17.01.2011    source источник


Ответы (4)


Прежде всего, поблагодарите Рика за то, что он нашел время помочь мне в этом вопросе.

Я разместил проблему на форуме msdn, и у меня был ответ на ее решение.

<TextBlock Text="{Binding Name}" Width="80" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGrid}}, Path=DataContext}">
<TextBlock.ContextMenu>                                                                                                
    <ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.Tag.listDataMenu}" ItemContainerStyle="{StaticResource ContextMenuItemStyle}"/>
</TextBlock.ContextMenu>                                

The think to do is passing the UserControl's DataContext to theContextMenu through the TextBox's Tag

Для тех, кто хочет, чтобы он правильно работал с моим кодом, вам нужно определить UserControlRessoucre как:

<UserControl.Resources>
    <HierarchicalDataTemplate DataType="{x:Type cmd:Menu}" ItemsSource="{Binding listMenu}">
        <TextBlock Text="{Binding Path=Name}"/>            
    </HierarchicalDataTemplate>

    <Style x:Key="ContextMenuItemStyle">
        <Setter Property="MenuItem.ItemsSource" Value="{Binding Path=listMenu}"/>             
    </Style>
</UserControl.Resources>

это ссылка на форум msdn исходного ответа: -->здесь‹--

большое спасибо Шелдону Сяо за этот ответ

person Hass_NB    schedule 20.01.2011

Ты на правильном пути. Вам действительно нужно использовать RelativeSource, но используя Self, а затем использовать PlacementTarget, чтобы поменять местами визуальные деревья на TextBox, из которых вы можете получить его DataContext, который должен был быть унаследован от DataGridCell, и, наконец, получить доступ к вашему свойству меню.

Непроверенный пример того, что я имею в виду:

<ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext.listDataMenu}"/>
person Rick Sladkey    schedule 18.01.2011
comment
Я проверил это сейчас. Вы правы, DataContext из TextBlock это Person, а не MyViewModel. Это показывает проблему: если вам нужен столбец ContextMenu, его нельзя привязать к MyViewModel.listDataMenu, потому что тогда элементы будут одинаковыми для всей таблицы. Вы должны переместить меню в класс Person. Я рекомендую добавить свойство Column к Person и добавить свойство Column.listDataMenu. Тогда последнее Path равно PlacementTarget.DataContext.Column.listDataMenu. - person Rick Sladkey; 18.01.2011
comment
Привет, Рик, проблема в том, что я хочу одно и то же ContextMenu с одинаковыми элементами для каждой ячейки. - person Hass_NB; 19.01.2011

Несмотря на то, что мне нужно одно и то же contextMenu для каждой строки в Datagrid, я попробовал ваше предложение и не могу заставить его работать: (Может быть, я что-то забыл

Услышьте, что я изменяю свои классы следующим образом:

    public class Person
{
    public string Type { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public int Age { get; set; }
    public Column Column { get; set; }
}

public class Menu
{
    public string Name { get; set; }
    public int Code { get; set; }
    public ObservableCollection<Menu> listMenu { get; set; }
}
public class Column
{
    public ObservableCollection<Menu> listDatatMenu { get; set; }
}

Затем я меняю функцию InitData в своей ViewModel, например:

private void InitData()
    {
        listDataPersons = new List<Person>();
        listDataMenu = new ObservableCollection<Menu>();

        DataPersons.Add(new Person() { Type = "Friend", Name = "Doe", Surname = "John", Age = 42});
        DataPersons.Add(new Person() { Type = "Friend", Name = "Smith", Surname = "Jack", Age = 42});

        DataMenu.Add(new Menu() { Name = "Principal", Code = 1});
        DataMenu.Add(new Menu() { Name = "Secondary", Code = 2});
        DataMenu.Add(new Menu() { Name = "Associated", Code = 3});

        DataMenu[2].listMenu = new ObservableCollection<Menu>();
        DataMenu[2].listMenu.Add(new Menu() { Name = "Associated 1", Code = 31 });

        DataPersons[0].Column = new Column();
        DataPersons[0].Column.listDatatMenu = DataMenu;

        DataPersons[1].Column = new Column();
        DataPersons[1].Column.listDatatMenu = DataMenu;

        listDataPersons = DataPersons;
        listDataMenu = DataMenu;
    }

И, наконец, ContextMenu на мой взгляд

<ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext.Column.listDataMenu}"/>  

Извините, если я сделал ошибку новичка, но это работает

person Hass_NB    schedule 19.01.2011

Контекстные меню работают не так просто, как могли бы, потому что по умолчанию они находятся в другом визуальном дереве, поэтому DataContext не может быть найдено.

Ключевым моментом является создание <Style>, определяющего контекстное меню, а затем прикрепление этого стиля к целевому элементу, который подключает контекстное меню. Это смещает контекстное меню в визуальное дерево, которое выстраивается в линию с нужным вам DataContext по умолчанию.

Сначала создайте стиль:

<UserControl.Resources>                                                                                                                        
    <ResourceDictionary>

        <!-- For the context menu to work, we must shift it into a style, which means that the context menu is now in a
        visual tree that is more closely related to the current data context. All we have to do then is set the style, 
        which hooks up the context menu. -->
        <Style x:Key="ContextMenuStyle" TargetType="{x:Type StackPanel}">
            <Setter Property="ContextMenu" Value="{DynamicResource TreeViewContextMenu}"/>
        </Style>
        <ContextMenu x:Key="TreeViewContextMenu">
            <MenuItem Header="Test" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.CmdDisplayDetailsGraph}"/>
        </ContextMenu>


Затем подключите контекстное меню в любом месте, не сталкиваясь с проблемами, вызванными различными визуальными деревьями.

Пример 1:

<HierarchicalDataTemplate DataType="{x:Type snapshot:Details}" ItemsSource="{Binding DetailsList}">
    <StackPanel Orientation="Vertical" Style="{StaticResource ContextMenuStyle}">
        <ContentPresenter Content="{Binding}" ContentTemplate="{Binding View.DefaultDataRowTemplate}" />
</StackPanel>

Example 2:

<DataTemplate DataType="{x:Type snapshot:InstrumentDetails}">
  <StackPanel Orientation="Vertical" Style="{StaticResource ContextMenuStyle}">                 
      <Grid HorizontalAlignment="Stretch" VerticalAlignment="Center">
person Contango    schedule 03.02.2015