Category: WPF


UniformStackPanel

For a personal application I’m writing, I needed a panel that stacks my items, but unlike the regular StackPanel, which doesn’t limit the size of the items, I need mine to divide its items evenly among its available space.

Also, I needed some of the items to be able to receive their desired size and not the size like the rest of the items, so I can use an Expander control, make them collapsed and make them using less space.

This panel is usually useful when creating navigation buttons like Outlook has (the buttons to switch between Mail, Calendar, Notes and so on).

For example, if this UniformStackPanel has 300 units to display 3 items, it will give each one 100 units.
If I set the second one as UniformStackPanel.IsAutoSized=”True” and it only needs 20 units, then the first and third items will have a size of 140 units, and the second item will have a size of 20 units.

Notice that “size” could be either height or width, depends on the orientation of the panel.

Also, notice that I’m using the Boolean boxes from my previous post: https://xstatic2.wordpress.com/2011/10/21/tip-improving-boolean-dependency-properties-performance/.

Here is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
 
namespace Panels
{
    public class UniformStackPanel : Panel
    {
        public static bool GetIsAutoSized(UIElement element)
        {
            if (element == null)
            {
                throw new ArgumentNullException(“element”);
            }
 
            return (bool)element.GetValue(IsAutoSizedProperty);
        }
 
