I would like to have global Context Menu that can be used in all datagrids. I defined ContextMenu and style in App.xaml. Main Window is build with many UserControl.
<Application.Resources>
<ContextMenu x:Key="contextCommonMenu">
<MenuItem Header="Import from Excel" Command={???} />
<MenuItem Header="Export table to .csv file"/>
<MenuItem Header="Save to Database"/>
<MenuItem Header="Clear Data" />
<MenuItem Header="Synchronize with DB"/>
</ContextMenu>
<Style TargetType="DataGrid">
<Setter Property="ContextMenu" Value="{StaticResource contextCommonMenu}"/>
</Style>
</Application.Resources>
My problem is how can I bind a command from ViewModel to ContextMenu ?
If the ContextMenu was created in UI Control, then it is simple, because Binding see the ViewModel but I do not have access to ViewModel?
The trick here is to use the PlacementTarget property, that contains the element, the ContextMenu is aligned to, what is the DataGrid in our case.
But this is only half of the solution. Because of the data template, the DataContext is set to a dataitem, and not the view model. So you need another relative source lookup, to find the view model. Trick Number 2 is to use the Tag property to bind the view model from outside to the grid, which is the PlacementTarget used above. And there we are.
You can always set the context menu by traversing through the Relative source. For example you set the datacontext of the context menu like below:
<Application.Resources>
<ContextMenu x:Key="contextCommonMenu" DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Import from Excel" Command="{Binding MyCommand}"/>
<MenuItem Header="Export table to .csv file"/>
<MenuItem Header="Save to Database"/>
<MenuItem Header="Clear Data" />
<MenuItem Header="Synchronize with DB"/>
</ContextMenu>
<Style TargetType="{x:Type DataGrid}">
<Setter Property="ContextMenu" Value="{StaticResource contextCommonMenu}"/>
</Style>
</Application.Resources>
Now in the view where you declare your datagrid you can place the tag for the context menu to understand its binding:
<DataGrid Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" />
I hope this works for you. The context menu will automatically bind to your command that you have defined in the view model.
Related
I'm trying to write just some sample application using MVVM. In my view, I have two MenuItems with submenus
<Menu >
<--!first submenu-->
<MenuItem Header="{Binding AppViewModel.AppSubtitles[top_menu_navigation]}" >
<MenuItem Header="{Binding AppViewModel.AppSubtitles[top_menu_go_forward]}" Command="{Binding NavigateForward}" />
<MenuItem Header="{Binding AppViewModel.AppSubtitles[top_menu_go_back]}" Command="{Binding NavigateBack}" />
</MenuItem>
<--!second submenu-->
<MenuItem ItemsSource="{Binding AppViewModel.AllLanguagesItems}" Header="{Binding AppViewModel.AppSubtitles[top_menu_language]}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" >
<Setter Property="Header" Value="{Binding DisplayName}"/>
<Setter Property="Command" Value="{Binding ChangeLanguage}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
(items in AllLanguagesItems list have properties DisplayName and ChangeLanguage)
I also dynamically change application style by adding/removing ResourceDictionaries (with styles for buttons, menuitems etc.) to/from application MergedDictionaries.
The problem is that changing styles works well with first submenu, menuitems styles are updated correctly, but in second submenu, only partially - mainly, OnHover effect doesn't change.
Before adding dictionary
First submenu beforeSecond submenu before
After adding dictionary
First submenu afterSecond submenu after
I assume it has something to do with overwriting styles, but how would you define bindings otherwise?
I tried
<MenuItem ItemsSource="{Binding AppViewModel.AllLanguagesItems}" Header="{Binding AppViewModel.AppSubtitles[top_menu_language]}" >
<MenuItem.ItemTemplate>
<DataTemplate >
<MenuItem Header="{Binding DisplayName}" MaxWidth="200" MaxHeight="20"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
but it just creates MenuItem inside MenuItem
MenuItem inside MenuItem
EDIT:
Styles: https://github.com/Kaszub09/MVVM-WPF-DI-Sample/tree/master/WPFSampleApplication/ApplicationData/Themes
Use the BasedOn property in your style
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>
If the Context menu is "hardcoded" in xaml, then it is easy to add submenus. For example:
<ContextMenu>
<MenuItem Header="Comm1" Command="{Binding Blabla1}">
<MenuItem Header="SubComm1" Command="{Binding Blabla2}"></MenuItem>
</MenuItem>
<MenuItem Command="Comm2"></MenuItem>
<MenuItem Command="Comm3"></MenuItem>
</ContextMenu>
This means, that the ContextMenu has three elements (Comm1, Comm2 and Comm3) and Comm1 has submenu SubComm1.
I have made my ContextMenu a bit more flexible:
<ContextMenu ItemsSource="{Binding ContextMenuItemsSource}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding ContextMenuCommandHeader}"></Setter>
<Setter Property="Command" Value="{Binding ContextMenuCommand}"></Setter>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
Basically I can have any number of elements in ContextMenu, and any element can have any Command. How can I add submenu to ContextMenu element?
You can set MenuItem.ItemsSource to the nested collection. This will generate the submenu for any menuitem. For this the model backing your MenuItem should have submenuitems collection in it
<ContextMenu ItemsSource="{Binding ContextMenuItemsSource}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="ItemsSource" Value="{Binding ContextMenuSubItems}"></Setter>
<Setter Property="Header" Value="{Binding ContextMenuCommandHeader}"></Setter>
<Setter Property="Command" Value="{Binding ContextMenuCommand}"></Setter>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
Similarly you can set the MenuItem.ItemContainerStyle and MenuItem.ItemTemplate to style your submenuitems.
Specifying explicitly, without binding a collection you can add sub menus by nesting them.
A MenuItem can have other MenuItem elements within it as child/sub menus and can go up to several levels. The following code adds three children menu items to first menu item.
<MenuItem Header="_File">
<MenuItem Header="_Open" IsCheckable="true" />
<MenuItem Header="_Close" IsCheckable="true" />
<MenuItem Header="_Save" IsCheckable="true" />
</MenuItem>
Source: https://www.c-sharpcorner.com/uploadfile/mahesh/menus-in-wpf/
I am trying to reduce code duplication. Consider the following:
<page...>
<page.resources>
<MenuItem x:Key="commonItem" />
</page.resources>
<TextBlock>
<TextBlock.ContextMenu>
<ContextMenu>
<ContextMenu.Resources>
<local:thingOne x:Key="one"/>
<local:thingTwo x:Key="two"/>
</ContextMenu.Resources>
<StaticResourceExtension PropertyKey="commonItem"/>
<StaticResourceExtension PropertyKey="commonItem"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</page>
How do I pass "one" to the first commonItem instance and "two" to the second?
I realize given the above example there would be a better way to do this, this is an extremely trimmed down version of what is really going on in our XAML.
More generally the question is, how do I follow good DRY principles when I have context menus all over the app that are different, but share some similar menu items?
First, you shouldn't put a MenuItem in your Resources. This will just create a single instance of a MenuItem, and because it's a UIElement it can only be used in one location on your Page.
You could instead keep a Style for a MenuItem in your resources, with all setting that are common to most MenuItems, and apply that style to your items. Tip: If you omit the x:Key from your Style and just give it a TargetType, it will be applied to all MenuItems:
<Page...>
<Page.resources>
<Style TargetType="MenuItem" >
<Setter Property="Header" Value="{Binding}" />
<Setter Property="Foreground" Value="Lime" />
</Style>
</Page.resources>
<TextBlock>
<TextBlock.ContextMenu>
<ContextMenu>
<ContextMenu.Resources>
<local:thingOne x:Key="one"/>
<local:thingTwo x:Key="two"/>
</ContextMenu.Resources>
<MenuItem DataContext="{StaticResource one}" />
<MenuItem DataContext="{StaticResource two}" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</Page>
So after some research, I realized I was trying to be too complicated. Here is what I did:
Add a custom menu item type:
public class MyMenuItem : MenuItem {}
And in the proper scope context (for me it was global):
<Style TargetType="namespace:MyMenuItem">
<!-- common control internals -->
</Style>
When it is needed to be used:
<ContextMenu>
<ContextMenu.Resources>
<local:thingOne x:Key="one"/>
<local:thingTwo x:Key="two"/>
</ContextMenu.Resources>
<namespace:MyMenuItem DataContext={Binding one}/>
<namespace:MyMenuItem DataContext={Binding two}/>
</ContextMenu>
This approach allows setting the DataContext and allows for automatic style application when combined with other MenuItems in a MenuBase control that need to have a different behavior.
One would expect a named style could be applied and a simple menu item could be used. I tried that and it did not work. I expect somewhere along the way something was overriding this for a MenuItem, but does not for a MenuItem derived type.
I have an array of strings. For each of these strings, I'd like to create a seperate xaml element (<menuitem> is from an external library):
<MenuItem Header="Update">
<MenuItem Header="arrayvalue1" Command="{Binding UpdateCommand}" />
<MenuItem Header="arrayvalue2" Command="{Binding UpdateCommand}" />
<MenuItem Header="arrayvalue3" Command="{Binding UpdateCommand}" />
</MenuItem>
Instead of hardcoding 3 elements, I'd like to create these from the array of strings.
Is this possible and if so, how?
MenuItem is an ItemsControl, so you can bind any collection to the ItemsSource property and it will generate the children for you. In the case of MenuItem, the children generated are also MenuItems. To apply bound values to properties on those children you can set an ItemContainerStyle which will be applied to each. Since the Command you want to use is on the top level DataContext you will need to use more of an indirect binding, which may be different depending on which technology you're using. Here's how it looks for WPF:
<MenuItem Header="Update" ItemsSource="{Binding Strings}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Path=DataContext.UpdateCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Menu}}}" />
<Setter Property="Header" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
What you're looking for is called an ItemsControl. You can use it to present a bunch of items in whatever form you like by adding an ItemTemplate to it.