Grey out image on button when element is disabled (simple and beautiful way)

大憨熊 提交于 2019-11-30 17:10:34

As Thomas Lebrun says in his post How to gray the icon of a MenuItem ? the best way at the moment is probably to create a little class, AutoGreyableImage, which allow you to have an image that will be turn in gray automatically when the control is deactivated.

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:

Sources

You could use a pixel shader to do this automatically.

More complete version of the AutoGreyableImage by Thomas Lebrun. For anyone interested, I started using Thomas Lebruns class and ran into a couple of nullreference exceptions, as well as finding out that an image would not be disabled if the isEnabled property was set first and the source set after.

So here's the class that finally did the trick for me. À propos, you can of course add the matter of opacity into this, but I decided to leave that up to the xaml around the image.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using System.Windows.Media;

namespace MyDisabledImages
{
    /// <summary>
    /// Class used to have an image that is able to be gray when the control is not enabled.
    /// Based on the version by 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 and Source property.
            IsEnabledProperty.OverrideMetadata(typeof(AutoGreyableImage), new FrameworkPropertyMetadata(true, new PropertyChangedCallback(OnAutoGreyScaleImageIsEnabledPropertyChanged)));
            SourceProperty.OverrideMetadata(typeof(AutoGreyableImage), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnAutoGreyScaleImageSourcePropertyChanged)));
        }

        protected static AutoGreyableImage GetImageWithSource(DependencyObject source)
        {
            var image = source as AutoGreyableImage;
            if (image == null)
                return null;

            if (image.Source == null)
                return null;

            return image;
        }

        /// <summary>
        /// Called when [auto grey scale image source property changed].
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        protected static void OnAutoGreyScaleImageSourcePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs ars)
        {
            AutoGreyableImage image = GetImageWithSource(source);
            if (image != null)
                ApplyGreyScaleImage(image, image.IsEnabled);
        }

        /// <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>
        protected static void OnAutoGreyScaleImageIsEnabledPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            AutoGreyableImage image = GetImageWithSource(source);
            if (image != null)
            {
                var isEnabled = Convert.ToBoolean(args.NewValue);
                ApplyGreyScaleImage(image, isEnabled);
            }
        }

        protected static void ApplyGreyScaleImage(AutoGreyableImage autoGreyScaleImg, Boolean isEnabled)
        {
            try
            {
                if (!isEnabled)
                {
                    BitmapSource bitmapImage = null;

                    if (autoGreyScaleImg.Source is FormatConvertedBitmap)
                    {
                        // Already grey !
                        return;
                    }
                    else if (autoGreyScaleImg.Source is BitmapSource)
                    {
                        bitmapImage = (BitmapSource)autoGreyScaleImg.Source;
                    }
                    else // trying string 
                    {
                        bitmapImage = new BitmapImage(new Uri(autoGreyScaleImg.Source.ToString()));
                    }
                    FormatConvertedBitmap conv = new FormatConvertedBitmap(bitmapImage, PixelFormats.Gray32Float, null, 0);
                    autoGreyScaleImg.Source = conv;

                    // Create Opacity Mask for greyscale image as FormatConvertedBitmap does not keep transparency info
                    autoGreyScaleImg.OpacityMask = new ImageBrush(((FormatConvertedBitmap)autoGreyScaleImg.Source).Source); //equivalent to new ImageBrush(bitmapImage)
                }
                else
                {
                    if (autoGreyScaleImg.Source is FormatConvertedBitmap)
                    {
                        autoGreyScaleImg.Source = ((FormatConvertedBitmap)autoGreyScaleImg.Source).Source;
                    }
                    else if (autoGreyScaleImg.Source is BitmapSource)
                    {
                        // Should be full color already.
                        return;
                    }

                    // Reset the Opcity Mask
                    autoGreyScaleImg.OpacityMask = null;
                }
            }
            catch (Exception)
            {
                // nothin'
            }

        }

    }
}

Or the same by attached property.

<Image behaviors:GrayoutImageBehavior.GrayOutOnDisabled="True" Source="/WpfApp;component/Resources/picture.png" />

GrayoutImageBehavior:

public class GrayoutImageBehavior
{
    public static readonly DependencyProperty GrayOutOnDisabledProperty = DependencyProperty.RegisterAttached("GrayOutOnDisabled", typeof(bool), typeof(GrayoutImageBehavior), new PropertyMetadata(default(bool), OnGrayOutOnDisabledChanged));
    public static void SetGrayOutOnDisabled(Image element, bool value) { element.SetValue(GrayOutOnDisabledProperty, value); }
    public static bool GetGrayOutOnDisabled(Image element) { return (bool)element.GetValue(GrayOutOnDisabledProperty); }

    private static void OnGrayOutOnDisabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        Image image = (Image) obj;
        image.IsEnabledChanged -= OnImageIsEnabledChanged;

        if ((bool)args.NewValue)
            image.IsEnabledChanged += OnImageIsEnabledChanged;