        public static void SetIsAutoSized(DependencyObject element, bool value)
        {
            if (element == null)
            {
                throw new ArgumentNullException(“element”);
            }
 
            element.SetValue(IsAutoSizedProperty, BooleanBoxes.Box(value));
        }
        public static readonly DependencyProperty IsAutoSizedProperty =
            DependencyProperty.RegisterAttached(“IsAutoSized”, typeof(bool), typeof(UniformStackPanel), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsParentArrange | FrameworkPropertyMetadataOptions.AffectsParentMeasure));
 
 
        public Orientation Orientation
        {
            get { return (Orientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }
 
        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.Register(“Orientation”, typeof(Orientation), typeof(UniformStackPanel), new FrameworkPropertyMetadata(Orientation.Vertical, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));
 
 
 
        protected override Size MeasureOverride(Size availableSize)
        {
            _autoSizeSum = 0d;
            double maxElementWidth = 0d;
            int count = InternalChildren.Count;
            Orientation orientation = Orientation;
            List<UIElement> fixedSizedElements = new List<UIElement>(InternalChildren.Count);
 
            for (int i = 0; i < count; i++)
            {
                UIElement element = InternalChildren[i];
 
                if (element.Visibility == Visibility.Collapsed)
                {
                    continue;
                }
 
                bool isElementAutoSized = GetIsAutoSized(element);
 
                if (isElementAutoSized)
                {
                    element.Measure(availableSize);
                    double autoSizedValue = element.DesiredSize.Height;
 
                    if (orientation == Orientation.Horizontal)
                    {
                        autoSizedValue = element.DesiredSize.Width;
                    }
 
                    _autoSizeSum += autoSizedValue;
                }
                else
                {
                    fixedSizedElements.Add(element);
                }
 
                double elementWidth = element.DesiredSize.Width;
 
                if (orientation == Orientation.Horizontal)
                {
                    elementWidth = element.DesiredSize.Height;
                }
 
                maxElementWidth = Math.Max(maxElementWidth, elementWidth);
            }
 
            _fixedSizedElementsCount = fixedSizedElements.Count;
 
            double fixedSizeAvailableSpace = availableSize.Height – _autoSizeSum;
 
            if (orientation == System.Windows.Controls.Orientation.Horizontal)
            {
                fixedSizeAvailableSpace = availableSize.Width – _autoSizeSum;
            }
 
            double singleFixedSizeElementHeight = fixedSizeAvailableSpace / (double)_fixedSizedElementsCount;
 
            Size singleFixedSizeElementSize = Size.Empty;
 
            if (orientation == Orientation.Vertical)
            {
                singleFixedSizeElementSize = new Size(availableSize.Width, singleFixedSizeElementHeight);
            }
            else
            {
                singleFixedSizeElementSize = new Size(singleFixedSizeElementHeight, availableSize.Height);
            }
 
            for (int i = 0; i < fixedSizedElements.Count; i++)
            {
                UIElement element = fixedSizedElements[i];
                element.Measure(singleFixedSizeElementSize);
            }
 
            return new Size(maxElementWidth, singleFixedSizeElementHeight + _autoSizeSum);
        }
 
        double _autoSizeSum;
        int _fixedSizedElementsCount;
 
        protected override Size ArrangeOverride(Size finalSize)
        {
            double x = 0d;
            double y = 0d;
            Orientation orientation = Orientation;
 
            Size availableSize = finalSize;
 
            if (orientation == System.Windows.Controls.Orientation.Horizontal)
            {
                availableSize = newSize(finalSize.Height, finalSize.Width);
            }
 
            double fixedSizeAvailableSpace = Math.Max(0d, availableSize.Height – _autoSizeSum);
 
            foreach (UIElement element in InternalChildren)
            {
                Size elementSize = element.DesiredSize;
 
                bool isElementAutoSized = GetIsAutoSized(element);
 
                if (!isElementAutoSized)
                {
                    if (orientation == Orientation.Vertical)
                    {
                        elementSize = new Size(elementSize.Width, fixedSizeAvailableSpace / (double)_fixedSizedElementsCount);
                    }
                    else
                    {
                        elementSize = new Size(fixedSizeAvailableSpace / (double)_fixedSizedElementsCount, elementSize.Height);
                    }
                }
 
                Rect finalRect = new Rect(x, y, Math.Max(0d, finalSize.Width – x), Math.Max(0d, finalSize.Height – y));
 
                if (element.Visibility != Visibility.Collapsed)
                {
                    if (orientation == System.Windows.Controls.Orientation.Vertical)
                    {
                        y += elementSize.Height;
                        finalRect.Height = elementSize.Height;
                    }
                    else
                    {
                        x += elementSize.Width;
                        finalRect.Width = elementSize.Width;
                    }
 
                }
 
                element.Arrange(finalRect);
            }
 
            return finalSize;
        }
    }
}

If you’re using WPF, you probably encountered this obscure message of "Cannot find governing FrameworkElement or FrameworkContentElement for target element.", and found that your binding doesn’t work.

This message means that the binding engine could not locate the object of which to bind to.
This usually happens when the data context hierarchy chain is broken by a non-FrameworkElement object. Interestingly enough, this chain breaking also makes the search by ElementName broken.

For example, place a FrameworkElement inside a binding’s ConverterParameter property, and try to bind one of the element’s properties. VS will write in its output window the error message above.
You can place the element like this:
<Button.Content>
    <Binding Converter="{StaticResource someConverter}">
        <Binding.ConverterParameter>
            <!–Here is the binding that shouldn’t work: –>
            <FrameworkElement DataContext="{Binding ElementName=myElement, Path=Content}"/>
        </Binding.ConverterParameter>
    </Binding>
</Button.Content>

You can overcome this problem by using the x:Reference markup extension (or its improved version I wrote about in one of my previous posts). You can a name to the element you want to bind to, and then let the binding engine find it by using the Source property, like this:

{Binding Source={x:Reference myElement}, Path=Width}

How can you name your target element? You can simply use the x:Name directive, or the more elegant "Name" property, which is mapped to the x:Name directive by FrameworkElement with the RuntimeNamePropertyAttribute attribute.

Drop-Down Button

Today I want to talk about a useful control that WPF is missing (and to be honest, WinForms was also missing it): A Drop-Down Button, or (according to Microsoft’s UX Guide) a Menu Button.

Sadly, I didn’t find a satisfying implementation on the internet, and when I say "satisfying" I mean lightweight, with very little code, and most of the behavior in XAML.

I’ve has several attempts to do this, but I think my final attempt was the most satisfying.

In this attempt (the final one) I was using the technique that the ToolBar is using to apply its child controls their special template (you can find it with Reflector under ToolBar.PrepareContainerForItemOverride): The FrameworkElement.SetResourceReference(DependencyProperty, object) method, which searches for the resource by the key in the second argument, and assigns it to the dependency property from the first argument. It’s actually just like writing in XAML: <… DependencyProperty="{DynamicResource key}"/>.

 

My MenuButton derives from ToggleButton for maximum simplicity.

I’ve exposed 3 dependency properties:

