Checkable MenuItem With Sub Menus - wpf

Can you have submenus with the top level set to checkable in WPF? I can't seem to get this to work.
<Window.ContextMenu>
<ContextMenu>
<MenuItem Header="Top Level 1" IsCheckable="True" IsChecked="True">
<MenuItem Header="Sub Level" />
<MenuItem Header="Sub Level" />
</MenuItem>
<MenuItem Header="Top Level 2">
<MenuItem Header="Sub Level" />
<MenuItem Header="Sub Level" />
</MenuItem>
</ContextMenu>
</Window.ContextMenu>
Top Level 1 is checkable, but the sub levels don't appear. Any thoughts?

If you dig into MenuItem's ControlTemplate, you will see that it uses different templates depending on it's Role property.
Reference:
Menu Styles and Templates
<Style x:Key="{x:Type MenuItem}"
TargetType="{x:Type MenuItem}">
<Setter Property="OverridesDefaultStyle"
Value="True" />
<Style.Triggers>
<Trigger Property="Role"
Value="TopLevelHeader">
<Setter Property="Template"
Value="{StaticResource {x:Static MenuItem.TopLevelHeaderTemplateKey}}" />
<Setter Property="Grid.IsSharedSizeScope"
Value="true" />
</Trigger>
<Trigger Property="Role"
Value="TopLevelItem">
<Setter Property="Template"
Value="{StaticResource {x:Static MenuItem.TopLevelItemTemplateKey}}" />
</Trigger>
<Trigger Property="Role"
Value="SubmenuHeader">
<Setter Property="Template"
Value="{StaticResource {x:Static MenuItem.SubmenuHeaderTemplateKey}}" />
</Trigger>
<Trigger Property="Role"
Value="SubmenuItem">
<Setter Property="Template"
Value="{StaticResource {x:Static MenuItem.SubmenuItemTemplateKey}}" />
</Trigger>
</Style.Triggers>
</Style>
Seems like it can either allow checking or subitems by default.
To workaround that, use following code:
XAML:
<ContextMenu>
<MenuItem Header="Top Level 1"
Mouse.PreviewMouseUp="MenuItem_MouseLeftButtonUp">
<MenuItem Header="Sub Level" />
<MenuItem Header="Sub Level" />
</MenuItem>
<MenuItem Header="Top Level 2">
<MenuItem Header="Sub Level" />
<MenuItem Header="Sub Level" />
</MenuItem>
</ContextMenu>
Code behind:
private void MenuItem_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
(sender as MenuItem).IsChecked = !(sender as MenuItem).IsChecked;
}
I strongly recommend converting/encapsulating this piece of functionality into an Attached Property or a Behavior.

To add to decyclone's answer:
Since the menu will sit there still open after doing this, and if you want it to close, you can then close the menu by setting IsOpen = false on the parent contextmenu:
private void MenuItem_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
(sender as MenuItem).IsChecked = !(sender as MenuItem).IsChecked;
((sender as MenuItem).Parent as ContextMenu).IsOpen = false;
}

Another approach is to simply use a CheckBox as the MenuItem's Icon:
<MenuItem>
<MenuItem.Icon>
<CheckBox HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding MyCheckProperty}"/>
</MenuItem.Icon>
<MenuItem Header="Item1"/>
<MenuItem Header="Item2"/>
</MenuItem>
In this case the user must click the CheckBox - not just anywhere on the MenuItem - in order to change the state, while clicking elsewhere will keep the default behavior of immediately opening the submenu (which may or may not be desired). Also, this allows for three-state values. In particular, this is excellent if the top level menu should function as a master switch for all its submenus, with the null state indicating that some substates are checked, and some are not. As for decyclone's answer, the menu will stay open unless further measures are taken.

Related

Change context menu in xaml

