Click here to Skip to main content
13,900,451 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

25.5K views
25 bookmarked
Posted 29 May 2016
Licenced CPOL

Colored Image Controls

, 6 Sep 2016
Rate this:
Please Sign up or sign in to vote.
A collection of WPF controls that allow changing the color of images (image, button, toggle button, and dropdown buttons).

Introduction

This article shows how to make images more customizable, and offers many alternatives to standard controls that can be used anywhere, easily, and sometimes with just one line.

It is assumed that you have basic knowledge of creating user controls and converters. You may refer to the following links for more information on the latter: 

Preview

Table Of Contents

Background

It all started when I needed to change the color of an image when the mouse hovered over it. How is this normally done? Can it be done in a few lines or less?

Before discovering the grass really is greener on the other side, I had concluded the only way to do this was by means of hacking or binding trickery. Before I even attempted to bark up that tree, I searched for inspiration to find a better solution. Lo and behold, I found inspiration on StackOverflow and wrapped the concepts I learned into a user control.

Using this new control, I was able to craft four other ones based on similar needs and desires.

MaskedImage

MaskedImage is intended to take a regular image and mask it with a color. Color masking is achievable using the Rectangle control and it's OpacityMask property, which is mostly all MaskedImage consists of.

Example Usage

<Controls.Common:MaskedImage Source="/Images/Gear.png"/>

Properties

  • Source
    • An image to mask.
  • ImageColor
    • The color to mask the image with.
  • ImageWidth
    • The width of the image.
  • ImageHeight
    • The height of the image.

 

Implementation

public class MaskedImage : UserControl
{
    #region DependencyProperties

