ContextMenu bound to ObservableCollection<MenuItem> does not refresh data - wpf

Given the following XAML snippet:
<UserControl.Resources>
<ResourceDictionary>
<Style x:Key="ContextMenuItemStyle">
<Setter Property="MenuItem.Header" Value="{Binding Text}"/>
<Setter Property="MenuItem.ItemsSource" Value="{Binding Children}"/>
<Setter Property="MenuItem.Command" Value="{Binding Command}" />
</Style>
<ContextMenu x:Key="contextMenu" ItemsSource="{Binding MenuOptions}" ItemContainerStyle="{StaticResource ContextMenuItemStyle}" />
</ResourceDictionary>
</UserControl.Resources>
<DockPanel>
<TextBox Height="30" DockPanel.Dock="Top" ContextMenu="{StaticResource contextMenu}" />
<Button Content="Add Menu Item" DockPanel.Dock="Top" Command="{Binding AddMenuItem}" />
</DockPanel>
And View Model:
public class MyViewModel {
public ObservableCollection<MenuItem> DocumentExplorerMenuOptions { get; set; }
MenuItem firstMenuItem;
MenuItem secondMenuItem;
public MyViewModel() {
firstMenuItem = new MenuItem("First") { Command = new DelegatingCommand(x => MessageBox.Show("First Selected") };
secondMenuItem = new MenuItem("Second") { Command = new DelegatingCommand(x => MessageBox.Show("Second Selected") };
MenuOptions = new ObservableCollection<MenuItem> { firstMenuItem, secondMenuItem };
AddMenuItem = new DelegateCommand<object>(x => firstMenuItem.Children.Add(
new MenuItem("Child of First")));
}
public DelegateCommand<object> AddMenuItem { get; set; }
}
And class:
public class MenuItem {
public MenuItem(string text) {
Text = text;
Children = new List<MenuItem>();
}
public string Text { get; set; }
public List<MenuItem> Children { get; private set; }
public ICommand Command { get; set; }
}
Clicking the button does add the child to firstMenuItem but it does not appear in the context menu of the TextBox.
I can't figure out how to make the context menu show the dynamic content of the context menu. Any thoughts?

I would not bind to a collection of MenuItems but rather to a more data-driven collection which may contain the MenuItem header, a command which is executed upon click and another collection of such items for the sub-items. Then you could use a (Hierarchical)DataTemplate to generate the menu on the fly. Doing so would probably take care of update issues if your datatype implements the necessary interfaces.
Edit: You seem to have such a datatype already, could you post its code?
Edit2: I think the problem is that you use a style that explicitly needs to be applied (it is probably only being applied to the main context menu, not the sub-items), as noted before i'd suggest a HierarchicalDataTemplate.
Edit3: lol...
public List<MenuItem> Children { get; private set; }
Of course it's not going to update if it's a List and not an ObservableCollection.
(The class is quite badly designed overall by the way, Lists should normally not even have a private setter, they should be properties with just a getter to a readonly field)

Related

Pure XAML for Menu with ObservableCollection?

This is my ViewModel:
public ObservableCollection<Person> Persons;
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
And i want to create menu with all this Persons object from my List.
So in XAML i just create this:
<Menu Name="menuPersons" DockPanel.Dock="Top" SnapsToDevicePixels="True">
</Menu>
Code behind
MenuItem fileMenuItem = new MenuItem();
fileMenuItem.Header = "Persons";
menuPersons.Items.Add(fileMenuItem);
foreach (Person item in Persons)
{
fileMenuItem.Items.Add(new MenuItem
{
Header = item.FirstName,
});
}
But as you can see this looks ugly so my question is is pure XAML way to do that and bind all my object with all its properties ?
Update
So the solution works fine for me but i only have one issue:
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}"/>
</Style>
</Menu.ItemContainerStyle>
I have Style that i am using but so i cannot use this Menu.ItemContainerStyle
How can i solve this ? (note that i have another several different Menus that i using with this Style but i only need this Command for this Menu)

WPF Menu Sub Items [duplicate]

Following the example from Omer van Kloeten I filled a menu with menu items bound from my observable collection. I got a single menu item for a single collection item. Fine, but I actually want more. I also want to be able to have a menu item with two or three sub items for one collection item. Here is some rough sketch:
What I Have What I Want
+ First Item + Create First Item
+ Second Item + Second Item
+ Delete
+ Update
Here "First Item" has property Exists = false, but "Second Item" has it true. My current code:
public class CollectionItem
{
public string Name { get; set; }
public bool Exists { get; set; }
}
public partial class MainWindow : Window
{
ObservableCollection<CollectionItem> items;
public MainWindow()
{
items = new ObservableCollection<CollectionItem>();
items.Add(new CollectionItem() { Name = "First Item", Exists = false });
items.Add(new CollectionItem() { Name = "Second Item", Exists = true });
AllItems.ItemsSource = items;
}
}
<MenuItem x:Name="AllItems" Header="What I Have">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Path=Name}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
How do I mix simple menu items with sub items?
How do I mix simple menu items with sub items?
First, you need to have your data structured appropriately, with collection properties to represent the child, or sub MenuItems. Then, you need to data bind the collection property to the ItemsSource property of the parent MenuItem using the MenuItem.ItemContainerStyle, similar to what you're doing now. You should end up with something like this:
public class CollectionItem
{
public string Name { get; set; }
public bool Exists { get; set; }
public ObservableCollection<CollectionItem> CollectionOfSubItems { get; set; }
}
...
<MenuItem x:Name="AllItems" Header="What I Have">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Path=Name}" />
<Setter Property="ItemsSource" Value="{Binding CollectionOfSubItems}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>

