Search code examples
wpfbindingmenuitem

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!


Solution

  • 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