I have a WPF submenu that I want to reuse in a few places in my XAML. It's a collection of eight <MenuItem> elements with some complicated bindings that I don't want to copy/paste. However, the holder is different in each case: in one place the parent is a <Menu>, in another place the parent is a <MenuItem> in a <ContextMenu>.
I've been experimenting with <Setter Property="Items"> in my <Style> but I think maybe I'm on the wrong track.
To make it concrete, I'm trying to reduce the code duplication from something like this:
<Menu>
<MenuItem Header="Details" IsCheckable="True" ... />
<MenuItem Header="List" IsCheckable="True" ... />
<MenuItem Header="Thumbnails" IsCheckable="True" ... />
...
</Menu>
...
<ContextMenu>
<MenuItem Header="View">
<MenuItem Header="Details" IsCheckable="True" ... />
<MenuItem Header="List" IsCheckable="True" ... />
<MenuItem Header="Thumbnails" IsCheckable="True" ... />
...
</MenuItem>
</ContextMenu>
How about something like this:
You'll need to create the following collection in your resource dictionary:
<Collections:ArrayList x:Key="MenuItems" x:Shared="false">
<MenuItem Header="Details" />
<MenuItem Header="List" />
<MenuItem Header="Thumbnails" />
</Collections:ArrayList>
You'll need to add the following namespace:
xmlns:Collections="clr-namespace:System.Collections;assembly=mscorlib"
...
And then just use the collection:
<Menu ItemsSource="{StaticResource MenuItems}" />
...
<ContextMenu>
<MenuItem Header="View" ItemsSource="{StaticResource MenuItems}" />
</ContextMenu>
Related
I have a simple popup control that holds multiple MenuItem's in it.
<Popup
IsOpen="{Binding ShowPopupMenu}"
<StackPanel Background="White">
<ItemsControl>
<MenuItem Header="Open file..." />
<MenuItem Header="Settings" />
<!-- Nested items -->
<MenuItem Header="Test">
<MenuItem Header="Nested Item" />
<MenuItem Header="Nested Item" />
<MenuItem Header="Nested Item" />
<MenuItem Header="Nested Item" />
<MenuItem Header="Nested Item" />
</MenuItem>
<MenuItem Header="Exit" />
</ItemsControl>
</StackPanel>
</Popup>
My problem is that MenuItem with header Test, that contains nested childrens is not expanding on mouse over.
I'm actually able to see it expanding in the design time
,
However, when it's simply not working on runtime.
Any ideas why is that?
It seems that the issue is that the Popup control handles mouse click event and, therefore, it does not reaches your menu item, try opening it manually by handling PreviewLeftButton event:
<StackPanel x:Name="Panel">
<Popup PlacementTarget="{Binding ElementName=Panel}"
IsOpen="True">
<StackPanel Background="White">
<!--<Menu>-->
<MenuItem Header="Open file..." />
<MenuItem Header="Settings" />
<!-- Nested items -->
<MenuItem Header="Test" Click="MenuItem_OnClick" PreviewMouseLeftButtonDown="UIElement_OnPreviewMouseLeftButtonDown">
<MenuItem Header="Nested Item" />
<MenuItem Header="Nested Item" />
<MenuItem Header="Nested Item" />
<MenuItem Header="Nested Item" />
<MenuItem Header="Nested Item" />
</MenuItem>
<MenuItem Header="Exit" />
<!--</Menu>-->
</StackPanel>
</Popup>
</StackPanel>
And:
private void UIElement_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
((MenuItem)sender).IsSubmenuOpen = true;
}
EDIT
My mistake, the problem is not related with the Click event not being handled. I just put a button in the stackpanel and it worked. The problem is that when a MenuItem has subitems the click event is not fired and one must manually handled the state of the submenu if the root menu item is not "child" of either a Menu or a ContextMenu control. Those controls handle that already, but since you are implementing your own "ContextMenu" then you must implement that behavior by yourself
You could use a Style with a Trigger that sets the IsSubmenuOpen property to true:
<Popup IsOpen="True">
<StackPanel Background="White">
<ItemsControl>
<MenuItem Header="Open file..." />
<MenuItem Header="Settings" />
<!-- Nested items -->
<MenuItem Header="Test">
<MenuItem Header="Nested Item" />
<MenuItem Header="Nested Item" />
<MenuItem Header="Nested Item" />
<MenuItem Header="Nested Item" />
<MenuItem Header="Nested Item" />
<MenuItem.Style>
<Style TargetType="MenuItem">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="IsSubmenuOpen" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
<MenuItem Header="Exit" />
</ItemsControl>
</StackPanel>
</Popup>
I tried to enable Item2 when Item1 is checked and disable it when Item1 is not checked. How to do that with IValueConverter to convert IsChecked property to Boolean and bind it to IsEnabled Property in Item2.
<ContextMenu x:Name="ItemsContxtMenu">
<MenuItem IsCheckable="True" x:Name="Item1" Header="item1 .."/>
<MenuItem x:Name="Item2" Header="item2 .." IsEnabled="{Binding ElementName=Item1, Path=IsChecked"}/>
</ContextMenu>
You don't need converter, write following xaml:
<Menu VerticalAlignment="Top">
<MenuItem Header="Items">
<MenuItem Name="item1" Header="Item #1" IsCheckable="True" />
<MenuItem Name="item2" Header="Item #2" IsEnabled="{Binding ElementName=item1,Path=IsChecked}" />
</MenuItem>
</Menu>
I'm trying to have a dynamic menu item using MVVM from an observable collection. Everything worked, but then I needed to add a "add new" button to the end. I found a solution using a CompositeCollection, like here:
How do I dynamically bind and statically add MenuItems?
So have the following code, where TimeSpans is a collection of ViewModels:
<MenuItem Header="Time Ranges">
<MenuItem.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding TimeSpans}" />
<Separator />
<MenuItem Header="Add New" cal:Message.Attach="NewTimeSpan()" />
</CompositeCollection>
</MenuItem.ItemsSource>
<MenuItem.ItemTemplate>
<ItemContainerTemplate>
<MenuItem Header="{Binding Name}" cal:Message.Attach="ConfigureTimeSpan()" />
</ItemContainerTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
However, the view models are not populated like it was just using ItemsSource="{Binding TimeSpans}", it's not showing anything:
I suspect this is because I'm in the StackOverflow answer above the binding is actually a collection of MenuItems, so that composite collection makes sense. Whereas mine's mixing ViewModels & MenuItems.
Is there any way to construct the collection of menu-items created from ViewModels in the XAML so I can bind it?
For anyone else who comes across this, as Szabolcs Dezsi said, I needed to use a resource for the CollectionViewSource (bad reading comprehension on my part, as that was in the answer linked in my question).
Working code below:
<MenuItem Header="Time Ranges" x:Name="TimeRangesMenuItem">
<MenuItem.Resources>
<CollectionViewSource Source="{Binding ElementName=TimeRangesMenuItem, Path=TimeSpans}" x:Key="TimeSpanMenuItems" />
</MenuItem.Resources>
<MenuItem.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource TimeSpanMenuItems}}" />
<Separator />
<MenuItem Header="Add New" cal:Message.Attach="NewTimeSpan()" />
</CompositeCollection>
</MenuItem.ItemsSource>
<MenuItem.ItemTemplate>
<ItemContainerTemplate>
<MenuItem Header="{Binding Name}" cal:Message.Attach="ConfigureTimeSpan()" />
</ItemContainerTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
I have the following piece of code (XAML C#):
<Menu IsMainMenu="True" DockPanel.Dock="Top">
<MenuItem Name="fileMenu" Header="_File" />
<MenuItem Name="editMenu" Header="_Edit" />
<MenuItem Name="setupMenu" Header="_Setup">
<MenuItem Header="_Language">
<MenuItem.Icon>
//I want to insert image here
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Name="helpMenu" Header="_Help" />
</Menu>
And a resource file named images.resx containing an image called lang.png.
How can I insert the image as an icon for the Menu-Item?
Is there a better way?
As Jason said, it's better to add your images as Resources to your project.
Open "Properties" for your project
Select Vertical-tab Resources
Choose Images from the left ComboBox
Choose "Add Resource -> Add Existing File..." from the right ComboBox
Locate the Image you would like to use, e.g "C1.png" (it will automatically be copied to the Resources folder in the root of your project)
Select properties on your newly added Resource Image
In properties, set Build Action to Resource
Open the designer for the .xaml file containing the Menu and add an Image in MenuItem.Icon and then place the cursor on Image.
xaml
<Menu IsMainMenu="True" DockPanel.Dock="Top">
<MenuItem Name="fileMenu" Header="_File" />
<MenuItem Name="editMenu" Header="_Edit" />
<MenuItem Name="setupMenu" Header="_Setup">
<MenuItem Header="_Language">
<MenuItem.Icon>
<Image/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Name="helpMenu" Header="_Help" />
</Menu>
From properties you can now select the symbol on the Source Property and all available Image resources will be displayed.
From this dialog you can also choose "Add", locate an image file on the disk and all the above steps will be made for you by Visual Studio.
The resulting uri for the Image.Source in xaml will look something like this (which ofcourse also can be added by hand)
<Menu IsMainMenu="True" DockPanel.Dock="Top">
<MenuItem Name="fileMenu" Header="_File" />
<MenuItem Name="editMenu" Header="_Edit" />
<MenuItem Name="setupMenu" Header="_Setup">
<MenuItem Header="_Language">
<MenuItem.Icon>
<Image Source="/MenuIconImage;component/Resources/C1.png" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Name="helpMenu" Header="_Help" />
</Menu>
You could add this, to the Menu.Icon.
<Image>
<Image.Source>
<BitmapImage UriSource="/ASSEMBLYNAME;component/PATH/IMAGE.png" />
</Image.Source>
<Image>
Here My code to bind IsChecked property of menu.
<MenuItem Header="_View">
<MenuItem IsCheckable="True" IsChecked="{Binding ElementName=TermMenu, Path=IsChecked}" Header="Term" />
<MenuItem IsCheckable="True" IsChecked="True" Header="Key" />
<MenuItem IsCheckable="True" IsChecked="True" Header="Hand" />
<MenuItem IsCheckable="True" IsChecked="True" Header="Rule" />
</MenuItem>
Here is the context menu
<ContextMenu x:Key="DataGridColumnHeaderContextMenu" >
<MenuItem x:Name="TermMenu" IsCheckable="True" IsChecked="True" Header="Key Term" />
<MenuItem x:Name="Key" IsCheckable="True" IsChecked="True" Header="Key Term Description" />
<MenuItem x:Name="ShortHand" IsCheckable="True" IsChecked="True" Header="Hand" />
<MenuItem x:Name="Rule" IsCheckable="True" IsChecked="True" Header="Rule" />
</ContextMenu>
Now I want when the context menu item is check the main menu View will be checked automatically. But my code is not working. Please explain me why.
x:Name does not work in resources. the element with the specified name in ElementPath does not exist. this is why the binding doesn't work. you can't access TermMenu from the code behind either. You should bind both to a view-model as #wilford sugested.