Как добавить ContextMenu в WPF DataGridColumn в MVVM? - PullRequest
0 голосов
/ 17 января 2011

У меня есть хитрая проблема, связанная с 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.Поэтому я попытался отредактировать DataGridColumn DataTemplate с TextBox, который имеет ContextMenu.К сожалению, когда я щелкаю правой кнопкой мыши по TextBox, ContextMenu ItemsSource кажется пустым.Однако, когда я щелкаю правой кнопкой мыши за TextBox в DataGrid, DataGrid ContextMenu правильно связывается.

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

Есть идеи?

Ответы [ 4 ]

1 голос
/ 20 января 2011

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

Я разместил проблему на форуме 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>                                

Нужно передать DataContext UserControl в ContextMenu через тег TextBox

Для тех, кто хочет, чтобы он правильно работал с моим кодом, вам нужно определить 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 исходного ответа: -> здесь <- </a>

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

0 голосов
/ 04 февраля 2015

Контекстные меню работают не так легко, как могли бы, потому что по умолчанию они находятся в другом визуальном дереве, чем все остальные, поэтому 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>

Пример 2:

<DataTemplate DataType="{x:Type snapshot:InstrumentDetails}">
  <StackPanel Orientation="Vertical" Style="{StaticResource ContextMenuStyle}">                 
      <Grid HorizontalAlignment="Stretch" VerticalAlignment="Center">
0 голосов
/ 19 января 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 в моем View

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

Мне жаль, если я сделалошибка новичка, но она работает

0 голосов
/ 18 января 2011

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

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

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