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 MenuItem
s 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
Image
s 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 MenuItem
s 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