  • DropDown (of type ContextMenu): In this property you can place your own context menu that will show the items of the button.
  • IsDropDownOpen (of type Boolean): The property indicates whether the drop-down is open or not. Notice that I’m using the BooleanBoxes technique from my previous post.
  • DropDownPlacement (of type PlacementMode): Lets you decide where to locate the opened drop-down.

I also have an internal attached property (named ParentMenuButton) in order to give the context menu items access to the MenuButton from the style I’m applying them, and a public static property (named MenuDropDownStyleKey) that defines the resource key for the ContextMenu style I’m applying.

 

The basics of the control are very simple: When a context menu is assigned to the DropDown property, I’m applying it a style using the SetResourceReference method. This style is defined in the Generic.xaml file, and its resource key is taken from the static MenuDropDownStyleKey property.

The rest resides in the default style in the XAML.
The default style binds the TobbleButton’s IsChecked property to the IsDropDownOpen property.
Since most of the drop-down buttons will probably have a downwards arrow, I added a default ContentTemplate that adds this arrow.

Finally, I created a style for the context menu that binds its IsOpen property to the IsDropDOwnOpen property, binds the placement, the MinWidth (to make the drop-down’s width at least as the width of the MenuButton’s), the data context and so on.

Also, I’ve created a style for the button inside a ToolBar. Now I could derive from ToolBar and override the PrepareContainerForItemOverride method to apply the style on the ToolBar, but I preferred to create a static class named ToolBarStyleKeys with a static MenuButtonStyleKey property that defines the key for the ToolBar-style and apply the style with this key inside a tool-bar.

 

I hope this post was helpful.

Haim.

 

The code of the MenuButton:

public class MenuButton : ToggleButton
    {
        private static ResourceKey _dropDownStyleKey;

        static MenuButton()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MenuButton), new FrameworkPropertyMetadata(typeof(MenuButton)));
            _dropDownStyleKey = new ComponentResourceKey(typeof(MenuButton), "MenuButton.ContextMenu style key");
        }

        public static ResourceKey MenuDropDownStyleKey
        {
            get { return _dropDownStyleKey; }
        }

        /// <summary>
        /// Gets or sets he drop-down menu of the button.
        /// </summary>
        public ContextMenu DropDown
        {
            get { return (ContextMenu)GetValue(DropDownProperty); }
            set { SetValue(DropDownProperty, value); }
        }

        public static readonly DependencyProperty DropDownProperty =
            DependencyProperty.Register("DropDown", typeof(ContextMenu), typeof(MenuButton), new UIPropertyMetadata(OnDropDownProeprtyChanged));


        private static void OnDropDownProeprtyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MenuButton sender = (MenuButton)d;
            sender.OnDropDownChanged(e.OldValue as ContextMenu, e.NewValue as ContextMenu);
        }

        protected virtual void OnDropDownChanged(ContextMenu oldMenu, ContextMenu newMenu)
        {
            if (oldMenu != null)
            {
                oldMenu.SetResourceReference(ContextMenu.StyleProperty, ContextMenu.DefaultStyleKeyProperty);
                oldMenu.SetValue(FrameworkElement.DefaultStyleKeyProperty, ContextMenu.DefaultStyleKeyProperty.DefaultMetadata.DefaultValue);
                SetParentMenuButton(oldMenu, null);
            }

            if (newMenu != null)
            {
                SetParentMenuButton(newMenu, this);

                if (DependencyPropertyHelper.GetValueSource(newMenu, FrameworkElement.StyleProperty).BaseValueSource <= BaseValueSource.ImplicitStyleReference)
                {
                    newMenu.SetResourceReference(FrameworkElement.StyleProperty, _dropDownStyleKey);
                }

                newMenu.SetValue(FrameworkElement.DefaultStyleKeyProperty, _dropDownStyleKey);
            }
        }

        /// <summary>
        /// Gets or sets the whether the drop-down menu is open or not.
        /// </summary>
        public bool IsDropDownOpen
        {
            get { return (bool)GetValue(IsDropDownOpenProperty); }
            set { SetValue(IsDropDownOpenProperty, BooleanBoxes.Box(value)); }
        }

        public static readonly DependencyProperty IsDropDownOpenProperty =
            DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(MenuButton), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));


        /// <summary>
        /// Gets or sets the placement of the drop-down menu.
        /// </summary>
        public PlacementMode DropDownPlacement
        {
            get { return (PlacementMode)GetValue(DropDownPlacementProperty); }
            set { SetValue(DropDownPlacementProperty, value); }
        }

        public static readonly DependencyProperty DropDownPlacementProperty =
            DependencyProperty.Register("DropDownPlacement", typeof(PlacementMode), typeof(MenuButton), new UIPropertyMetadata(PlacementMode.Bottom));



        internal static MenuButton GetParentMenuButton(ContextMenu obj)
        {
            return (MenuButton)obj.GetValue(ParentMenuButtonProperty);
        }

        internal static void SetParentMenuButton(ContextMenu obj, MenuButton value)
        {
            obj.SetValue(ParentMenuButtonProperty, value);
        }

        // Using a DependencyProperty as the backing store for MenuButtonParent.  This enables animation, styling, binding, etc...
        internal static readonly DependencyProperty ParentMenuButtonProperty =
            DependencyProperty.RegisterAttached("ParentMenuButton", typeof(MenuButton), typeof(MenuButton), new UIPropertyMetadata());


    }