    public static DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(ImageSource), typeof(MaskedImage), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSourceChanged));
    public ImageSource Source
    {
        get
        {
            return (ImageSource)GetValue(SourceProperty);
        }
        set
        {
            SetValue(SourceProperty, value);
        }
    }
    static void OnSourceChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e)
    {
        MaskedImage MaskedImage = Object as MaskedImage;
        MaskedImage.ImageBrush = new ImageBrush(MaskedImage.Source);
    }

    public static DependencyProperty ImageBrushProperty = DependencyProperty.Register("ImageBrush", typeof(ImageBrush), typeof(MaskedImage), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public ImageBrush ImageBrush
    {
        get
        {
            return (ImageBrush)GetValue(ImageBrushProperty);
        }
        private set
        {
            SetValue(ImageBrushProperty, value);
        }
    }

    public static readonly DependencyProperty ImageColorProperty = DependencyProperty.Register("ImageColor", typeof(Brush), typeof(MaskedImage), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public Brush ImageColor
    {
        get
        {
            return (Brush)GetValue(ImageColorProperty);
        }
        set
        {
            SetValue(ImageColorProperty, value);
        }
    }

    public static DependencyProperty ImageWidthProperty = DependencyProperty.Register("ImageWidth", typeof(double), typeof(MaskedImage), new FrameworkPropertyMetadata(16.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public double ImageWidth
    {
        get
        {
            return (double)GetValue(ImageWidthProperty);
        }
        set
        {
            SetValue(ImageWidthProperty, value);
        }
    }

    public static DependencyProperty ImageHeightProperty = DependencyProperty.Register("ImageHeight", typeof(double), typeof(MaskedImage), new FrameworkPropertyMetadata(16.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public double ImageHeight
    {
        get
        {
            return (double)GetValue(ImageHeightProperty);
        }
        set
        {
            SetValue(ImageHeightProperty, value);
        }
    }

    #endregion

    #region MaskedImage

    public MaskedImage()
    {
        this.DefaultStyleKey = typeof(MaskedImage);
    }

    #endregion
}

Template

<ControlTemplate TargetType="{x:Type local:MaskedImage}">
    <Border 

        BorderBrush="{TemplateBinding BorderBrush}"

        BorderThickness="{TemplateBinding BorderThickness}"

        Background="{TemplateBinding Background}"

        Padding="{TemplateBinding Padding}"

        Margin="{TemplateBinding Margin}"

        HorizontalAlignment="{TemplateBinding HorizontalAlignment}"

        VerticalAlignment="{TemplateBinding VerticalAlignment}">
        <Rectangle 

            Width="{TemplateBinding ImageWidth}" 

            Height="{TemplateBinding ImageHeight}" 

            Fill="{TemplateBinding ImageColor}" 

            OpacityMask="{TemplateBinding ImageBrush}"/>
    </Border>
</ControlTemplate>

Notes

  • Encapsulating the Rectangle in a control makes it possible to set image size and color all in one line.

MaskedButton

The idea here was to take everything I loved about the Button control and just build upon that with a fresh template. If we try to inherit from MaskedImage, we sacrifice essential functionality Button already defines; therefore, MaskedButton inherits Button.

Example Usage

<Controls.Common:MaskedButton 

    Source="/Images/Gear.png" 

    Content="Options"

    DropDownVisibility="Visible"

    ToolTip="Button tip"

    DropDownToolTip="Dropdown tip"

    Click="MaskedButton_Click">
    <Controls.Common:MaskedButton.DropDown>
        <ContextMenu>
            <MenuItem Header="Option 1"/>
            <MenuItem Header="Option 2"/>
            <MenuItem Header="Option 3"/>
            <MenuItem Header="Option 4"/>
            <MenuItem Header="Option 5"/>
            <MenuItem Header="Option 6"/>
            <MenuItem Header="Option 7"/>
            <MenuItem Header="Option 8"/>
        </ContextMenu>
    </Controls.Common:MaskedButton.DropDown>
</Controls.Common:MaskedButton>

Properties

  • Content
    • Content displayed relative to image.
  • ContentMargin
    • Margin for content.
  • ContentPlacement
    • Content placement relative to image.
  • DropDown

    • A dropdown menu to show.

  • DropDownDataContext

    • The data context for the dropdown menu.

  • DropDownToolTip

    • A tooltip for the dropdown button, separate from main button.

  • DropDownVisibility

    • Gets or sets value that determines if dropdown button should show.

  • IsChecked

    • Indicates checked state.

  • Source

    • An image to mask.

  • ImageWidth

    • Width of masked image.

  • ImageHeight

    • Height of masked image.

  • ImageColor

    • Color of masked image.

MaskedButton by itself is just a clickable image, though it can also have a dropdown menu by setting DropDownVisibility = Visibility.Visible. In that event, you have a clickable image and a dropdown button next to it. The dropdown button activates the dropdown menu when clicked; when clicked, the dropdown button (an arrow, by default) rotates using an animation to indicate the menu is open.

Consider the following example.

Say you have an "open recent" button that enables opening files that have been opened before. MaskedButton would enable you to:

  1. Open the most recently opened file when the main button is clicked, and
  2. Display a list of files recently opened in the dropdown menu, each of which open when clicked.

Implementation

public class MaskedButton : Button
{
    #region DependencyProperties

    public static DependencyProperty ContentMarginProperty = DependencyProperty.Register("ContentMargin", typeof(Thickness), typeof(MaskedButton), new FrameworkPropertyMetadata(default(Thickness), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public Thickness ContentMargin
    {
        get
        {
            return (Thickness)GetValue(ContentMarginProperty);
        }
        set
        {
            SetValue(ContentMarginProperty, value);
        }
    }

    public static DependencyProperty ContentPlacementProperty = DependencyProperty.Register("ContentPlacement", typeof(Side), typeof(MaskedButton), new FrameworkPropertyMetadata(Side.Right, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public Side ContentPlacement
    {
        get
        {
            return (Side)GetValue(ContentPlacementProperty);
        }
        set
        {
            SetValue(ContentPlacementProperty, value);
        }
    }

    public static readonly DependencyProperty DropDownProperty = DependencyProperty.Register("DropDown", typeof(ContextMenu), typeof(MaskedButton), new UIPropertyMetadata(null, OnDropDownChanged));
    public ContextMenu DropDown
    {
        get
        {
            return (ContextMenu)GetValue(DropDownProperty);
        }
        set
        {
            SetValue(DropDownProperty, value);
        }
    }
    static void OnDropDownChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e)
    {
        MaskedButton MaskedButton = (MaskedButton)Object;
        if (MaskedButton.DropDown != null)
        {
            MaskedButton.DropDown.PlacementTarget = MaskedButton;
            MaskedButton.DropDown.Placement = PlacementMode.Bottom;
            if (MaskedButton.DropDownDataContext != null)
                MaskedButton.DropDown.DataContext = MaskedButton.DropDownDataContext;
        }
    }

    public static DependencyProperty DropDownDataContextProperty = DependencyProperty.Register("DropDownDataContext", typeof(object), typeof(MaskedButton), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnDropDownDataContextChanged));
    public object DropDownDataContext
    {
        get
        {
            return (object)GetValue(DropDownDataContextProperty);
        }
        set
        {
            SetValue(DropDownDataContextProperty, value);
        }
    }
    static void OnDropDownDataContextChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e)
    {
        MaskedButton MaskedButton = (MaskedButton)Object;
        if (MaskedButton.DropDown != null)
            MaskedButton.DropDown.DataContext = MaskedButton.DropDownDataContext;
    }

    public static DependencyProperty DropDownToolTipProperty = DependencyProperty.Register("DropDownToolTip", typeof(string), typeof(MaskedButton), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public string DropDownToolTip
    {
        get
        {
            return (string)GetValue(DropDownToolTipProperty);
        }
        set
        {
            SetValue(DropDownToolTipProperty, value);
        }
    }

    public static DependencyProperty DropDownVisibilityProperty = DependencyProperty.Register("DropDownVisibility", typeof(Visibility), typeof(MaskedButton), new FrameworkPropertyMetadata(Visibility.Collapsed, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public Visibility DropDownVisibility
    {
        get
        {
            return (Visibility)GetValue(DropDownVisibilityProperty);
        }
        set
        {
            SetValue(DropDownVisibilityProperty, value);
        }
    }

    public static DependencyProperty IsCheckedProperty = DependencyProperty.Register("IsChecked", typeof(bool), typeof(MaskedButton), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public bool IsChecked
    {
        get
        {
            return (bool)GetValue(IsCheckedProperty);
        }
        set
        {
            SetValue(IsCheckedProperty, value);
        }
    }

    public static DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(ImageSource), typeof(MaskedButton), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSourceChanged));
    public ImageSource Source
    {
        get
        {
            return (ImageSource)GetValue(SourceProperty);
        }
        set
        {
            SetValue(SourceProperty, value);
        }
    }
    private static void OnSourceChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e)
    {
        MaskedButton Button = Object as MaskedButton;
        Button.ImageBrush = new ImageBrush(Button.Source);
    }

    public static DependencyProperty ImageBrushProperty = DependencyProperty.Register("ImageBrush", typeof(ImageBrush), typeof(MaskedButton), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public ImageBrush ImageBrush
    {
        get
        {
            return (ImageBrush)GetValue(ImageBrushProperty);
        }
        set
        {
            SetValue(ImageBrushProperty, value);
        }
    }

    public static readonly DependencyProperty ImageColorProperty = DependencyProperty.Register("ImageColor", typeof(Brush), typeof(MaskedButton), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public Brush ImageColor
    {
        get
        {
            return (Brush)GetValue(ImageColorProperty);
        }
        set
        {
            SetValue(ImageColorProperty, value);
        }
    }

    public static DependencyProperty ImageWidthProperty = DependencyProperty.Register("ImageWidth", typeof(double), typeof(MaskedButton), new FrameworkPropertyMetadata(16.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public double ImageWidth
    {
        get
        {
            return (double)GetValue(ImageWidthProperty);
        }
        set
        {
            SetValue(ImageWidthProperty, value);
        }
    }

    public static DependencyProperty ImageHeightProperty = DependencyProperty.Register("ImageHeight", typeof(double), typeof(MaskedButton), new FrameworkPropertyMetadata(16.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public double ImageHeight
    {
        get
        {
            return (double)GetValue(ImageHeightProperty);
        }
        set
        {
            SetValue(ImageHeightProperty, value);
        }
    }

    #endregion

    #region MaskedButton

    public MaskedButton()
    {
        this.DefaultStyleKey = typeof(MaskedButton);
        this.ContentMargin = new Thickness(5, 0, 0, 0);
    }

    public override void OnApplyTemplate()
    {
        base.ApplyTemplate();
        this.Template.FindName("PART_Dropdown", this).As<ContentControl>().MouseLeftButtonDown += OnDropdownMouseLeftButtonDown;
    }

    #endregion

    #region Methods

    protected override void OnMouseDown(MouseButtonEventArgs e)
    {
        base.OnMouseDown(e);
        if (e.Handled)
            return;
    }

    void OnDropdownMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        e.Handled = true;
        if (this.DropDown != null)
            this.DropDown.IsOpen = true;
    }

    #endregion
}

Template

<ControlTemplate TargetType="{x:Type local:MaskedButton}">
    <Border 

        Margin="{TemplateBinding Margin}" 

        Padding="{TemplateBinding Padding}" 

        BorderThickness="{TemplateBinding BorderThickness}" 

        BorderBrush="{TemplateBinding BorderBrush}"  

        Background="{TemplateBinding Background}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <local:MaskedImage

                x:Name="PART_Rectangle" 

                Source="{TemplateBinding Source}"

                ImageWidth="{TemplateBinding ImageWidth}" 

                ImageHeight="{TemplateBinding ImageHeight}"

                ImageColor="{TemplateBinding ImageColor}"

                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"

                Visibility="{TemplateBinding Source, Converter={StaticResource NullObjectToVisibilityConverter}}"/>
            <ContentControl 

                Grid.Column="1" 

                x:Name="PART_Content"

                Content="{TemplateBinding Content}" 

                Margin="{TemplateBinding ContentMargin}" 

                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"

                Visibility="{TemplateBinding Content, Converter={StaticResource NullObjectToVisibilityConverter}}"/>
            <ContentControl 

                Grid.Column="2" 

                x:Name="PART_Dropdown"

                Margin="5,0,0,0"

                Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MaskedButton}}}" 

                ToolTip="{TemplateBinding DropDownToolTip}" 

                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"

                Visibility="{TemplateBinding DropDownVisibility}">
                <ContentControl.Style>
                    <Style TargetType="{x:Type ContentControl}">
                        <Setter Property="Cursor" Value="Hand"/>
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="{x:Type ContentControl}">
                                    <Rectangle x:Name="PART_Rectangle" Width="12" Height="12" RenderTransformOrigin="0.5,0.5">
                                        <Rectangle.OpacityMask>
                                            <ImageBrush ImageSource="pack://application:,,,/Imagin.Controls.Common;component/Images/ArrowDown.png"/>
                                        </Rectangle.OpacityMask>
                                        <Rectangle.RenderTransform>
                                            <RotateTransform Angle="-90"/>
                                        </Rectangle.RenderTransform>
                                    </Rectangle>
                                    <ControlTemplate.Triggers>
                                        <DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource AncestorType={x:Type local:MaskedButton}}}" Value="True">
                                            <DataTrigger.EnterActions>
                                                <BeginStoryboard>
                                                    <Storyboard>
                                                        <DoubleAnimation 

                                                            Duration="0:0:0.25" 

                                                            Storyboard.TargetName="PART_Rectangle" 

                                                            Storyboard.TargetProperty="RenderTransform.Angle" 

                                                            From="-90" 

                                                            To="0"/>
                                                    </Storyboard>
                                                </BeginStoryboard>
                                            </DataTrigger.EnterActions>
                                            <DataTrigger.ExitActions>
                                                <BeginStoryboard>
                                                    <Storyboard>
                                                        <DoubleAnimation 

                                                            Duration="0:0:0.25" 

                                                            Storyboard.TargetName="PART_Rectangle" 

                                                            Storyboard.TargetProperty="RenderTransform.Angle" 

                                                            From="0" 

                                                            To="-90"/>
                                                    </Storyboard>
                                                </BeginStoryboard>
                                            </DataTrigger.ExitActions>
                                            <Setter TargetName="PART_Rectangle" Property="Fill" Value="{DynamicResource ImagePressedBrush}"/>
                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource AncestorType={x:Type local:MaskedButton}}}" Value="False">
                                            <Setter TargetName="PART_Rectangle" Property="Fill" Value="{DynamicResource ImageBrush}"/>
                                        </DataTrigger>
                                        <Trigger Property="IsMouseOver" Value="True">
                                            <Setter TargetName="PART_Rectangle" Property="Fill" Value="{DynamicResource ImageHoverBrush}"/>
                                        </Trigger>
                                    </ControlTemplate.Triggers>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </ContentControl.Style>
            </ContentControl>
        </Grid>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="ContentPlacement" Value="Top">
            <Setter TargetName="PART_Rectangle" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.ColumnSpan" Value="2"/>
            <Setter TargetName="PART_Content" Property="Grid.ColumnSpan" Value="2"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.Row" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.RowSpan" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.RowSpan" Value="1"/>
        </Trigger>
        <Trigger Property="ContentPlacement" Value="Bottom">
            <Setter TargetName="PART_Rectangle" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.ColumnSpan" Value="2"/>
            <Setter TargetName="PART_Content" Property="Grid.ColumnSpan" Value="2"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Row" Value="1"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.RowSpan" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.RowSpan" Value="1"/>
        </Trigger>
        <Trigger Property="ContentPlacement" Value="Left">
            <Setter TargetName="PART_Rectangle" Property="Grid.Column" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.ColumnSpan" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.ColumnSpan" Value="1"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.RowSpan" Value="2"/>
            <Setter TargetName="PART_Content" Property="Grid.RowSpan" Value="2"/>
        </Trigger>
        <Trigger Property="ContentPlacement" Value="Right">
            <Setter TargetName="PART_Rectangle" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Column" Value="1"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.ColumnSpan" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.ColumnSpan" Value="1"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.RowSpan" Value="2"/>
            <Setter TargetName="PART_Content" Property="Grid.RowSpan" Value="2"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Notes

  • ContextMenu encounters trouble when resolving the data context of a visual parent; one trick is to bind it's data context to it's placement target or any of it's placement target's properties. The property DropDownDataContext sets the data context for you and eliminates the need to explicitly define one.

MaskedToggleButton

MaskedToggleButton inherits...you guessed it, ToggleButton. Functionality is identical except an image's color is toggled and grouping is supported (akin to RadioButton grouping).

Example Usage

<Controls.Common:MaskedToggleButton 
    Source="/Images/Gear.png" 
    Content="Options"
    CheckedToolTip="Options are checked"
    UncheckedToolTip="Options are not checked"/>

Properties

  • Source

    • An image to mask.

  • ImageWidth

    • Width of masked image.

  • ImageHeight

    • Height of masked image.

  • ImageColor

    • Color of masked image.

  • GroupName
    • Enables grouping by ensuring only one item in shared panel with matching name is checked.
  • IsChecked

    • Indicates checked state.

  • CheckedToolTip
    • The tool tip to display when the button is checked
  • UncheckedToolTip
    • The tool tip to display when the button is not checked
  • ToolTip
    • Shouldn't be used.
  • Content
    • Content displayed relative to image.
  • ContentMargin
    • Margin for content.
  • ContentPlacement
    • Content placement relative to image.

Implementation

public class MaskedToggleButton : ToggleButton
{
    #region DependencyProperties

    public static DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(ImageSource), typeof(MaskedToggleButton), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSourceChanged));
    public ImageSource Source
    {
        get
        {
            return (ImageSource)GetValue(SourceProperty);
        }
        set
        {
            SetValue(SourceProperty, value);
        }
    }
    private static void OnSourceChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e)
    {
        MaskedToggleButton MaskedToggleButton = Object as MaskedToggleButton;
        MaskedToggleButton.ImageBrush = new ImageBrush(MaskedToggleButton.Source);
    }

    public static DependencyProperty ImageBrushProperty = DependencyProperty.Register("ImageBrush", typeof(ImageBrush), typeof(MaskedToggleButton), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public ImageBrush ImageBrush
    {
        get
        {
            return (ImageBrush)GetValue(ImageBrushProperty);
        }
        set
        {
            SetValue(ImageBrushProperty, value);
        }
    }

    public static readonly DependencyProperty ImageColorProperty = DependencyProperty.Register("ImageColor", typeof(Brush), typeof(MaskedToggleButton), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public Brush ImageColor
    {
        get
        {
            return (Brush)GetValue(ImageColorProperty);
        }
        set
        {
            SetValue(ImageColorProperty, value);
        }
    }

    public static DependencyProperty GroupNameProperty = DependencyProperty.Register("GroupName", typeof(string), typeof(MaskedToggleButton), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnGroupNameChanged));
    public string GroupName
    {
        get
        {
            return (string)GetValue(GroupNameProperty);
        }
        set
        {
            SetValue(GroupNameProperty, value);
        }
    }
    private static void OnGroupNameChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e)
    {
        MaskedToggleButton MaskedToggleButton = Object as MaskedToggleButton;
        if (MaskedToggleButton.GroupName == string.Empty)
        {
            MaskedToggleButton.Checked -= MaskedToggleButton.MaskedToggleButton_Checked;
        } else
        {
            MaskedToggleButton.Checked += MaskedToggleButton.MaskedToggleButton_Checked;
        }
    }

    private void MaskedToggleButton_Checked(object sender, EventArgs e)
    {
        //We only want to affect the other values if current is true. This avoids other controls from attempting to execute same method when their values have changed.
        //In order for this to work, all controls sharing same group name should be in same parent.
        DependencyObject Parent = this.FindParent<DependencyObject>(this);
        for (int i = 0, Count = VisualTreeHelper.GetChildrenCount(Parent); i < Count; i++)
        {
            var Child = VisualTreeHelper.GetChild(Parent, i);
            if (!(Child is MaskedToggleButton)) continue; //If it's not same type of control, skip it
            MaskedToggleButton Button = Child as MaskedToggleButton;
            if (Button == this) continue; //If we're at this, skip it
            Button.IsChecked = false; //If it's not this, we'll want to uncheck it.
        }
    }

    public static DependencyProperty CheckedToolTipProperty = DependencyProperty.Register("CheckedToolTip", typeof(string), typeof(MaskedToggleButton), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public string CheckedToolTip
    {
        get
        {
            return (string)GetValue(CheckedToolTipProperty);
        }
        set
        {
            SetValue(CheckedToolTipProperty, value);
        }
    }

    public static DependencyProperty UncheckedToolTipProperty = DependencyProperty.Register("UncheckedToolTip", typeof(string), typeof(MaskedToggleButton), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public string UncheckedToolTip
    {
        get
        {
            return (string)GetValue(UncheckedToolTipProperty);
        }
        set
        {
            SetValue(UncheckedToolTipProperty, value);
        }
    }

    public static DependencyProperty ImageWidthProperty = DependencyProperty.Register("ImageWidth", typeof(double), typeof(MaskedToggleButton), new FrameworkPropertyMetadata(16.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public double ImageWidth
    {
        get
        {
            return (double)GetValue(ImageWidthProperty);
        }
        set
        {
            SetValue(ImageWidthProperty, value);
        }
    }

    public static DependencyProperty ImageHeightProperty = DependencyProperty.Register("ImageHeight", typeof(double), typeof(MaskedToggleButton), new FrameworkPropertyMetadata(16.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public double ImageHeight
    {
        get
        {
            return (double)GetValue(ImageHeightProperty);
        }
        set
        {
            SetValue(ImageHeightProperty, value);
        }
    }

    public static DependencyProperty ContentMarginProperty = DependencyProperty.Register("ContentMargin", typeof(Thickness), typeof(MaskedToggleButton), new FrameworkPropertyMetadata(default(Thickness), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public Thickness ContentMargin
    {
        get
        {
            return (Thickness)GetValue(ContentMarginProperty);
        }
        set
        {
            SetValue(ContentMarginProperty, value);
        }
    }

    public static DependencyProperty ContentPlacementProperty = DependencyProperty.Register("ContentPlacement", typeof(Side), typeof(MaskedToggleButton), new FrameworkPropertyMetadata(Side.Right, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    public Side ContentPlacement
    {
        get
        {
            return (Side)GetValue(ContentPlacementProperty);
        }
        set
        {
            SetValue(ContentPlacementProperty, value);
        }
    }

    #endregion

    #region Methods

    private T FindParent<T>(DependencyObject child) where T : DependencyObject
    {
        DependencyObject parentObject = VisualTreeHelper.GetParent(child); //Get parent item
        if (parentObject == null) return null; //We've reached the end of the tree
        T parent = parentObject as T; //Check if the parent matches the type we're looking for
        if (parent != null) return parent; else return FindParent<T>(parentObject);
    }

    public void SetGroup()
    {
    }

    #endregion

    #region MaskedToggleButton

    public MaskedToggleButton()
    {
        this.DefaultStyleKey = typeof(MaskedToggleButton);
        this.ContentMargin = new Thickness(5, 0, 0, 0);
    }

    #endregion
}

Template

<ControlTemplate TargetType="{x:Type local:MaskedToggleButton}">
    <Border 

        Margin="{TemplateBinding Margin}" 

        Padding="{TemplateBinding Padding}" 

        BorderBrush="{TemplateBinding BorderBrush}" 

        BorderThickness="{TemplateBinding BorderThickness}" 

        Background="{TemplateBinding Background}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <local:MaskedImage

                x:Name="PART_Rectangle" 

                Margin="0"

                Padding="0"

                Source="{TemplateBinding Source}"

                ImageWidth="{TemplateBinding ImageWidth}" 

                ImageHeight="{TemplateBinding ImageHeight}"

                ImageColor="{TemplateBinding ImageColor}"

                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"

                Visibility="{TemplateBinding Source, Converter={StaticResource NullObjectToVisibilityConverter}}"/>
            <ContentPresenter 

                Grid.Column="1" 

                x:Name="PART_Content"

                Content="{TemplateBinding Content}" 

                ContentTemplate="{TemplateBinding ContentTemplate}"

                Margin="{TemplateBinding ContentMargin}" 

                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"

                Visibility="{TemplateBinding Content, Converter={StaticResource NullObjectToVisibilityConverter}}"/>
        </Grid>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="ContentPlacement" Value="Top">
            <Setter TargetName="PART_Rectangle" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.ColumnSpan" Value="2"/>
            <Setter TargetName="PART_Content" Property="Grid.ColumnSpan" Value="2"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.Row" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.RowSpan" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.RowSpan" Value="1"/>
        </Trigger>
        <Trigger Property="ContentPlacement" Value="Bottom">
            <Setter TargetName="PART_Rectangle" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.ColumnSpan" Value="2"/>
            <Setter TargetName="PART_Content" Property="Grid.ColumnSpan" Value="2"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Row" Value="1"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.RowSpan" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.RowSpan" Value="1"/>
        </Trigger>
        <Trigger Property="ContentPlacement" Value="Left">
            <Setter TargetName="PART_Rectangle" Property="Grid.Column" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.ColumnSpan" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.ColumnSpan" Value="1"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.RowSpan" Value="2"/>
            <Setter TargetName="PART_Content" Property="Grid.RowSpan" Value="2"/>
        </Trigger>
        <Trigger Property="ContentPlacement" Value="Right">
            <Setter TargetName="PART_Rectangle" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Column" Value="1"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.ColumnSpan" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.ColumnSpan" Value="1"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.RowSpan" Value="2"/>
            <Setter TargetName="PART_Content" Property="Grid.RowSpan" Value="2"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

MaskedDropDownButton

MaskedDropDownButton inherits MaskedToggleButton, but intead of just toggling the image color, it displays a dropdown menu that appears during checked state.

Example Usage

<Controls.Common:MaskedDropDownButton 

    Source="/Images/Gear.png" 

    Content="Options">
    <Controls.Common:MaskedDropDownButton.DropDown>
        <ContextMenu>
            <MenuItem Header="Option 1"/>
            <MenuItem Header="Option 2"/>
            <MenuItem Header="Option 3"/>
            <MenuItem Header="Option 4"/>
            <MenuItem Header="Option 5"/>
        </ContextMenu>
    </Controls.Common:MaskedDropDownButton.DropDown>
</Controls.Common:MaskedDropDownButton>

Properties

  • DropDown

    • The dropdown menu.
  • DropDownDataContext
    • The data context for the dropdown menu.

Implementation

public class MaskedDropDownButton : MaskedToggleButton
{
    #region Properties

    public static readonly DependencyProperty DropDownProperty = DependencyProperty.Register("DropDown", typeof(ContextMenu), typeof(MaskedDropDownButton), new UIPropertyMetadata(null, OnDropDownChanged));
    public ContextMenu DropDown
    {
        get
        {
            return (ContextMenu)GetValue(DropDownProperty);
        }
        set
        {
            SetValue(DropDownProperty, value);
        }
    }
    static void OnDropDownChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e)
    {
        MaskedDropDownButton MaskedDropDownButton = (MaskedDropDownButton)Object;
        if (MaskedDropDownButton.DropDown != null)
        {
            MaskedDropDownButton.DropDown.PlacementTarget = MaskedDropDownButton;
            MaskedDropDownButton.DropDown.Placement = PlacementMode.Bottom;
            if (MaskedDropDownButton.DropDownDataContext != null)
                MaskedDropDownButton.DropDown.DataContext = MaskedDropDownButton.DropDownDataContext;
        }
    }

    public static DependencyProperty DropDownDataContextProperty = DependencyProperty.Register("DropDownDataContext", typeof(object), typeof(MaskedDropDownButton), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnDropDownDataContextChanged));
    public object DropDownDataContext
    {
        get
        {
            return (object)GetValue(DropDownDataContextProperty);
        }
        set
        {
            SetValue(DropDownDataContextProperty, value);
        }
    }
    static void OnDropDownDataContextChanged(DependencyObject Object, DependencyPropertyChangedEventArgs e)
    {
        MaskedDropDownButton MaskedDropDownButton = (MaskedDropDownButton)Object;
        if (MaskedDropDownButton.DropDown != null)
            MaskedDropDownButton.DropDown.DataContext = MaskedDropDownButton.DropDownDataContext;
    }

    #endregion

    #region MaskedDropDownButton

    public MaskedDropDownButton()
    {
        this.DefaultStyleKey = typeof(MaskedDropDownButton);
        this.ContentMargin = new Thickness(5, 0, 0, 0);
        this.SetBinding(MaskedDropDownButton.IsCheckedProperty, new Binding("DropDown.IsOpen")
        {
            Source = this
        });
        this.SetBinding(MaskedDropDownButton.DropDownDataContextProperty, new Binding("DropDown.DataContext")
        {
            Source = this
        });
    }

    #endregion

    #region Methods

    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    {
        e.Handled = true;
        if (this.DropDown != null)
            this.DropDown.IsOpen = true;
    }

    #endregion
}

Template

<ControlTemplate TargetType="{x:Type local:MaskedDropDownButton}">
    <Border 

        Background="{TemplateBinding Background}"

        BorderThickness="{TemplateBinding BorderThickness}"

        BorderBrush="{TemplateBinding BorderBrush}" 

        Margin="{TemplateBinding Margin}" 

        Padding="{TemplateBinding Padding}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <local:MaskedImage

                x:Name="PART_Rectangle" 

                Source="{TemplateBinding Source}"

                ImageWidth="{TemplateBinding ImageWidth}" 

                ImageHeight="{TemplateBinding ImageHeight}"

                ImageColor="{TemplateBinding ImageColor}"

                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"

                Visibility="{TemplateBinding Source, Converter={StaticResource NullObjectToVisibilityConverter}}"/>
            <ContentControl 

                Grid.Column="1" 

                x:Name="PART_Content"

                Content="{TemplateBinding Content}" 

                Margin="{TemplateBinding ContentMargin}" 

                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"

                Visibility="{TemplateBinding Content, Converter={StaticResource NullObjectToVisibilityConverter}}"/>
        </Grid>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="ContentPlacement" Value="Top">
            <Setter TargetName="PART_Rectangle" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.ColumnSpan" Value="2"/>
            <Setter TargetName="PART_Content" Property="Grid.ColumnSpan" Value="2"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.Row" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.RowSpan" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.RowSpan" Value="1"/>
        </Trigger>
        <Trigger Property="ContentPlacement" Value="Bottom">
            <Setter TargetName="PART_Rectangle" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.ColumnSpan" Value="2"/>
            <Setter TargetName="PART_Content" Property="Grid.ColumnSpan" Value="2"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Row" Value="1"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.RowSpan" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.RowSpan" Value="1"/>
        </Trigger>
        <Trigger Property="ContentPlacement" Value="Left">
            <Setter TargetName="PART_Rectangle" Property="Grid.Column" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.ColumnSpan" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.ColumnSpan" Value="1"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.RowSpan" Value="2"/>
            <Setter TargetName="PART_Content" Property="Grid.RowSpan" Value="2"/>
        </Trigger>
        <Trigger Property="ContentPlacement" Value="Right">
            <Setter TargetName="PART_Rectangle" Property="Grid.Column" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Column" Value="1"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.ColumnSpan" Value="1"/>
            <Setter TargetName="PART_Content" Property="Grid.ColumnSpan" Value="1"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Content" Property="Grid.Row" Value="0"/>
            <Setter TargetName="PART_Rectangle" Property="Grid.RowSpan" Value="2"/>
            <Setter TargetName="PART_Content" Property="Grid.RowSpan" Value="2"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Notes

  • What makes MaskedDropDownButton nice is the ability to change the image's color when the dropdown menu is shown.

Points Of Interest

  • Border, padding, margin, width, and height of the control can be set separate from the masked image.
  • Triggers are used to determine placement of content relative to the masked image by setting the row and column of the affected elements.

History

  • 29th of May, 2016
    •  Initial post.
  • 1st of June, 2016
    • Combined former multiple articles into one.
  • 17th of August, 2016
    • AdvancedImageDropDownButton's template no longer contains a DropDownButton and instead uses a ContentControl to display the rotating arrow. This enables the dropdown to display directly below the entire control as opposed to directly below the arrow button. This also makes it possible to bind the dropdown's placement target to the dropdown's data context, which is often necessary when binding to a ContextMenu.
  • 6th of September
    • Updated article.

Future

The code in this article is now part of the open source project, Imagin.NET

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

James J M
Software Developer Imagin
United States United States
No Biography provided

You may also be interested in...

Pro

Comments and Discussions

 
GeneralMy vote of 5 Pin
InvisibleMedia5-Oct-17 7:07
professionalInvisibleMedia5-Oct-17 7:07 
GeneralGreat Post, but Pin
Jalapeno Bob18-Sep-17 8:08
professionalJalapeno Bob18-Sep-17 8:08 
GeneralRe: Great Post, but Pin
James J M21-Oct-17 4:31
memberJames J M21-Oct-17 4:31 
GeneralMy vote of 5 Pin
docNyie9-Sep-16 3:51
memberdocNyie9-Sep-16 3:51 
QuestionVery nice post Pin
Member 1271096430-Aug-16 2:20
memberMember 1271096430-Aug-16 2:20 
AnswerRe: Very nice post Pin
James J M30-Aug-16 7:25
memberJames J M30-Aug-16 7:25 
PraiseNice Work Pin
Bob Ranck18-Aug-16 13:53
memberBob Ranck18-Aug-16 13:53 
SuggestionScreenshots Pin
webmaster44230-May-16 8:57
memberwebmaster44230-May-16 8:57 
GeneralRe: Screenshots Pin
James J M30-May-16 10:52
memberJames J M30-May-16 10:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web03 | 2.8.190306.1 | Last Updated 6 Sep 2016
Article Copyright 2016 by James J M
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid