Enter to Tab as an Attached Property
Updated! As suggested by Eric Burke in the comments, I'm now handling the "Unloaded" event and unhooking the event handlers there, so the elements are properly cleaned up by the garbage collector. Thanks Eric!
A while ago I posted about a trick to make your WPF applications treat the enter key as a tab, and shift focus to the next available control. Paul commented that it should be possible to do that using an attached property, and I agreed, but didn't know how.
Well, now I do.
I'll post the full code for the property itself below, but first, its usage. Firstly you'll need to add an xmlns declaration to the top of your form pointing to the class' namespace. Usually this is just the namespace of your application, like WpfApplication1. I like to call this XML namespace "my". So your window's declaration ends up looking like:
<Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:my="clr-namespace:WpfApplication1" Title="Window1">
Next you want to find the container control (usually a Grid or StackPanel) in which all your data-entry controls live, and add the attached property to it, like this:
<StackPanel my:EnterKeyTraversal.IsEnabled="True">
Now all the controls within that StackPanel will treat the Enter key as a tab! Once the focus shifts out of the StackPanel the Enter key reverts to its normal behaviour, which is nice because the very next control will probably be an "OK" button, and the user will want to be able to press Enter to click it.
Here's the complete class declaration to give you the EnterKeyTraversal.IsEnabled property. Have fun!
public class EnterKeyTraversal { public static bool GetIsEnabled(DependencyObject obj) { return (bool)obj.GetValue(IsEnabledProperty); } public static void SetIsEnabled(DependencyObject obj, bool value) { obj.SetValue(IsEnabledProperty, value); } static void ue_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) { var ue = e.OriginalSource as FrameworkElement; if (e.Key == Key.Enter) { e.Handled = true; ue.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)); } } private static void ue_Unloaded(object sender, RoutedEventArgs e) { var ue = sender as FrameworkElement; if (ue == null) return; ue.Unloaded -= ue_Unloaded; ue.PreviewKeyDown -= ue_PreviewKeyDown; } public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled", typeof(bool),
typeof(EnterKeyTraversal), new UIPropertyMetadata(false, IsEnabledChanged)); static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ue = d as FrameworkElement; if (ue == null) return; if ((bool)e.NewValue) { ue.Unloaded += ue_Unloaded; ue.PreviewKeyDown += ue_PreviewKeyDown; } else { ue.PreviewKeyDown -= ue_PreviewKeyDown; } } }

Comments
# eric burke
18/08/2008 4:11 AM
don't forget to unhook that event handler when the UE is unloaded also, and not just when IsEnabled is set to false (if it's a FrameworkElement), otherwise you will leak and keep your whole window around. ;)
# mabster
18/08/2008 8:52 AM
Hi Eric,
Now you've got me worried! What's the best approach to do that? Keep a list of controls I've handled and then catch the Application.Exit event to unhook the handlers?
# eric burke
19/08/2008 12:27 AM
there are 3 approaches that i've seen. i'm sure others exist but these are the most common.
1. have the owner of the FrameworkElement you are working with explicitly set EnterKeyTraversal.IsEnabled="false" when the element is unloaded. this is ok, but it sort of defeats the purpose of the attached property.
2. have the helper class listen for Unloaded. your code is really close, just needs a small refactor.
+ write a function called HookHandlers() which listens for Unloaded as well as any other events you care about
+ write a function called UnhookHandlers() which removes all those event handlers, including Unloaded
+ when IsEnabled is set, call HookHandlers()
+ when IsEnabled is cleared, call UnhookHandlers()
+ when your Unloaded handler fires, call UnhookHandlers()
3. use the WeakEvent pattern. this works well, but requires you to have an instance of a class that can implement IWeakEventListener. it's more code than it's worth IMO.
attached properties are a double-edged sword. they make it super-easy to extend the functionality of built-in components, but they often aren't aware of object lifetimes.
for FrameworkElements, you can watch Unloaded, but for data objects, you either have to hold WeakReferences or somehow know when you can/should release your strong references.
as you might have guessed, i've spent waaay too much time tracking down why my Window (and all its data/visuals/etc) were leaking, only to find that i was holding a list of objects in a helper class, one of which was something in the Window. :(
# mabster
19/08/2008 8:52 AM
Thanks for the reply, Eric.
Which "Unloaded" event are you talking about here? UIElement doesn't seem to have one.
# mabster
19/08/2008 9:53 AM
Ah! Got it! I have changed the property to work with FrameworkElement instead of UIElement and used the Unloaded event to disconnect the handlers. Thanks Eric!
Leave a Comment