The XAML (in the Generic.XAML):

    <!--MenuButton:-->
    <Style TargetType="{x:Type local:MenuButton}" BasedOn="{StaticResource {x:Type ToggleButton}}">
        <Setter Property="IsChecked" Value="{Binding RelativeSource={RelativeSource Self}, Path=IsDropDownOpen, Mode=TwoWay}"/>
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Grid Margin="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:MenuButton}, Path=Padding}">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition Width="15"/>
                        </Grid.ColumnDefinitions>

                        <ContentControl Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:MenuButton}, Path=Content}"/>
                        <Path Data="M0,0L3,3 6,0z" Fill="{TemplateBinding Foreground}" Margin="2,0" IsHitTestVisible="False" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="1"/>
                    </Grid>
                </DataTemplate>
            </Setter.Value>
        </Setter>

        <Style.Triggers>
            <Trigger Property="DropDown" Value="{x:Null}">
                <Setter Property="IsEnabled" Value="False"/>
            </Trigger>
            <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=DropDown.HasItems}" Value="False">
                <Setter Property="IsEnabled" Value="False"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>

    <!--DropDown for MenuButton:-->
    <Style x:Key="{x:Static local:MenuButton.MenuDropDownStyleKey}" TargetType="ContextMenu" BasedOn="{StaticResource {x:Type ContextMenu}}">
        <Setter Property="IsOpen" Value="{Binding RelativeSource={RelativeSource Self}, Path=(local:MenuButton.ParentMenuButton).IsDropDownOpen}"/>
        <Setter Property="MinWidth" Value="{Binding RelativeSource={RelativeSource Self}, Path=(local:MenuButton.ParentMenuButton).ActualWidth}"/>
        <Setter Property="Placement" Value="{Binding RelativeSource={RelativeSource Self}, Path=(local:MenuButton.ParentMenuButton).DropDownPlacement}"/>
        <Setter Property="PlacementTarget" Value="{Binding RelativeSource={RelativeSource Self}, Path=(local:MenuButton.ParentMenuButton)}"/>
        <Setter Property="DataContext" Value="{Binding RelativeSource={RelativeSource Self}, Path=(local:MenuButton.ParentMenuButton).DataContext}"/>
    </Style>

    <!--MenuButton on ToolBar:-->
    <Style x:Key="{x:Static styles:ToolBarStyleKeys.MenuButtonStyleKey}" TargetType="{x:Type local:MenuButton}" BasedOn="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}">
        <Setter Property="IsChecked" Value="{Binding RelativeSource={RelativeSource Self}, Path=IsDropDownOpen, Mode=TwoWay}"/>
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Grid Margin="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:MenuButton}, Path=Padding}">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition Width="15"/>
                        </Grid.ColumnDefinitions>

                        <ContentControl Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:MenuButton}, Path=Content}"/>
                        <Path Data="M0,0L3,3 6,0z" Fill="{TemplateBinding Foreground}" Margin="2,0" IsHitTestVisible="False" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="1"/>
                    </Grid>
                </DataTemplate>
            </Setter.Value>
        </Setter>

        <Style.Triggers>
            <Trigger Property="DropDown" Value="{x:Null}">
                <Setter Property="IsEnabled" Value="False"/>
            </Trigger>
            <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=DropDown.HasItems}" Value="False">
                <Setter Property="IsEnabled" Value="False"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>


