Moving to next control on Enter keypress in WPF
I want to move to the next control when I press the Enter key instead of the Tab key in a WPF MVVM application. How can I achieve this?
Solution 1:
Below is an attached property that I've used for just this.
First, example usage:
<TextBox Width="100"
Text="{Binding Name, Mode=TwoWay}"
UI:FocusAdvancement.AdvancesByEnterKey="True" />
(UI is the namespace alias for where I've defined the following.)
The attached property:
public static class FocusAdvancement
{
public static bool GetAdvancesByEnterKey(DependencyObject obj)
{
return (bool)obj.GetValue(AdvancesByEnterKeyProperty);
}
public static void SetAdvancesByEnterKey(DependencyObject obj, bool value)
{
obj.SetValue(AdvancesByEnterKeyProperty, value);
}
public static readonly DependencyProperty AdvancesByEnterKeyProperty =
DependencyProperty.RegisterAttached("AdvancesByEnterKey", typeof(bool), typeof(FocusAdvancement),
new UIPropertyMetadata(OnAdvancesByEnterKeyPropertyChanged));
static void OnAdvancesByEnterKeyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as UIElement;
if(element == null) return;
if ((bool)e.NewValue) element.KeyDown += Keydown;
else element.KeyDown -= Keydown;
}
static void Keydown(object sender, KeyEventArgs e)
{
if(!e.Key.Equals(Key.Enter)) return;
var element = sender as UIElement;
if(element != null) element.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
You also said "instead of tab," so I'm wondering if you want to suppress the ability to use tab in the usual way. I'd advise against it, as it is a common, well known paradigm, but if that is the case, you can add a PreviewKeyDown
handler in the attached property, check for the tab key, and set Handled = true
for the event args.
Solution 2:
If you only want it to work for a few text boxes, Jay's answer is best.
If you want your whole application to work that way, makwana.a's answer is better but can be improved.
Below is my modification of makwana.a's answer, which I have used in numerous applications. It also includes support for moving to the next control via enter if the active control is a check box. Instead of using the tag property to decide whether or not the focus should move, I used the AcceptsReturn
property of the text box. I did this because it defaults to false and will only be set to true on multi-line text boxes. In that case, you won't want the focus to move to the next control on enter anyway.
Declare these event handlers in the OnStartup void of App.xaml
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.KeyDownEvent, new KeyEventHandler(TextBox_KeyDown));
EventManager.RegisterClassHandler(typeof(CheckBox), CheckBox.KeyDownEvent, new KeyEventHandler(CheckBox_KeyDown));
Here are the rest of the methods needed to make it work application wide.
void TextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter & (sender as TextBox).AcceptsReturn == false) MoveToNextUIElement(e);
}
void CheckBox_KeyDown(object sender, KeyEventArgs e)
{
MoveToNextUIElement(e);
//Sucessfully moved on and marked key as handled.
//Toggle check box since the key was handled and
//the checkbox will never receive it.
if (e.Handled == true)
{
CheckBox cb = (CheckBox)sender;
cb.IsChecked = !cb.IsChecked;
}
}
void MoveToNextUIElement(KeyEventArgs e)
{
// Creating a FocusNavigationDirection object and setting it to a
// local field that contains the direction selected.
FocusNavigationDirection focusDirection = FocusNavigationDirection.Next;
// MoveFocus takes a TraveralReqest as its argument.
TraversalRequest request = new TraversalRequest(focusDirection);
// Gets the element with keyboard focus.
UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;
// Change keyboard focus.
if (elementWithFocus != null)
{
if (elementWithFocus.MoveFocus(request)) e.Handled = true;
}
}
Edit
I updated the code to mark the keystroke as handled if the movement was successful and also toggle the checkbox since the key was handled and will no longer reach it.
Solution 3:
sample solution: using PreviewKeyDown in the stack panel. The Preview... is a bubble up so the event can be handled at a higher level. You may need to handle this differently for different element types, like button it seems should keep the enter key and not change focus on the enter key.
Here is the xaml:
<StackPanel PreviewKeyDown="StackPanel_PreviewKeyDown" >
<TextBox >
Hello
</TextBox>
<TextBox>
World
</TextBox>
<TextBox>
test
</TextBox>
</StackPanel>
And here is the code behind:
private void StackPanel_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
TextBox s = e.Source as TextBox;
if (s != null)
{
s.MoveFocus(new TraversalRequest( FocusNavigationDirection.Next));
}
e.Handled = true;
}
}
This is only a sandbox for proof of concept.
Happy Coding...
Solution 4:
Hope this help: use AttachedProperty http://madprops.org/blog/enter-to-tab-as-an-attached-property/
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;
}
}
}
<StackPanel my:EnterKeyTraversal.IsEnabled="True">