I've got a view with two large context menus defined as resouces. They look something like this (only larger) :
<ContextMenu x:Key="ContextMenu1">
<MenuItem Header="Append" Command="{Binding AppendCommand}" />
<MenuItem Header="Edit" Command="{Binding AppendCommand}" />
</ContextMenu>
<ContextMenu x:Key="ContextMenu2">
<MenuItem Header="Delete" Command="{Binding DeleteCommand}" />
<MenuItem Header="Verify" Command="{Binding VerifyCommand}" />
</ContextMenu>
I know that I can dynamically show / hide items with the canExecute method of the command. But since this is two completely different modes I would like to just Bind to a bool property that decides which context menu to display. Something like this:
<ListView ContextMenu={binding ContextMenuSelector}>
Does anyone know how I can do something like that ?
Consider using a DataTemplateSelector.
The DataTemplateSelector enables you to display specific presentations based on the context of each datacontext item within your itemscontrol.
I have used it for context menu items that may differin behavior.
I got a few good ideas from the DataTemplateSelector, but I ended up with a style with data trigger:
<Style TargetType="{x:Type StackPanel}" x:Key="stackPanelStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=AppendMode}" Value="True">
<Setter Property="ContextMenu" Value="{DynamicResource PanelContextMenuAttachFile}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=AppendMode}" Value="False">
<Setter Property="ContextMenu" Value="{DynamicResource PanelContextMenu}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>

Why can't this Image trigger binding in MenuItem.Icon find the MenuItem as a DataSource?

I've been stuck on this for a while now and I can't work out why. Basically, I have a ContextMenu with some MenuItem objects in it. I have declared Image objects for the MenuItem.Icon properties. I have Command objects bound to the MenuItems and that all works fine... in particular, when the Command.CanExecute method returns false, the MenuItem is correctly disabled and the MenuItem.Header text is greyed out.
I've been trying to set the Image.Opacity of the MenuItem Images to 0.5 when the MenuItem is disabled and this is where the problem is. For some reason, a binding in a DataTrigger in the Image.Style cannot find the MenuItem that I am trying to bind to. I have added a simplified example of my problem below.
<UserControl.Resources>
<Style x:Key="MenuItemIconStyle" TargetType="{x:Type Image}">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
<Style.Triggers>
<!--This Binding is not working-->
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type MenuItem}}}" Value="False">
<Setter Property="Image.Opacity" Value="0.5" />
</DataTrigger>
</Style.Triggers>
</Style>
<!--This is all working just fine-->
<ContextMenu x:Key="ContextMenu" DataContext="{Binding PlacementTarget.Tag,
RelativeSource={RelativeSource Self}}">
<MenuItem Header="Open" Command="{Binding Open}" CommandParameter="{Binding
PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
<MenuItem.Icon>
<Image Source="/Application;component/Images/Actions/FolderOpen_16.png"
Style="{StaticResource MenuItemIconStyle}" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
...
</UserControl.Resources>
Please note that this example is simplified... there are many MenuItems in my application. I am aware that I could individually name each MenuItem and use ElementName to find them, but there must be a better way.
Any help would be greatly appreciated.
UPDATE >>>
Thanks to punker76's answer, I realised that all I needed to do was to change the Image Trigger to the following:
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
Instead of trying to bind to the MenuItem.IsEnabled property with a DataTrigger, we can bind to the Image.IsEnabled property directly... this is because the when the MenuItem becomes disabled, it also disables its children. Much simpler!
try this one
<Style x:Key="MenuItemIconStyle" TargetType="{x:Type Image}">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</Style.Triggers>
</Style>
<ContextMenu x:Key="ContextMenu" DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<!-- IsEnabled="False" is only for testing (tested with kaxaml) -->
<MenuItem IsEnabled="False" Header="Open" Command="{Binding Open}" CommandParameter="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
<MenuItem.Icon>
<Image Source="http://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Disambig-dark.svg/25px-Disambig-dark.svg.png"
Style="{StaticResource MenuItemIconStyle}" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
EDIT
here is another solution that works (the button gets the DataContext) with this tip that i found:
How to Solve Execution Problems of RoutedCommands in a WPF ContextMenu
The problem was, that the commands could not be
executed, even if the CommandBinding on the parent window allowed it.
The reason is, that ContextMenus are separate windows with their own
VisualTree and LogicalTree. The reason is that the CommandManager
searches for CommandBindings within the current focus scope. If the
current focus scope has no command binding, it transfers the focus
scope to the parent focus scope. The simplest solution is to initially
set the logical focus of the parent window that is not null. When the
CommandManager searches for the parent focus scope it finds the window
and handels the CommandBinding correctly. Another solution is to
manually bind the CommandTarget to the parent ContextMenu.
<Window.Resources>
<Style x:Key="MenuItemIconStyle"
TargetType="{x:Type Image}">
<Setter Property="Width"
Value="16" />
<Setter Property="Height"
Value="16" />
<Style.Triggers>
<Trigger Property="IsEnabled"
Value="False">
<Setter Property="Opacity"
Value="0.5" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Button Content="With ContextMenu"
DataContext="{Binding ElementName=window, Path=DataContext}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Enabled"
CommandTarget="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}"
Command="{Binding Open}"
CommandParameter="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
<MenuItem.Icon>
<Image Source="http://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Disambig-dark.svg/25px-Disambig-dark.svg.png"
Style="{StaticResource MenuItemIconStyle}" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Disabled"
CommandTarget="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}"
Command="{Binding NotOpen}"
CommandParameter="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
<MenuItem.Icon>
<Image Source="http://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Disambig-dark.svg/25px-Disambig-dark.svg.png"
Style="{StaticResource MenuItemIconStyle}" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</Grid>
code behind
public partial class Window11 : Window
{
public static readonly DependencyProperty OpenProperty =
DependencyProperty.Register("Open", typeof(ICommand), typeof(Window11), new PropertyMetadata(default(ICommand)));
public static readonly DependencyProperty NotOpenProperty =
DependencyProperty.Register("NotOpen", typeof(ICommand), typeof(Window11), new PropertyMetadata(default(ICommand)));
public ICommand NotOpen {
get { return (ICommand)this.GetValue(NotOpenProperty); }
set { this.SetValue(NotOpenProperty, value); }
}
public ICommand Open {
get { return (ICommand)this.GetValue(OpenProperty); }
set { this.SetValue(OpenProperty, value); }
}
public Window11() {
this.DataContext = this;
this.InitializeComponent();
this.Open = new RoutedCommand("Open", typeof(Window11));
this.CommandBindings.Add(new CommandBinding(this.Open, null, (sender, args) =>
{
args.CanExecute = true;
}));
this.NotOpen = new RoutedCommand("NotOpen", typeof(Window11));
this.CommandBindings.Add(new CommandBinding(this.NotOpen, null, (sender, args) =>
{
args.CanExecute = false;
}));
}
}
hope this works

