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.
Related
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.
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.
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.
I would like to avoid having to build a menu manually in XAML or code, by binding to a list of ICommand-derived objects. However, I'm experiencing a bit of a problem where the resulting menu has two levels of menu-items (i.e. each MenuItem is contained in a MenuItem):
My guess is that this is happening because WPF is automatically generating a MenuItem for my binding, but the "viewer" I'm using actually already is a MenuItem (it's derived from MenuItem):
<ContextMenu
x:Name="selectionContextMenu"
ItemsSource="{Binding Source={x:Static OrangeNote:Note.MultiCommands}}"
ItemContainerStyleSelector="{StaticResource separatorStyleSelector}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<Viewers:NoteCommandMenuItemViewer
CommandParameter="{Binding Source={x:Static OrangeNote:App.Screen}, Path=SelectedNotes}" />
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
(The ItemContainerStyleSelector is from http://bea.stollnitz.com/blog/?p=23, which allows me to have Separator elements inside my bound source.)
So, the menu is bound to a collection of ICommands, and each item's CommandParameter is set to the same global target (which happens to be a collection, but that's not important).
My question is, is there any way I can bind this such that WPF doesn't automatically wrap each item in a MenuItem?
Unfortunately, the best way I've found to work around this issue is to use a style for the MenuItems, rather than an ItemTemplate. Then each property in the style can be bound to properties on your object. Something like this, for example:
<Style x:Key="SelectionContextMenuStyle" TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Text}" />
<Setter Property="Command" Value="{Binding Path=Command}" />
<Setter Property="CommandParameter" Value="{Binding Path=Parameter}" />
</Style>
It really seems like an ItemTemplate should work, and it would be the better way to go, but this is the only way I've found that actually works properly.
I would be inclined to subclass ContextMenu and override GetContainerForItemOverride:
public class ContextMenuWithNoteCommands : ContextMenu
{
protected virtual DependencyObject GetContainerForItemOverride()
{
return new NoteCommandMenuItemViewer();
}
}
Then set the CommandParameter binding in the NoteCommandMenuItemViewer style, or in ContextMenu.ItemContainerStyle, whichever is more appropriate.
This presumes you can't simply use ItemContainerStyle on a regular MenuItem to get the effect you want:
<ContextMenu ...>
<ContextMenu.ItemContainerStyle>
<Style>
...
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>