The ToolBarStyleKeys class:

public static class ToolBarStyleKeys
    {
        private static ResourceKey _menuButtonStyleKey;

        static ToolBarStyleKeys()
        {
            _menuButtonStyleKey = new ComponentResourceKey(typeof(ToolBarStyleKeys), "ToolBarStyleKeys.MenuButton style key");
        }

        public static ResourceKey MenuButtonStyleKey
        {
            get { return _menuButtonStyleKey; }
        }
    }

Dependency properties aren’t type-safe and aren’t generic. That’s because when they were designed, generics weren’t commonly used in c# (remember that WPF was first designed around the release of .Net 2.0 which presented the generics).

For this reason, the API of dependency properties is based on the Object class, which is a reference type. When using dependency properties for value types, we encounter a lot of boxing and unboxing.

According to Microsoft (in the link), the performance impact of boxing and unboxing is fairly big (more information can be found here). What Microsoft did in WPF, was creating (sadly internal) predefined boxed versions of commonly used value-types like Boolean, Size struct, Visibility enum and FillRule enum.

I recommend using the same mechanism, at least for Booleans, as this type is used for a lot of developers’ dependency properties.

The basic idea used for Booleans (and also the Visibility and FillRule enums; The Size struct is implemented slightly different) is to create a static BooleanBoxes class that has the following public static members:

  • A field for a predefined boxed "false" value.
  • A field for a predefined boxed "true" value.
  • A method named Box(boolean) that receives a regular Boolean, and returns its boxed representation.

The reason the fields are public, is to use them for registering the default value of the dependency properties, which is of type Object as well.

The Box method will be mainly used in the setter of your wrapping .Net property, inside the DependencyObject.SetValue method. The getter of the property will not use an "unbox" method because we can’t really know if two boxed Booleans are equal without unboxing them, and we can’t prevent someone from setting the property’s value directly from the DependencyObject.SetValue instead of our setter.

You can see these boxing classes with Reflector or IL Spy under MS.Internal.KnownBoxes (inside WindowsBase, PresentationCore and PresentationFramework).

Here is the code of the class:

/// <summary>
/// Helps boxing Booolean values.
/// </summary>
public static class BooleanBoxes
{
    /// <summary>
    /// Gets a boxed representation for Boolean's "true" value.
    /// </summary>
    public static readonly object TrueBox;

    /// <summary>
    /// Gets a boxed representation for Boolean's "false" value.
    /// </summary>
    public static readonly object FalseBox;

    /// <summary>
    /// Initializes the <see cref="BooleanBoxes"/> class.
    /// </summary>
    static BooleanBoxes()
    {
        TrueBox = true;
        FalseBox = false;
    }

    /// <summary>
    /// Returns a boxed representation for the specified Boolean value.
    /// </summary>
    /// <param name="value">The value to box.</param>
    /// <returns></returns>
    public static object Box(bool value)
    {
        if (value)
        {
            return TrueBox;
        }

        return FalseBox;
    }
}

And here is how I might use it:

public bool ValidateNames
{
    get { return (bool)GetValue(ValidateNamesProperty); }
    set { SetValue(ValidateNamesProperty, BooleanBoxes.Box(value)); }
}

public static readonly DependencyProperty ValidateNamesProperty =
    DependencyProperty.Register("ValidateNames", typeof(bool), typeof(MyClass), new UIPropertyMetadata(BooleanBoxes.TrueBox));

WPF isn’t perfect. yet.

I was trying the other day to make a button with an image look like it’s disabled, but I couldn’t make the image gray.

