I have a databound hierarchical menu in WPF. All items are displayed, but the commands only fire for the leafs of the menu, not the items that have children. I'm guessing the command is overriden by expanding the child menu...
How do I get the command to execute even for the menu items with children?
What I have now is
<UserControl ...>
<WrapPanel>
<Menu>
<Menu.Resources>
<Style x:Key="MenuItemStyle" TargetType="MenuItem" d:DataContext="{d:DesignInstance local:TreeItem}">
<Setter Property="Command" Value="{Binding DataContext.AddColumnCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</Menu.Resources>
<MenuItem Header="Add ▼" ItemsSource="{Binding AvailableFields}">
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:TreeItem}" ItemsSource="{Binding NestedItems}" ItemContainerStyle="{StaticResource MenuItemStyle}">
<ContentPresenter Content="{Binding Annotation}"/>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
</WrapPanel>
</UserControl>
I found a question with a similar name, but the situation is different and it doesn't have a good answer anyway.
All items are displayed, but the commands only fire for the leafs of the menu, not the items that have children.
Yes, this is the expected behaviour since clicking on a MenuItem with children is supposed to expand the submenu of child items. It doesn't execute a command.
If you want to expand the child items and execute the command you could handle the PreviewMouseLeftButtonDown event of the MenuItem:
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding DataContext.AddColumnCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="OnMouseDown" />
</Style>
-
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null && mi.Command != null && mi.HasItems)
mi.Command.Execute(mi.CommandParameter);
}
Note that handling the event in the code-behind of the view doesn't really break the MVVM pattern here since you are just invoking the command of the view model from the code-behind instead of invoking it from the XAML markup of the same view. But if you don't like this approach you could use an attached behaviour: https://www.codeproject.com/articles/28959/introduction-to-attached-behaviors-in-wpf
Related
As you can see in the image below, you can see 2 hover states. Here is the XAML
<Menu ItemsSource="{Binding Data.MenuCollection}">
<Menu.ItemTemplate >
<DataTemplate DataType="MenuItem">
<MenuItem Header="{Binding Header}" Command="{Binding Command}" ItemsSource="{Binding Children}"/>
</DataTemplate>
</Menu.ItemTemplate>
</Menu>
The Collection of data works on the header. However I can't get the Children nodes to appear.
public void CreateTempMenuList()
{
MenuCollection = new ObservableCollection<MenuItem>()
{
new MenuItem()
{
Header = "File",
Children = new ObservableCollection<MenuItem>()
{
new MenuItem()
{
Header = "Exit"
}
}
}
};
}
The MenuItem class is something I created. Each property has a setter that called the OnPropertiesChanged Function. I can add the class if needed, but I am pretty sure thats not the problem.
So my question is. How do i get rid of the 'double' hover. In the image you can see 2 borders. An outer border which i hover over. the hover stays until focused on something else.
My second question is how can i get the child items to work? The itemssource on the menuitem tag could be wrong but its all i could think of.
Define an HierarchicalDataTemplate:
<Menu ItemsSource="{Binding Data.MenuCollection}">
<Menu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.Resources>
<Menu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}" />
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
A System.Windows.Controls.MenuItem container is implicitly created for each item so you shouldn't add another MenuItem element in the template.
Also make sure that you don't bind to an ObservableCollection<System.Windows.Controls.MenuItem> because the ItemTemplate won't be applied to built-in MenuItem elements.
To make your current code work, right click your Menu control > Edit Additional Template > Edit ItemContainerStyle > Edit Copy.
And in the generated Style,
Search for this piece of code :
<Trigger Property="Role" Value="TopLevelItem">
<Setter Property="Padding" Value="7,2,8,3"/>
<Setter Property="Template" Value="{DynamicResource {ComponentResourceKey ResourceId=TopLevelItemTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}}"/>
</Trigger>
And change Padding to 0 instead of 7,2,8,3 .
I have a nested context menu like this:
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Thing Count" ItemsSource="{Binding ThingsProvider}">
NB the Thing Count parent MenuItem with children from ThingsProvider.
ThingsProvider provides a List of ThingViewModel which contains properties Thing and IsChecked. I want to be able to control IsChecked from my main view model and from the user via the MenuItem but I have hit a problem; If I use an ItemContainerStyle like this.
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Thing, Mode=OneWay}"/>
<Setter Property="IsChecked" Value="{Binding Path=IsChecked, UpdateSourceTrigger=PropertyChanged}"/>
</Style>
</MenuItem.ItemContainerStyle>
then (predictably I suppose as it's a style) it will observe the ViewModel's IsChecked but will not set it.
If I use an ItemTemplate like so
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Thing, Mode=OneWay}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
I can't get to the MenuItem's IsChecked property as I've only got the TextBlock. If I put a MenuItem in the ItemTemplate then I end up with nested MenuItems in my UI and it looks bad.
There must be a way to do this but I'm stumped at the moment, can anyone help?
Cheers
Rich
I am creating an MVVM Wpf client application. I want create menu in the main View for the application that his a menu item called "Window" on it. That menu item will dynamically update itself with a submenu of menuitems who are made up of the list of active windows running in the application. I created a ViewManager whom each View registers itself with to compile a list of active windows.
I am trying to do this in XAML but getting an error when I click on "Window"
<MenuItem Header="Window">
<ItemsControl ItemsSource="{Binding ViewMgr.Views}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.OpenWindowCmd ,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</MenuItem>
How do I create a dynamically updated list of menuitems on my menu in XAML using a MVVM style of data bindings and commands?
You are adding a new ItemsControl as a single child of the menu item, instead of adding each view as one child of the menu item itself. You probably get the error because the styles TargetType doesn't match. MenuItem inherits from ItemsControl itself and exposes a property ItemsSource. Try the following:
<MenuItem ItemsSource="{Binding ViewMgr.Views}" DisplayMemberPath="Title">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.OpenWindowCmd, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
I have the following code:
<Menu>
<MenuItem ItemsSource="{Binding SomethingMenuItems}" Header="Something"/>
</Menu>
Where MenuItems is a collection of objects of type SomethingMenuItem.
I also have:
<DataTemplate DataType="{x:Type SomethingMenuItem}">
<MenuItem Header="{Binding OrderTypeName}">
<MenuItem.Icon>
<Image Source="{Binding IconName}"/>
</MenuItem.Icon>
</MenuItem>
</DataTemplate>
I'd expect to get (I get something like this when I hardcode the menu items):
What I get instead is:
What am I doing wrong?
You might want to use ItemContainerStyle instead of DataTemplate. You have to style the container of the data item than just providing a template for data item.
With your DataTemplate, you basically displaying another nested MenuItem as content for each MenuItem generated for your Menu Something, and your inner MenuItem has the image in the correct place. I am attaching VisualTree from Snoop here for your reference.
Below is the Style for the container of the data item (in this case a MenuItem):
<MenuItem ItemsSource="{Binding SomethingMenuItems}"
Header="Something">
<MenuItem.Resources>
<Image Source="{Binding IconPath}" x:Key="IconImage" x:Shared="False"/>
<Style TargetType="MenuItem" >
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="Icon" Value="{StaticResource IconImage}" />
</Style>
</MenuItem.Resources>
</MenuItem>
You can see no nested MenuItems when you apply the above style, have added image here
With the above style applied, this is how the Menu looks:
Refer to this MSDN page to know more about ItemContainerStyle.
So, sthotakura's answer set me on the right track, but the code he posted didn't quite work because the style got applied to the parent menuitem as well.
In case someone else has a similar problem and stumbles on this question, here's the code that works:
<MenuItem ItemsSource="{Binding SomethingMenuItems}"
Header="Something">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem" >
<Style.Resources>
<Image Source="{Binding IconPath}" x:Key="IconImage" x:Shared="False"/>
</Style.Resources>
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="Icon" Value="{StaticResource IconImage}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
I have listview in "gridview style" with 2 columns. I wonder how to get first column value of item on which i called context menu (CM has one button only where i want to handle value)?
Here is XAML:
<ListView.Resources>
<ContextMenu x:Key="ContextMenu">
<MenuItem Header="Run test with parameters" x:Name="runTestWithParams" Click="runTestWithParams_Click" />
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu}"/>
</Style>
</ListView.ItemContainerStyle>
And here is contextMenu button handeler:
private void runTestWithParams_Click(object sender, RoutedEventArgs e)
{
string valueOfFirstColumn = // something like ItemThatWasClicked["ID"].ToString();
}
If you have any ideas please help me.
SOLUTION FOUND
It was necessary to create handler of RightMouseUp event and get value I need from its sender,
because of Context_Menu_button_Click's sender does not contain info about item it was clicked on, but contains info about ContextMenu.
So... I handled it like this:
Handler:
private void HandleMouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
ListViewItem selected = sender as ListViewItem;
String valueINeed = (selected.Content as TestIdentification).Id;
}
XAML:
<Style TargetType="ListViewItem">
<EventSetter Event="MouseRightButtonUp" Handler="HandleMouseRightButtonUp" />
</Style>
Thanks for all!
Another solution is to handle the ContextMenuOpening event on the ListView. From there, you can access the ContextMenuEventArgs.OriginalSource property in order to get the element from which the event originates.
Your problem is a common one... the ContextMenu is not part of the main visual tree that the rest of your controls are in and so it does not have the correct DataContext by default. However, you'll be glad to hear that it is relatively simple to fix this. I generally use the Tag property to pass the DataContext to the ContextMenu:
<ListView.Resources>
<ContextMenu x:Key="ContextMenu" DataContext="{Binding PlacementTarget.Tag,
RelativeSource={RelativeSource Self}}">
<MenuItem Header="Run test with parameters" x:Name="runTestWithParams"
Click="runTestWithParams_Click" />
</ContextMenu>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type YourNamespace:YourDataType}">
<Grid Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}"
ContextMenu="{StaticResource ContextMenu}">
<!--Define what your data objects look like here or just use this-->
<TextBlock Text="{Binding}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
Now, I've not done this quite in this way before, so we might have some problems to work through, but give this a go anyway. When the user clicks on the MenuItem, you should now have access to the data object using the sender.DataContext property. Let me know how it goes.