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
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:
- Create a class of your choice, no inheritance needed
- Declare an
attachedProperty
by registering it and adding the corresponding GetXXX and SetXXX
- 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
),
- Add the behavior logic inside this last event handler
- 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?).
#region Command Property
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
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
.
static void CommandPropertyChanged
(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Debug.Assert(sender is IInputElement,
"Attached property only for a IInputElement");
if (sender is IInputElement)
{
IInputElement inputElement = sender as IInputElement;
if (e.OldValue != null)
{
Detach(inputElement);
}
if (e.NewValue != null)
{
Attach(inputElement);
}
}
}
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):
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.
public static object GetCommandParameter(DependencyObject obj)
{
return (object)obj.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(DependencyObject obj, object value)
{
obj.SetValue(CommandParameterProperty, value);
}
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:
- Thinking in WPF: attached properties
- More advanced attached property use: the Ramora pattern
CodeProject