I searched the internet for a solution but couldn’t find anything that satisfied me.
Don’t get me wrong, some of the solutions did get the job done, but I have this problem that I must have everything in the most elegant way. Cest la vie.

I’ve decided to find a solution myself, and I didn’t want to limit myself only to the gray color, and to the "IsEnabled false" state because I was involved in a project that also required displaying button images in a bluish hue until the mouse if over them, and only than present them in full color.

One of the approaches that I considered was using the FormatConvertedBitmap class inside the XAML, as the Image.Source value, but WPF’s designer doesn’t seem to work with it (no image is shown), and this class lets me convert only to a gray scale image and not to colors other than gray.

Another approach I saw was of Microsoft in Visual Studio 2010. As you know, the UI of Visual Studio 2010 is written in WPF so I wanted to see how they fixed the problem. I saw (using Reflector, of course) that they created a GrayscaleImageConverter. On one hand the usage of this converter looks cumbersome because there is not a direct way to activate it when the image is disabled, and will probably force me to use a style; but on the other hand it does allow me to set a color other than gray, although it was written in a very limiting way (I guess VS doesn’t need more then 128×128 images with different types of image formats).

I’ve decided to take a different approach: First, I created my own MonotoneImage class which derives from the original Image class, and I rewrote the OnRender method to change the hue of the image.

I’ve created 2 dependency properties: BiasColor (of type Color) and IsFullColor (of type Boolean).

I was thinking a lot about the IsFullColor property. On my first attempt I was using the IsEnabled property to decide if to draw the image in gray, but than I thought it might limit me when I’ll use it with the "mouse over" scenario, so I’ve decided to create the IsFullColor property and bind the IsEnabled property to it. You might wonder why I didn’t create a property like IsMonotone (which has a more elegant name). The reason is I wanted the colorful state to be the positive state. That way it’s easier to use binding the IsEnabled or IsMouseOver properties without using value converters to convert ‘true’ to ‘false’ and vice versa.

The BiasColor property controls the hue of the monotone image. The default is White, which makes the image grayscale, but you can choose any other color to make the image appear in that color.
I also wanted to be able to make the color transformation smooth using animation, so I needed to find a way to control the impact of the bias color, which means finding a way to control for each pixel how much I want to see its original color, and how much I want to see the bias color.
I ‘ve decided to use the bias color’s unused alpha channel for that purpose. On one hand I has an unused property that I could harness for my needs, and on the other hand it made sense to specify the alpha channel to control that behavior; So, the closer the bias color’s alpha channel is to 0, the less we’ll see the bias channel, and the closer it gets to 255 (FF), the more we’ll see the bias color and not the original color of each pixel.

Notice that you we can’t animate the actual alpha channel, because the alpha property (Color.A) is not a dependency property, hence not animatable. What we can do is use the ColorAnimation and use the same RGB values, but different A value.

I based my conversion methods on Microsoft’s GrayscaleImageConverter, but I’ve made some changes to them in order to support a larger variety of images, and to support the bias color’s alpha channel impact.

Last, I’ve extended Image’s default style to bind the IsFullColor property to the IsEnabled property.

Now I can use the image and make it gray when it’s disabled, or bind the IsFullColor to the IsMouseOver property to make the image in some color when the mouse is not over the image, and displaying it in full color when the mouse is over the image.

I hope you’ll find this class useful.

Haim.

 

I’m adding some images to demonstrate:

1. The original image:
 image

2. The image with BiasColor #FF6D58FF (255 alpha, 109 red, 88 green, 255 blue):
image

3. The image with BiasColor #B36D58FF (179 alpha, 109 red, 88 green, 255 blue):
image

4. The image with BiasColor #5D6D58FF (93 alpha, 109 red, 88 green, 255 blue):
image

5. The image with White BiasColor:
image

 

Here is the code of the class:

/// <summary>
/// Represents a control that displays an image with a monotone color.
/// </summary>
public class MonotoneImage : Image
{
    #region Ctors

