I've got a tricky issue regarding ContextMenu
in a WPF DataGridColumn
. I don't know if someone have already face this issue but I will really appreciate if someone can help me!
Let's start by my classes
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; }
}
Now my 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;
}}
Here are my View and it's code behind
<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>
code behind
public partial class MyView : UserControl
{
public MyView()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
}
What I wanted in this example is to have a dynamic ContextMenu
in DataGridColumn
. First I put a ContextMenu
in the entire DataGrid
and it works fine. But in my case I need a ContextMenu
only on a right click in Cells not in the entire DataGrid
. So I tried to edit DataGridColumn
's DataTemplate
with a TextBox
which has a ContextMenu
. Unfortunately when I right click in the TextBox
it's ContextMenu
's ItemsSource
seem to be empty. However when I right click outside the TextBox
in the DataGrid
, the DataGrid
's ContextMenu
is correctly binded.
I was thinking that it might be a problem of DataContext
because ContextMenu
and DataGrid
do not have the same Visual Tree so I added RelativeSource
in the ContextMenu
's ItemsSource
binding but no result!!!
Any idea?
First of all thank Rick for taking time to guide me on this issue.
I'd posted the problem in msdn Forum and I had an answer to solve it
<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
For those who want to make it work properly with my code you'll need to define UserControlRessoucre as :
<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>
this is the msdn forum link of the original answer: -->here<--
many thanks Sheldon Xiao for this answer
You are on the right track. You do need to use RelativeSource
but using Self
, and then use the PlacementTarget
to swap visual trees to the TextBox
from which you can get its DataContext
which should have been inherited from the DataGridCell
and finally be able to reach your menu property.
Untested example of what I mean:
<ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext.listDataMenu}"/>
Even though I need the same contextMenu for each row in Datagrid, I tried you suggestion and can't make it works:( May be I forgot somthing
Hear we are I modify my classes like this :
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; }
}
Then I change InitData function in my ViewModel like:
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;
}
And finally the ContextMenu in my View
<ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext.Column.listDataMenu}"/>
Feel sorry if I made a rookie mistake but it does work
Context menus do not work as easily as they could because, by default, they are in a different visual tree to everything else, so the DataContext
cannot be found.
The key insight is to create a
<Style>
that defines a context menu, then attach that style to a target element, which hooks up the context menu. This shifts the context menu into a visual tree that is lined up with the defaultDataContext
you want.
First, create the style:
<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>
Then, hook the context menu up anywhere you want, without running into issues caused by different visual trees.
Example 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">
精彩评论