Disable the whole Context Menu

I know there is a way to block or now show context menu by using ContextMenuOpening event.
But I still want to show the context menu, just disable everything in it, is there a way to do it?
How can I disable all the menu item at a same time?
<DataTemplate x:Key="ItemDataTemplate">
<Grid Background="Transparent">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="New" Click="New_Click" />
<Separator />
<MenuItem Header="Duplicate" Click="Duplicate_Click"/>
<MenuItem Header="Delete" Click="Delete_Click" />
<MenuItem Header="Rename" Click="Rename_Click" />
<Separator />
<MenuItem Header="Export..." Click="Export_Click" />
<MenuItem Header="Print..."
Command="ApplicationCommands.Print"
InputGestureText="" />
<Separator />
<MenuItem Header="Properties" Click="Properties_Click" />
</ContextMenu>
</Grid.ContextMenu>
<StackPanel Orientation="Horizontal"
Margin="0,0,10,0"
HorizontalAlignment="Stretch"
Background="Transparent"
IsHitTestVisible="False">
</StackPanel>
</Grid>
</DataTemplate>
<Style x:Key="z3r0_Style_TextBox_Default" BasedOn="{x:Null}"
TargetType="{x:Type TextBox}">
<Setter Property="FontSize" Value="11"/>
<Setter Property="Background" Value="{StaticResource z3r0_SolidColorBrush_DarkerGray}"/>
<Setter Property="BorderBrush" Value="{x:Null}"/>
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="Foreground" Value="{StaticResource z3r0_SolidColorBrush_Green}"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="MinWidth" Value="10"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu IsEnabled="False" Visibility="Hidden">
</ContextMenu>
</Setter.Value>
</Setter>
For me, setting IsEnabled to False still caused a small empty context menu to be visible. After changing its visibility attribute, it was truly disabled.
IsEnabled = false on the ContextMenu?
Edit: As there appear to be closing problems when doing this i would suggest a container style:
<ContextMenu>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="IsEnabled" Value="False"/>
</Style>
</ContextMenu.ItemContainerStyle>
<MenuItem Header="Test"/>
</ContextMenu>
Of course the actual value can be bound as well but in the container style the context is the individual item, so to bind to the parent context a RelativeSource binding is necessary (unless the data object also has a connection to the parent).
The entire context menu can be disabled at the root of the ContextMenu by setting the IsEnabled property to false; there is no need to loop through each menu item in code.
<ContextMenu IsEnabled="False">
Or
<ContextMenu IsEnabled={Binding Path=SomeModelBooleanProperty}">
This will enable the context menu to be viewed, but all items will be disabled.
I used ContextMenu="{x:Null}" to disable the Context Menu in a TextBox.
Here is how I solve my question.
Still use ContextMenuOpening event. Use a loop.
This why can help me disable certain context menu item in code, but not in XAML.
private void SetContextMenuItemsEnableStatus(object sender)
{
FrameworkElement element = sender as FrameworkElement;
if (element == null)
{
return;
}
ContextMenu contextMenu = element.ContextMenu;
if (contextMenu == null)
return;
//Use this way.
//You can disable every item or certain item in context menu.
foreach (Control item in contextMenu.Items)
{
item.IsEnabled = false;
}
}
Here is how I solve my question.
<ContextMenu x:Key="TreeViewEmptyContextMenu" Width="0" Height="0"/>