    /// <summary>
    /// Initializes the MonotoneImage class.
    /// </summary>
    static MonotoneImage()
    {
        ///Crete a new style, based on Image's default style:
        Style defaultStyle = new Style(typeof(MonotoneImage),
            StyleProperty.DefaultMetadata.DefaultValue as Style);

        ///Bind the "IsFullColor" property to the "IsEnabled" property:
        defaultStyle.Setters.Add(new Setter(MonotoneImage.IsFullColorProperty,
            new Binding("IsEnabled") { RelativeSource = RelativeSource.Self }));

        defaultStyle.Seal();

        StyleProperty.OverrideMetadata(typeof(MonotoneImage),
            new FrameworkPropertyMetadata(defaultStyle));
    }

    #endregion

    #region Overridden Members

    protected override void OnRender(System.Windows.Media.DrawingContext dc)
    {
        if (IsFullColor)
        {
            base.OnRender(dc);
        }
        else
        {
            ///Get the source image that needs to be converted to a monotone image:
            BitmapSource source = this.Source as BitmapSource;

            if (source == null)
            {
                base.OnRender(dc);
                return;
            }

            ///The converted monotone image:
            BitmapSource target;

            Color biasColor = this.BiasColor;

            if (source.Format == PixelFormats.Bgra32)
            {
                target = ConvertToMonotoneImage(source, biasColor);
            }
            else if (biasColor == Colors.White)
            {
                ///If the bias color is white,
                ///convert the image to a grayscale image:
                FormatConvertedBitmap bitmap = new FormatConvertedBitmap();
                bitmap.BeginInit();
                bitmap.DestinationFormat = PixelFormats.Gray32Float;
                bitmap.Source = source;
                bitmap.EndInit();
                target = bitmap;
            }
            else
            {
                ///If the bias color is not white,
                ///convert the image to a Bgr32 pixel format
                ///and create a monotone image:
                FormatConvertedBitmap bitmap = new FormatConvertedBitmap();
                bitmap.BeginInit();
                bitmap.DestinationFormat = PixelFormats.Bgr32;
                bitmap.Source = source;
                bitmap.EndInit();
                target = ConvertToMonotoneImage(bitmap, biasColor);
            }

            if (target.CanFreeze)
            {
                target.Freeze();
            }

            dc.DrawImage(target, new Rect(new Point(), base.RenderSize));
        }
    }

    #endregion


    #region Dependency Properties

    /// <summary>
    /// Gets or sets the color of which the palette will be created.
    /// </summary>
    public Color BiasColor
    {
        get { return (Color)GetValue(BiasColorProperty); }
        set { SetValue(BiasColorProperty, value); }
    }

    public static readonly DependencyProperty BiasColorProperty =
        DependencyProperty.Register("BiasColor", typeof(Color), typeof(MonotoneImage),
        new FrameworkPropertyMetadata(Colors.White,
            FrameworkPropertyMetadataOptions.AffectsRender));



    /// <summary>
    /// Gets or sets whether to display the image in full color,
    /// or on a single color.
    /// </summary>
    public bool IsFullColor
    {
        get { return (bool)GetValue(IsFullColorProperty); }
        set { SetValue(IsFullColorProperty, value); }
    }

    public static readonly DependencyProperty IsFullColorProperty =
        DependencyProperty.Register("IsFullColor", typeof(bool), typeof(MonotoneImage),
        new FrameworkPropertyMetadata(true,
            FrameworkPropertyMetadataOptions.AffectsRender));

    #endregion


    #region Non-Public Methods

