More advanced attached property use: the Ramora pattern

I like to think of the technique I am about to demonstrate as the Ramora pattern - it allows you to attach a chunk of logic to any existing element that you have.

The RadialPanel example showed an example of storing information on an element using attached properties. In that example, we did not need to know when the property changed because we used the appropriate metadata to tell WPF that it affected the arrange part of the parent's layout.

What we will build now is a simple app that shows a bunch of buttons, and we will have the buttons change their background when you have the mouse over them. We will do this by just using the standard Button in WPF.

Do do this, we will create a static class called Hover which will define an attached property. First we define the class:

    static public class Hover

    {

    }

The first thing we will need is an attached property. We will define this one similar to the RadialPanel example, but this time we will have metadata telling WPF to call us whenever the property value changes on any element:

        public static readonly DependencyProperty BrushProperty = DependencyProperty.RegisterAttached(

            "Brush",

            typeof(Brush),

            typeof(Hover),

            new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnHoverBrushChanged)));

        public static Brush GetBrush(DependencyObject obj)

        {

            return (Brush)obj.GetValue(BrushProperty);

        }

        public static void SetBrush(DependencyObject obj, Brush value)

        {

            obj.SetValue(BrushProperty, value);

        }

This is where the real power of the WPF property system comes in. Not only can we attach any data we want to any DependencyObject, but WPF will tell us whenever it changes! We use this callback to do our devious Ramora work:

        private static void OnHoverBrushChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)

        {

            Control control = obj as Control;

            if (control != null)

            {

                // subscribe to mouse enter and leave events on the control

                control.MouseEnter += new MouseEventHandler(OnControlEnter);

                control.MouseLeave += new MouseEventHandler(OnControlLeave);

            }

            else

            {

                Debug.Fail("Hover only works on Control objects!");

            }

        }

As you can see, we use the fact that the Brush has changed as an opportunity to subscribe to the MouseEnter and MouseLeave events. One thing we will need to handle the mouse enter event is a place to store the Background while we override it. We can make another attached property for that:

        public static readonly DependencyProperty OriginalBrushProperty = DependencyProperty.RegisterAttached(

            "OriginalBrush",

            typeof(Brush),

            typeof(Hover));

Now we can simply handle the events. On enter we save the background on the control and set it to the hover brush:

        static void OnControlEnter(object sender, MouseEventArgs e)

        {

            Control control = (Control)e.OriginalSource;

            // remember what brush it had before we changed it

            control.SetValue(OriginalBrushProperty, control.Background);

            // set the background to the value that was set in the property

            control.Background = GetBrush(control);

        }

And on leave we can restore everything:

        static void OnControlLeave(object sender, MouseEventArgs e)

        {

            Control control = (Control)e.OriginalSource;

            // restore the old value

            control.Background = (Brush)control.GetValue(OriginalBrushProperty);

        }

Now the Hover class is done! Now we just need to use it:

<Window x:Class="AttachedBehaviorExample.Window1"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:AttachedBehaviorExample"

    Title="AttachedBehaviorExample" Height="300" Width="300"

    >

  <UniformGrid Rows="6" Columns="1">

    <Button Content="Button 1" local:Hover.Brush="Red" />

    <Button Content="Button 2" local:Hover.Brush="Green" />

    <Button Content="Button 3" local:Hover.Brush="Blue" />

    <Button Content="Button 4" local:Hover.Brush="BlanchedAlmond" />

    <Button Content="Button 5" local:Hover.Brush="Thistle" />

    <Button Content="Button 6" local:Hover.Brush="SpringGreen" />

  </UniformGrid>

</Window>

Now the example is finished - if you start it up then you can see that the buttons have fancy colors when you hover over them.

Why do something like this instead of just making a new Button class? Here are a few reasons:

  • You can apply the Hover to anything, not just Buttons. You can apply it to any control.
  • The code for the behavior is all contained in one class - this gives good encapsulation of code.
  • You can make other Ramoras and apply them to your Button objects as well, in any combination. This gives some of the power of multiple inheritance, but none of the mess.

Another way of using this trick is to have the value of the attached property be an instance of the Ramora class. This allows you to make more complex behavior that stores state per attachment, rather than having the stateless design shown above. The key part of the design is the same, though.

AttachedBehaviorExample.zip