Align menu in wpf

I have a vertical menu set to the left side of the window. Its items open just on (above) it and this prevents the user from having a full view of the menu when an item is open.
I want each element to open just on the right of the menu, so as to have an entire view of both the rest of menu and the opened elements. How can this be done? May be with the aid of transforms or triggers?
Here is some code:
<MenuItem Header="Maths">
<MenuItem Background="LightGray" Header="Add"/>
<MenuItem Background="LightGray" Header="Subtract"/>
<MenuItem Background="LightGray" Header="Multiply"/>
<MenuItem Background="LightGray" Header="Divide"/>
</MenuItem>
So just to be clear, the MenuItem 'Maths' above is in a WPF Menu and you have changed that Menu's ItemsPanel to be a Vertical StackPanel or something so 'Maths' is above/below other sibling menu items. If so what is happening is that the default template for MenuItem's whose role is TopLevelHeader (MenuItem that has child Items and is directly within a Menu) is such that the popup is below (or above) the menu item. You will probably want to retemplate those menu items. On hacky (and ugly alternative) is to use the template that would be used for SubmenuHeader role menu items (i.e. a MenuItem that has child Items and is within another MenuItem). e.g.
<Menu HorizontalAlignment="Left">
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<Menu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Style.Triggers>
<Trigger Property="Role" Value="TopLevelHeader">
<Setter Property="Template" Value="{DynamicResource {x:Static MenuItem.SubmenuHeaderTemplateKey}}" />
</Trigger>
<Trigger Property="Role" Value="TopLevelItem">
<Setter Property="Template" Value="{DynamicResource {x:Static MenuItem.SubmenuItemTemplateKey}}" />
</Trigger>
</Style.Triggers>
</Style>
</Menu.ItemContainerStyle>
<MenuItem Header="Just Item" />
<MenuItem Header="Maths">
<MenuItem Header="Add" />
<MenuItem Header="Subtract" />
</MenuItem>
<MenuItem Header="Misc">
<MenuItem Header="Other" />
</MenuItem>
</Menu>

WPF optical style of dynamically created MenuItem-Separator in MVVM

I have have a MenuItem that creates its sub-menu-items dynamicly from the ItemsSource-property.
For grouping, I have Separators in the menu. The separator is created for each null-entry in the ItemsSource-collection by a ControlTemplate of the MenuItem.ItemContainerStyle.
This works fine, however has the separator not the same optical style as the other separators have which are placed in a the Items-collection of a menu.
Is there a way to change the look of the separator so that it looks equal to the "normal" menu-item-separators?
Here is the code I use:
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Title}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding }" Value="{x:Null}">
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate>
<Separator /> <!-- THIS SEPARATOR IS NOT SHOWN AS COMMON MENUITEM-SEPARATORS ARE -->
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.ItemContainerStyle>
There is a Style that is declared in System.Resources with MenuItem.SeparatorStyleKey as the key. The parent MenuItem normally sets the style on children of type Separator, but since yours is a MenuItem, it won't, so you will have to do it manually:
<Separator Style="{StaticResource {x:Static MenuItem.SeparatorStyleKey}}" />
You may also want to read Bea Stollnitz's blog entry "How do I insert Separator objects in a data bound MenuItem?" for another approach.
Try wrapping the Seperator in a MenuItem
<ControlTemplate>
<MenuItem>
<MenuItem.Header>
<Separator />
</MenuItem.Header>
</MenuItem>
</ControlTemplate>

Resources