Context menu commands in an Observable Collection

I am new to WPF and am trying to bind a context menu to an ObservableCollection of models. I am able to get the context menu to display but I can't get the commands to fire. Here is my code
<ContextMenu ItemsSource="{Binding ContextMenuList}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="MenuItem.Header" Value="{Binding Name}" />
<Setter Property="MenuItem.ItemsSource" Value ="{Binding Children}" />
<Setter Property="Command" Value="{Binding MenuCommand}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
In the View Model I have
public class MenuItemViewModel
{
public string Name { get; set; }
public ObservableCollection Children { get; set; }
public ICommand MenuCommand;
public MenuItemViewModel(string name, ObservableCollection pChildren, ICommand pCommand)
{
this.Name = name;
this.Children = pChildren;
this.MenuCommand = pCommand;
}
}
and I populate the collection
public void AddContextMenuItems()
{
this.ContextMenuList = new ObservableCollection<MenuItemViewModel>();
ICommand pCommand = new ActionCommand(() => this.ChangeChartType());
MenuItemViewModel ohlc = new MenuItemViewModel("OHLC",null,pCommand);
MenuItemViewModel candlestick = new MenuItemViewModel("CandleStick");
ObservableCollection<MenuItemViewModel> chartTypeColl = new ObservableCollection<MenuItemViewModel>();
chartTypeColl.Add(ohlc);
chartTypeColl.Add(candlestick);
this.ContextMenuList.Add(new MenuItemViewModel("ChartType",chartTypeColl));
}
I don't see the ChangeChartType method ever get triggered, what I am I doing wrong here ?
Try to change MenuCommand field to property
public ICommand MenuCommand { get; set; }
Hope this work for you. I haven't tried this though.

Create contextmenus for datagrid rows

I have a datagrid that potentially can have many rows. As the user right clicks one of the rows, I need to show a context menu for each of the rows and perform an action (same action but different data item according to the current selected row) when the user clicks the option.
What is the best strategy for this?
I'm fearing that a ContextMenu for each row is overkill even though I'm creating the menu using the ContextMenuOpening event, sort of a "lazy load" for the context menu. Should I only use one ContextMenu for the datagrid? But with this I would have some more work regarding the click event, to determine the correct row, etc.
As far as I know, some of the actions will be disabled or enabled depending on the row, so there is no point in a single ContextMenu for a DataGrid.
I have an example of the row-level context menu.
<UserControl.Resources>
<ContextMenu x:Key="RowMenu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Edit" Command="{Binding EditCommand}"/>
</ContextMenu>
<Style x:Key="DefaultRowStyle" TargetType="{x:Type DataGridRow}">
<Setter Property="ContextMenu" Value="{StaticResource RowMenu}" />
</Style>
</UserControl.Resources>
<DataGrid RowStyle="{StaticResource DefaultRowStyle}"/>
The DataGrid must have a binding to a list of view models with commands:
public class ItemModel
{
public ItemModel()
{
this.EditCommand = new SimpleCommand
{
ExecuteDelegate = _ => MessageBox.Show("Execute"),
CanExecuteDelegate = _ => this.Id == 1
};
}
public int Id { get; set; }
public string Title { get; set; }
public ICommand EditCommand { get; set; }
}
The context menu is created in the resources collection of the UserControl and I think there is only one object which is connected with datagrid rows by reference, not by value.
Here is another example of ContextMenu for a Command inside a MainViewModel. I suppose that DataGrid has a correct view model as the DataContext, also the CommandParameter attribute must be placed before the Command attribute:
<ContextMenu x:Key="RowMenu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Edit" CommandParameter="{Binding}"
Command="{Binding DataContext.DataGridActionCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}" />
</ContextMenu>
Models:
public class MainViewModel
{
public MainViewModel()
{
this.DataGridActionCommand = new DelegateCommand<ItemModel>(m => MessageBox.Show(m.Title), m => m != null && m.Id != 2);
}
public DelegateCommand<ItemModel> DataGridActionCommand { get; set; }
public List<ItemModel> Items { get; set; }
}
public class ItemModel
{
public int Id { get; set; }
public string Title { get; set; }
}
But there is a problem that MenuItem isn't displayed as a disabled item if CanExecute returns false. The possible workaround is using a ParentModel property inside the ItemModel, but it doesn't differ much from the first solution.
Here is example of above-described solution:
public class ItemModel
{
public int Id { get; set; }
public string Title { get; set; }
public MainViewModel ParentViewModel { get; set; }
}
//Somewhere in the code-behind, create the main view model
//and force child items to use this model as a parent model
var mainModel = new MainViewModel { Items = items.Select(item => new ItemViewModel(item, mainModel)).ToList()};
And MenuItem in XAML will be simplier:
<MenuItem Header="Edit" CommandParameter="{Binding}"
Command="{Binding ParentViewModel.DataGridActionCommand}" />

How to add a ContextMenu in the WPF DataGridColumn in MVVM?

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 default DataContext 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">

Resources