    /// <summary>
    /// Converts the specified image to a single-color image,
    /// with a color palette of the specified bias color.
    /// </summary>
    /// <param name="inputImage">The image to convert.</param>
    /// <param name="biasColor">The color to create the palette for.</param>
    /// <returns></returns>
    private static BitmapSource ConvertToMonotoneImage(BitmapSource inputImage, Color biasColor)
    {
        if (inputImage == null)
        {
            throw new ArgumentNullException("inputImage");
        }

        if (inputImage.Format != PixelFormats.Bgra32
            && inputImage.Format != PixelFormats.Bgr32)
        {
            throw new ArgumentException("The image format is not the of expected type.", "inputImage");
        }

        int stride = inputImage.PixelWidth * 4;

        ///Create a pixel-array for the converted image,
        ///and copy the input image pixels into it:
        byte[] pixels = new byte[inputImage.PixelWidth * inputImage.PixelHeight * 4];
        inputImage.CopyPixels(pixels, stride, 0);

        float alphaRatio = (float)biasColor.A / 256f;

        if (inputImage.Format == PixelFormats.Bgra32 || inputImage.Format == PixelFormats.Bgr32)
        {
            for (int i = 0; (i + 4) < pixels.Length; i += 4)
            {
                ///Canculate the color intensity of the pixel:
                float pixelStrength = ((pixels[i] * 0.0004296875f) + (pixels[i + 1] * 0.002304687f))
                    + (pixels[i + 2] * 0.001171875f);

                ///Set the color of the pixel:
                pixels[i] += (byte)((pixelStrength * biasColor.B - pixels[i]) * alphaRatio);
                pixels[i + 1] += (byte)((pixelStrength * biasColor.G - pixels[i + 1]) * alphaRatio);
                pixels[i + 2] += (byte)((pixelStrength * biasColor.R - pixels[i + 2]) * alphaRatio);
            }
        }

        return BitmapSource.Create(inputImage.PixelWidth,
            inputImage.PixelHeight,
            inputImage.DpiX, inputImage.DpiY,
            inputImage.Format, inputImage.Palette,
            pixels, stride);
    }

    #endregion
}

x:Reference

For my first post, I would like to talk a little about XAML’s x:Reference markup extension.

Those who have some experience with XML serialization must remember the pain it caused to any sane person, driving him to insanity.

I remember myself few years ago trying to serialize a pair of classes which one was referencing the other, and transporting them through WCF with XML serialization.

XML didn’t give you a way (at least not an easy one) to handle what was called a “circular reference” (or cyclic reference), in which two objects referencing one another.

 

Along came XAML.

As some of you already know, XAML started as part of the WPF technology, but it was long ago when the guys at Microsoft realized it could be used to almost everything else. That’s why the XAML classes were migrated to their own assembly in .Net 4 (System.XAML).

XAML handles the “circular reference” problem quite nicely: You can take one element and assign it a name with the x:Name attribute (notice the that the namespace that is assigned to the x prefix isn’t related to WPF whatsoever), and take the other element and reference the first element with the x:Reference markup extension.

 

Here’s an example:

<Root x:Name=”rootParent”>

<Root.Child>

<Node Parent=”{x:Reference rootParent}”/>

</Root.Child>

</Root>

 

Here we have a parent of type Root, which in its Child property we have an object of type Node.

The child’s Parent property is referencing the root with the x:Reference markup extension.

 

Of course, the x:Reference markup extension could be used in WPF as well.
There are many cases when one control needs to reference another one by its name.
One way to do this is to use binding with the ElementName property set.
This way, in my opinion, is not very good because it’s forcing us to create a binding and it will have an impact on the performance. We could improve it a little bit by using the OneTime mode, but still I think the x:Reference is more proper an elegant.

There is one problem, though: As for the time of writing, WPF’s designer can’t handle the x:Reference markup extension. x:Reference is trying to locate the IXamlNameResolver  service, which doesn’t exist in design mode. This is causing the designer to be completely disabled, while displaying the message “Service provider is missing the INameResolver service.”.

There is, however, a simple solution for this problem.
As I was writing in this bug‘s workaround list at Microsoft’s Connect, we could extend the System.Windows.Markup.Reference class and override the ProvideValue method to return null if the control is is design mode, or (if you’re using this outside WPF) return null if the IXamlNameResolver service doesn’t exist, although if you’re using it for WPF exclusively, the first approach is preferred..

 

[ContentProperty(“Name”)]
public class Reference : System.Windows.Markup.Reference
{
public Reference()
: base()
{ }

public Reference(string name)
: base(name)
{ }

public override object ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
{
throw new ArgumentNullException(“serviceProvider”);
}

IProvideValueTarget valueTargetProvider = serviceProvider.GetService(typeof
IProvideValueTarget)) as IProvideValueTarget;

if (valueTargetProvider != null)
{
DependencyObject targetObject = valueTargetProvider.TargetObject as DependencyObject;

if (targetObject != null && DesignerProperties.GetIsInDesignMode(targetObject))
{
return null;
}
}

return base.ProvideValue(serviceProvider);
}
}