        ToggleGrayOut(image); // initial call
    }

    private static void OnImageIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs args)
    {
        var image = (Image)sender;
        ToggleGrayOut(image);
    }

    private static void ToggleGrayOut(Image image)
    {
        try
        {
            if (image.IsEnabled)
            {
                var grayImage = image.Source as FormatConvertedBitmap;
                if (grayImage != null)
                {
                    image.Source = grayImage.Source; // Set the Source property to the original value.
                    image.OpacityMask = null; // Reset the Opacity Mask
                    image.Opacity = 1.0;
                }
            }
            else
            {
                var bitmapImage = default(BitmapImage);

                if (image.Source is BitmapImage)
                    bitmapImage = (BitmapImage) image.Source;
                else if (image.Source is BitmapSource) // assume uri source
                    bitmapImage = new BitmapImage(new Uri(image.Source.ToString()));

                if (bitmapImage != null)
                {
                    image.Source = new FormatConvertedBitmap(bitmapImage, PixelFormats.Gray32Float, null, 0); // Get the source bitmap
                    image.OpacityMask = new ImageBrush(bitmapImage); // Create Opacity Mask for grayscale image as FormatConvertedBitmap does not keep transparency info
                    image.Opacity = 0.3; // optional: lower opacity
                }
            }
        }
        catch (Exception ex)
        {
            LogicLogger.WriteLogEntry("Converting image to grayscale failed", LogLevel.Debug, false, ex);
        }
    }
}

I've changed GrayoutImageBehavior so that there won't be memory leaks of the gray image, caused when the user changes the image 'enable' property from true to false and vice versa several times. I've added the DependencyProperty to hold the gray image and the gray image opacity mask.

 public class GrayoutImageBehavior
 {
     public static readonly DependencyProperty GrayOutOnDisabledProperty = DependencyProperty.RegisterAttached("GrayOutOnDisabled", typeof(bool), typeof(GrayoutImageBehavior), new PropertyMetadata(default(bool), OnGrayOutOnDisabledChanged));
     public static void SetGrayOutOnDisabled(Image element, bool value) { element.SetValue(GrayOutOnDisabledProperty, value); }
     public static bool GetGrayOutOnDisabled(Image element) { return (bool)element.GetValue(GrayOutOnDisabledProperty); }

     private static DependencyProperty GrayImageProperty = DependencyProperty.RegisterAttached("GrayImage", typeof(FormatConvertedBitmap), typeof(GrayoutImageBehavior));
     private static void SetGrayImage(Image element, FormatConvertedBitmap value) { element.SetValue(GrayImageProperty, value); }
     private static FormatConvertedBitmap GetGrayImage(Image element) { return (FormatConvertedBitmap)element.GetValue(GrayImageProperty); }

     private static DependencyProperty GrayImageOpacityMaskProperty = DependencyProperty.RegisterAttached("GrayImageOpacityMask", typeof(Brush), typeof(GrayoutImageBehavior));
     private static void SetGrayImageOpacityMask(Image element, Brush value) { element.SetValue(GrayImageOpacityMaskProperty, value); }
     private static Brush GetGrayImageOpacityMask(Image element) { return (Brush)element.GetValue(GrayImageOpacityMaskProperty); }

     private static void OnGrayOutOnDisabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
     {
         Image image = (Image)obj;
         image.IsEnabledChanged -= OnImageIsEnabledChanged;

         if ((bool)args.NewValue)
             image.IsEnabledChanged += OnImageIsEnabledChanged;

         ToggleGrayOut(image); // initial call
     }

     private static void OnImageIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs args)
     {
         var image = (Image)sender;
         ToggleGrayOut(image);
     }

     private static void ToggleGrayOut(Image image)
     {
         try
         {
             if (image.IsEnabled)
             {
                 var grayImage = image.Source as FormatConvertedBitmap;
                 if (grayImage != null)
                 {
                     image.Source = grayImage.Source; // Set the Source property to the original value.
                     image.OpacityMask = null; // Reset the Opacity Mask
                     //image.Opacity = 1.0;
                 }
             }
             else
             {
                 FormatConvertedBitmap grayImage = GetGrayImage(image);
                 Brush grayOpacityMask = GetGrayImageOpacityMask(image);

                 if(grayImage == null)
                 {
                     var bitmapImage = default(BitmapImage);

                     if (image.Source is BitmapImage)
                         bitmapImage = (BitmapImage)image.Source;
                     else if (image.Source is BitmapSource) // assume uri source
                         bitmapImage = new BitmapImage(new Uri(image.Source.ToString()));

                     if (bitmapImage != null)
                     {
                         grayImage = new FormatConvertedBitmap(bitmapImage, PixelFormats.Gray32Float, null, 0); // Get the source bitmap
                         SetGrayImage(image, grayImage);
                         grayOpacityMask = new ImageBrush(bitmapImage); // Create Opacity Mask for grayscale image as FormatConvertedBitmap does not keep transparency info
                         SetGrayImageOpacityMask(image, grayOpacityMask);
                         //image.Opacity = 0.3; // optional: lower opacity
                     }
                 }

                 image.Source = grayImage;
                 image.OpacityMask = grayOpacityMask;
             }
         }
         catch (Exception ex)
         {
             //LogicLogger.WriteLogEntry("Converting image to grayscale failed", LogLevel.Debug, false, ex);
         }
     }
 }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!