Click here to Skip to main content
15,886,689 members
Articles / Desktop Programming / WPF

Use AttachedProperties to Add Behaviors to the Components (Ramora Pattern)

Rate me:
Please Sign up or sign in to vote.
3.67/5 (2 votes)
21 Feb 2010CPOL2 min read 8.6K   10  
How to use AttachedProperties to add behaviors to the components (Ramora pattern)

The Problem

In WPF, you expect your components/controls to behave exactly as you want to... but this not always the case.
For example: why does this combobox not execute a command when I change the selection or more often, why does this textbox not execute a command when I press the Enter (return) key ?

This problem often occurs when you use the - well known - pattern M-V-VM and it's sometimes hard to find a workaround. Today, I will explain to you a design pattern, known as the Ramora pattern which I find very useful.

By the way, you can also use it to create a library of behaviors for all your projects...

Wanted Application

Ramora pattern

One Solution (or the Ramora Pattern Explained)

The Idea

The solution is based on the attached properties (MSDN page here) of the WPF binding engine. With this mechanism, you can attach any property to any object you want and it's massively used in layout controls (DockPanel.Dock for example).

By attaching and detaching properties, we will attach some handlers on the events of our choice to add behaviors to the controls. The value passed by the property will also be useful as a data storage....

The steps are:

  1. Create a class of your choice, no inheritance needed
  2. Declare an attachedProperty by registering it and adding the corresponding GetXXX and SetXXX
  3. Add an event handler to the change of this property: inside it, add an event handler to the event you want to catch (for example MouseEnter),
  4. Add the behavior logic inside this last event handler
  5. Attach this behavior to the aimed control inside your XAML (after you'd declared the XMLNS)

An Example

In this example, we'll try to add a nice behavior to any IInputElement: anytime the user presses the 'Enter' key, it will execute a Command.

We'll then declare a Command attached property of type 'ICommand' which will be the command to execute on a given event (very original, isn't it?).

C#
#region Command Property
/// <summary>
/// Enables Command functionality
/// </summary>
public static ICommand GetCommand(DependencyObject obj)
{
	return (ICommand)obj.GetValue(CommandProperty);
}
 
/// <summary>
/// Enables Command functionality
/// </summary>
public static void SetCommand(DependencyObject obj, ICommand value)
{
	obj.SetValue(CommandProperty, value);
}
 
/// <summary>
/// Enables Command functionality
/// </summary>
public static readonly DependencyProperty CommandProperty =
	DependencyProperty.RegisterAttached("Command",
 typeof(ICommand),
 typeof(CommandOnEnter), 
new UIPropertyMetadata(null, new PropertyChangedCallback(CommandPropertyChanged)));

Then everytime the property is assigned, we will attach or detach our event handlers on the DependencyObject which uses it. In our case, this is an IInputElement.

C#
/// <summary>
/// Command property changed callback
/// </summary>        
static void CommandPropertyChanged
	(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
	//Test in debug mode if it's binded to the correct type of element...
	Debug.Assert(sender is IInputElement, 
		"Attached property only for a IInputElement");
	if (sender is IInputElement)
	{
		IInputElement inputElement = sender as IInputElement;
 
		// Clean when Command is released
		if (e.OldValue != null)
		{
			Detach(inputElement);
		}
 
		//Attach the behavior
		if (e.NewValue != null)
		{
			Attach(inputElement);
		} 
	}
}
 
/// <summary>
/// Clean event handlers
/// </summary>
/// <param name="inputElement">The aimed IInputElement</param>
private static void Detach(IInputElement inputElement)
{
	inputElement.PreviewKeyDown -= CommandOnEnter_PreviewKeyDown;
	if (inputElement is FrameworkElement)
	(inputElement as FrameworkElement).Unloaded -= CommandOnEnter_Unloaded;
}
 
private static void Attach(IInputElement inputElement)
{
	inputElement.PreviewKeyDown += 
		new KeyEventHandler(CommandOnEnter_PreviewKeyDown);
	if (inputElement is FrameworkElement)
		(inputElement as FrameworkElement).Unloaded += 
			new RoutedEventHandler(CommandOnEnter_Unloaded); 
}

The keyPressed event handler checks if the enter key is pressed and executes the command if yes. The command to execute is retrieved via the attachedProperties system of WPF (GetValue method):

C#
static void CommandOnEnter_PreviewKeyDown
	(object sender, System.Windows.Input.KeyEventArgs e)
{
	if (sender is IInputElement)
	{
		var textBox = sender as IInputElement;
		if (e.Key == Key.Enter && e.KeyboardDevice.Modifiers == 
							ModifierKeys.None)
		{
			ICommand cmd = GetCommand(sender as DependencyObject) 
								as ICommand;
			cmd.Execute(null);
		} 
	}
}

That's all!

Adding a Parameter?

What if you want to pass a parameter to the executed Command? You will simply have to add another Attached property and get its value in the keyPressed event handler.

C#
/// <summary>
/// Enables CommandParameter functionality
/// </summary>
public static object GetCommandParameter(DependencyObject obj)
{
	return (object)obj.GetValue(CommandParameterProperty);
}
 
/// <summary>
/// Enables CommandParameter functionality
/// </summary>
public static void SetCommandParameter(DependencyObject obj, object value)
{
	obj.SetValue(CommandParameterProperty, value);
}
 
/// <summary>
/// Enables CommandParameterProperty functionality
/// </summary>
public static readonly DependencyProperty CommandParameterProperty =
	DependencyProperty.RegisterAttached
	("CommandParameterProperty", typeof(object), typeof(CommandOnEnter));

Funny Things To Do

The Ramora pattern can be used to do a lot of things. Here is a list of some I'm thinking about:

  • Execute a command on a textbox when pressing enter
  • Select all the text when a textbox gets the focus
  • Etc.

Here are some links you may find useful:

  1. Thinking in WPF: attached properties
  2. More advanced attached property use: the Ramora pattern

Shout it kick it on DotNetKicks.com

This article was originally posted at http://blog.lexique-du-net.com/index.php

License

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


Written By
Software Developer http://wpf-france.fr
France (Metropolitan) France (Metropolitan)
Jonathan creates software, mostly with C#,WPF and XAML.

He really likes to works on every Natural User Interfaces(NUI : multitouch, touchless, etc...) issues.



He is awarded Microsoft MVP in the "Client Application Development" section since 2011.


You can check out his WPF/C#/NUI/3D blog http://www.jonathanantoine.com.

He is also the creator of the WPF French community web site : http://wpf-france.fr.

Here is some videos of the projects he has already work on :

Comments and Discussions

 
-- There are no messages in this forum --