[WPF] How to gray the icon of a MenuItem ?

Here is a question that a friend of mine asked me recently. Indeed, as a beginner with WPF, it thought that setting the property IsEnabled = false, on the MenuItem, will disable it. So, the following code:

 

<MenuItem Header="Edit">

    <MenuItem x:Name="miPaste"

              Header="Paste" IsEnabled="False" >

        <MenuItem.Icon>

            <Image Source="pack://application:,,,/Images/Paste.png"

/>

        </MenuItem.Icon>

    </MenuItem>

</MenuItem>

 

Does not deactivate the image, as you can see here:

 

To correct this, you have 2 choices:

  • Use a second gray image which will be specified as the source for the control if this one is deactivated
  • Use the class FormatConvertedBitmap to create a gray image

 

So I’ve created a little class, AutoGreyableImage, which allow you to have an image that will be turn in gray automatically when the control is desactivated.

Here is how you can use it:

<MenuItem Header="Edit">

    <MenuItem x:Name="miPaste"

              Header="Paste">

        <MenuItem.Icon>

            <local:AutoGreyableImage Source="pack://application:,,,/Images/Paste.png"

                                                   />

        </MenuItem.Icon>

    </MenuItem>

</MenuItem>

 

And here is the implementation:

/// <summary>

/// Class used to have an image that is able to be gray when the control is not enabled.

/// Author: Thomas LEBRUN (http://blogs.developpeur.org/tom)

/// </summary>

public class AutoGreyableImage : Image

{

    /// <summary>

    /// Initializes a new instance of the <see cref="AutoGreyableImage"/> class.

    /// </summary>

    static AutoGreyableImage()

    {

        // Override the metadata of the IsEnabled property.

        IsEnabledProperty.OverrideMetadata(typeof(AutoGreyableImage), new FrameworkPropertyMetadata(true, new PropertyChangedCallback(OnAutoGreyScaleImageIsEnabledPropertyChanged)));

    }

    /// <summary>

    /// Called when [auto grey scale image is enabled property changed].

    /// </summary>

    /// <param name="source">The source.</param>

    /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>

    private static void OnAutoGreyScaleImageIsEnabledPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)

    {

        var autoGreyScaleImg = source as AutoGreyableImage;

        var isEnable = Convert.ToBoolean(args.NewValue);

        if (autoGreyScaleImg != null)

        {

            if (!isEnable)

            {

                // Get the source bitmap

                var bitmapImage = new BitmapImage(new Uri(autoGreyScaleImg.Source.ToString()));

                // Convert it to Gray

                autoGreyScaleImg.Source = new FormatConvertedBitmap(bitmapImage, PixelFormats.Gray32Float, null, 0);

                // Create Opacity Mask for greyscale image as FormatConvertedBitmap does not keep transparency info

                autoGreyScaleImg.OpacityMask = new ImageBrush(bitmapImage);

            }

            else

            {

                // Set the Source property to the original value.

                autoGreyScaleImg.Source = ((FormatConvertedBitmap) autoGreyScaleImg.Source).Source;

                // Reset the Opcity Mask

                autoGreyScaleImg.OpacityMask = null;

            }

        }

    }

}

 

Here is the result:

You can download the sources (and the demo) here: http://morpheus.developpez.com/wpf/DisableMenuItemIcon.zip/wpf/DisableMenuItemIcon.zip

Happy coding !

 

17 Comments

  • I've heard of Fitt's Law, but this is ridiculous. ;)

  • Neat, and simple, cheers.

  • Simple to use...thank you!

  • Great class, but one thing: the source doesn't say static but public, which doesn't work. I see you corrected it on the site.

  • Awesome. Exactly what I was looking for. Thanks you!

  • I have one important addition to your code.
    Replace:
    var bitmapImage = new BitmapImage(new Uri(autoGreyScaleImg.Source.ToString()));
    With:
    BitmapSource bitmapImage = null;

    if (autoGreyScaleImg.Source is BitmapSource)
    {
    bitmapImage = (BitmapSource)autoGreyScaleImg.Source;
    }
    else
    {
    bitmapImage = new BitmapImage(new Uri(autoGreyScaleImg.Source.ToString()));
    }

  • Very good.
    But it seems to function with only one button-image in the window. When I try to insert the second button in the toolbar, Visual Studio 2010 throw the exception "PropertyMetadata is already registered for type 'AutoGreyableImage'".

  • @Stefano:

    use this instead:

    public AutoGreyableImage() : base() {}

    static AutoGreyableImage()
    {
    // Override the metadata of the IsEnabled property.
    IsEnabledProperty.OverrideMetadata(typeof(AutoGreyableImage), new FrameworkPropertyMetadata(true, new PropertyChangedCallback(OnAutoGreyScaleImageIsEnabledPropertyChanged)));
    }

  • Very good.

    and, is there any way to achieve it via ControlTemplate ?

  • Hello Everyone! I like watching BBC Football online.

  • roayxxtgmkr ah lzxf

  • I enjoy your wp web template, where did you get a hold of it?

  • My trick, a lot easier.









    Now put that style to all your images you want to look grayish (In toolbars, MenuItems, etc)

  • Hi, it is one of the great tips for WPF. Thank you sharing it.

    However, I met an exception when I changed the windows design from "Windows XP Style" to "Windows Classic Style" (..and vice versa).

    Steps:
    1. Run an application which is implemented AutoGreyableImage.
    * the application seems to contain a disabled button(/image) at first.

    2. Open "Display Property" dialog box from desktop.
    3. Select "Design" tab
    4. Change style at "Window and Button" combobox.

    Then, null exception occurred. It is because that autoGreyScaleImage.Source is null.

    I can't find out the reason of this yet.
    Does anyone fix this?

  • Hi, it seems that I fixed my issue.

    I put ToggleButton element in my xaml.
    The xaml was written like below.
    -----







    -----

    I changed using Contet instead of ContentTemplate. Like below.

    -----





    -----

    ContentControl.ContentTemplate declaration seems to be initialzed when Windows desing changed.

  • Exactly where there does exist romantic relationship not including romance, it will likely be romance not including romantic relationship.

  • I really like your blog.. very nice colors & theme. Did you design this website yourself or did you hire someone to do it for you? Plz respond as I'm looking to create my own blog and would like to know where u got this from. appreciate it

Comments have been disabled for this content.