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

5

22

I want to grey out my images (on the buttons) when the buttons are disabled. When I have text (no images) on the button, the text is greyed out (With Images as Button Content they are not grey out). Is there any simple and beautiful way to do that?

This is my xaml file:

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <ToolBarTray VerticalAlignment="Top" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" IsLocked="true" Grid.Row="0">
            <ToolBar Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Band="1" BandIndex="1">
                <Button Command="{Binding Button1}" RenderOptions.BitmapScalingMode="NearestNeighbor">
                    <Button.Content>
                        <Image Source="open.ico"></Image>
                    </Button.Content>
                </Button>
                <Button Command="{Binding Button2}" RenderOptions.BitmapScalingMode="NearestNeighbor">
                    <Button.Content>
                        <Image Source="open.ico"></Image>
                    </Button.Content>
                </Button>
            </ToolBar>
        </ToolBarTray>
    </Grid>
</Window>

and this is my code behind file:

public partial class MainWindow : Window
{
    private RelayCommand _button1;
    private RelayCommand _button2;

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }

    public ICommand Button1
    {
        get
        {
            if (_button1 == null)
            {
                _button1 = new RelayCommand(Button1E, Button1C);
            }
            return _button1;
        }
    }

    public ICommand Button2
    {
        get
        {
            if (_button2 == null)
            {
                _button2 = new RelayCommand(Button2E, Button2C);
            }
            return _button2;
        }
    }

    public void Button1E(object parameter)
    {
        Trace.WriteLine("Button 1");
    }

    public bool Button1C(object parameter)
    {
        return true;
    }

    public void Button2E(object parameter)
    {
        Trace.WriteLine("Button 2");
    }

    public bool Button2C(object parameter)
    {
        return false;
    }
}
Glossator answered 3/7, 2012 at 6:29 Comment(1)
I don't see any attempt at tackling the problem in the codeRemontant
G
18

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:

Result of applying class to perform grayscale rendering when disabled

Sources

Glossator answered 6/7, 2012 at 6:19 Comment(3)
Dead link? I went through the trouble of registering to the forum and answering ridiculous anti-robot questions in French, and it shows be a blank page. Not fair!Rhoads
@IvanKrivyakov I've updated the dead link to the original article.Glossator
That worked. Merci! Thank you for the fast answer. It would be nice though if for the sake of other people you put it somewhere more accessible, like DropBox, or github. Most WPF programmers don't know French and will have difficulty registering to developpez.net.Rhoads
R
6

You could use a pixel shader to do this automatically.

Ripsaw answered 3/7, 2012 at 8:15 Comment(1)
nice one, I did not know about this effect! I wish I'd read that 2 years ago :pGlossator
B
5

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);
        }
    }
}
Barrister answered 26/8, 2014 at 17:16 Comment(1)
Nice! We use this successfully for a few years now. However, I just had to make a small extension to use it on a data-bound ImageSource (where it changes after ToggleGrayOut) Add this to OnGrayOutOnDisabledChanged: ` var imageSourceDescriptor = DependencyPropertyDescriptor.FromProperty(Image.SourceProperty, typeof(Image)); imageSourceDescriptor.RemoveValueChanged(image, OnImageSourceChanged); if ((bool)e.NewValue) { imageSourceDescriptor.AddValueChanged(image, OnImageSourceChanged); } `Volatilize
H
3

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'
            }

        }

    }
}
Helprin answered 1/7, 2014 at 13:50 Comment(0)
P
3

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);
         }
     }
 }
Pollypollyanna answered 6/12, 2016 at 6:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.