Tuesday, May 26, 2009

How to make images look disabled (greyed out) when the Button or MenuItem they are within is disabled in WPF

By default in WPF image controls that are within disabled menus and buttons will not be greyed out. One common way to achieve a standard look is to apply a style to the image that will cause it to be partially transparent when its parent is disabled. You create a style with TargetType of Image that is a Setter for the Opacity property with a Style.Trigger bound to the IsEnabled property of the image's parent (via RelativeSource). (Code later...) I made two triggers initially, as some of my Images are in Buttons and some are in MenuItems.

Just looking at my output window, I noticed a whole bunch of errors when the UI was coming up, some like this:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 
'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.MenuItem', 
AncestorLevel='1''. BindingExpression:Path=IsEnabled; DataItem=null; target 
element is 'Image' (Name=''); target property is 'NoTarget' (type 'Object')

And others like this:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 
'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.Button', 
AncestorLevel='1''. BindingExpression:Path=IsEnabled; DataItem=null; target 
element is 'Image' (Name=''); target property is 'NoTarget' (type 'Object') 

Thinking about it for a second, you can see that both of the style triggers will get applied to each image. For each image one will work and one will fail - there can only be one type for the parent element, Button or MenuItem not both. It is a silent failure so could just be lived with but I wanted to fix it.

First I was trying to find a way to choose which trigger to apply depending on the type of the parent*. I know you could remove the TargetType from the style and apply it as a named style to each element, but that just becomes a mess. The whole idea of this solution is to make at as simple to use as possible - basically just letting it modify the behaviour of all images. The real solution of course is to realise that the IsEnabled property is not a member of Button nor MenuItem directly, but it is on the UIElement class.

The fix then is to have only one Style.Trigger with the AncestorType of UIElement:

<Style TargetType="{x:Type Image}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type UIElement}, AncestorLevel=1}, Path=IsEnabled}" Value="False">
            <Setter Property="Opacity" Value="0.3"></Setter>
        </DataTrigger>
    </Style.Triggers>
</Style> 

* I would still like to know how to do this, but that will have to wait until another time when I really do need to do it. No time!

2 comments:

  1. Thank you so much it was a great support, now to make images look disabled (greyed out) when the button or menuitem they are within is disabled in wpf is simple and easy utilizing your advice. Thanks

    ReplyDelete
  2. Why not use

    Binding RelativeSource={RelativeSource Mode=Self}, Path=IsEnabled}

    So

    ReplyDelete