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(
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnHoverBrushChanged)));
public static Brush GetBrush(DependencyObject obj)
public static void SetBrush(DependencyObject obj, Brush 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);
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(
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
// 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)
// restore the old value
control.Background = (Brush)control.GetValue(OriginalBrushProperty);
Now the Hover class is done! Now we just need to use it:
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" />
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:
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.