I want to interpret Enter key as Tab key in whole my WPF application, that is, everywhere in my application when user press Enter I want to focus the next focusable control,except when button is focused. Is there any way to do that in application life circle? Can anyone give me an example?
Thanks a lot!
You can use my EnterKeyTraversal attached property code if you like. Add it to the top-level container on a WPF window and everything inside will treat enter as tab:
<StackPanel my:EnterKeyTraversal.IsEnabled="True">
...
</StackPanel>
Based on Richard Aguirre's answer, which is better than the selected answer for ease of use, imho, you can make this more generic by simply changing the Grid to a UIElement.
To change it in whole project you need to do this
In App.xaml.cs:
protected override void OnStartup(StartupEventArgs e)
{
EventManager.RegisterClassHandler(typeof(UIElement), UIElement.PreviewKeyDownEvent, new KeyEventHandler(Grid_PreviewKeyDown));
base.OnStartup(e);
}
private void Grid_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
var uie = e.OriginalSource as UIElement;
if (e.Key == Key.Enter)
{
e.Handled = true;
uie.MoveFocus(
new TraversalRequest(
FocusNavigationDirection.Next));
}
}
Compile.
And done it. Now you can use enter like tab.
Note: This work for elements in the grid
I got around woodyiii's issue by adding a FrameworkElement.Tag (whose value is IgnoreEnterKeyTraversal) to certain elements (buttons, comboboxes, or anything I want to ignore the enter key traversal) in my XAML. I then looked for this tag & value in the attached property. Like so:
if (e.Key == Key.Enter)
{
if (ue.Tag != null && ue.Tag.ToString() == "IgnoreEnterKeyTraversal")
{
//ignore
}
else
{
e.Handled = true;
ue.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
woodyiii, There is a function in the UIElement called PredictFocus() which by its name know its function, then you can check if that element is enabled or not so as to move the focus to it or not...
Here is Matt Hamilton's code, if anyone is wondering since his site is down apparently:
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;
}
}
}
Another, a more on/off implementation approach would be to use behaviors:
public class TextBoxEnterFocusesNextBehavior :
Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewKeyDown += AssociatedObjectOnPreviewKeyDown;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewKeyDown -= AssociatedObjectOnPreviewKeyDown;
base.OnDetaching();
}
private void AssociatedObjectOnPreviewKeyDown(object sender, KeyEventArgs args)
{
if (args.Key != Key.Enter) { return; }
args.Handled = true;
AssociatedObject.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
Usage example:
<UserControl xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:behaviors="clr-namespace:Your.Namespace.To.Behaviors"
...>
<DockPanel>
<TextBox x:Name="TextBoxWithBehavior"
DockPanel.Dock="Top">
<b:Interaction.Behaviors>
<behaviors:TextBoxEnterFocusesNextBehavior />
</b:Interaction.Behaviors>
</TextBox>
<TextBox x:Name="TextBoxWithoutBehavior"
DockPanel.Dock="Top" />
<TextBox x:Name="AnotherTextBoxWithBehavior"
DockPanel.Dock="Top">
<b:Interaction.Behaviors>
<behaviors:TextBoxEnterFocusesNextBehavior />
</b:Interaction.Behaviors>
</TextBox>
</DockPanel>
</UserControl>
My solution:
public class MoveToNext : TriggerAction<DependencyObject>
{
protected override void Invoke(object parameter)
{
if (parameter is RoutedEventArgs routedEventArgs && routedEventArgs.OriginalSource is FrameworkElement element)
{
routedEventArgs.Handled = true;
element.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}
Usage:
<StackPanel>
<i:Interaction.Triggers>
<i:KeyTrigger Key="Return">
<util:MoveToNext/>
</i:KeyTrigger>
</i:Interaction.Triggers>
<!-- put your controls here -->
</StackPanel>
If you want the behavior to be attached to only one control instead of all controls within a layouter, simply add the <i:Interaction.Triggers block to that specific control.
Related
I created a subclass of FrameworkElement that has a collection of Visuals :
public class GameElement : FrameworkElement
{
private VisualCollection Visuals { get; }
public GameElement()
{
this.KeyDown += this.OnKeyDown;
this.MouseDown += this.OnMouseDown;
}
private void OnKeyDown(object sender, KeyEventArgs keyEventArgs)
{
... // Does not get fired.
}
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
... // Does get fired.
}
protected override void OnRender(DrawingContext drawingContext)
{
// Draw a transparent background to catch mouse events (otherwise hit testing won't hit anything).
drawingContext.DrawRectangle(Brushes.Transparent, null, new Rect(0, 0, RenderSize.Width, RenderSize.Height));
}
protected override int VisualChildrenCount
{
get
{
return this.Visuals.Count;
}
}
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= this.Visuals.Count)
{
throw new ArgumentOutOfRangeException();
}
return this.Visuals[index];
}
}
I display this element in XAML with the following code :
<UserControl
x:Class="..."
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="Black">
<Grid Margin="10">
<local:GameElement x:Name="GameElement" ClipToBounds="True" />
</Grid>
</UserControl>
I have tried everything I can think of, but I just cannot get the KeyDown event to fire. The mostly used comment I find online has to do with focus. I have tried every combination of Focusable="True" and calling this.Focus(), but nothing works.
Anyone got any idea how to do this?
Thanks!
To be able to handle keys pressing your element should be focused.
Also try to derive it from control instead of FramworkElement if you can do that.
public class GameElement : Control
{
private VisualCollection Visuals { get; }
public GameElement()
{
this.KeyDown += this.OnKeyDown;
this.MouseDown += this.OnMouseDown;
}
private void OnKeyDown(object sender, KeyEventArgs keyEventArgs)
{
// Does get fired.
}
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
Focus();
}
protected override void OnRender(DrawingContext drawingContext)
{
// Draw a transparent background to catch mouse events (otherwise hit testing won't hit anything).
drawingContext.DrawRectangle(Brushes.Transparent, null, new Rect(0, 0, RenderSize.Width, RenderSize.Height));
}
protected override int VisualChildrenCount
{
get
{
return this.Visuals.Count;
}
}
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= this.Visuals.Count)
{
throw new ArgumentOutOfRangeException();
}
return this.Visuals[index];
}
}
I finally got it to work by registering a class handler that also handles handled events.
EventManager.RegisterClassHandler(typeof(Window), Keyboard.KeyDownEvent, new KeyEventHandler(OnKeyDown), true);
I'm using the WPF TabControl in my application in order to switch between different areas/functions of the program.
One thing annoys me though. I have hidden the tabs so I can control the selectedtab, instead of the user. The user however, can still switch between tabs using the arrow-keys.
I have tried using the KeyboardNavigation-attribute, but I can't get this working.
Can this be disabled?
You can hook on to the TabControl.PreviewKeyDown event for this one. Check to see if it's the left or right arrow and say that you've handled it.
private void TabControl_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Left || e.Key == Key.Right)
e.Handled = true;
}
if you're using a pure view model application you could apply the above as an attached property.
XAMl to use the below attached property.
<TabControl local:TabControlAttached.IsLeftRightDisabled="True">
<TabItem Header="test"/>
<TabItem Header="test"/>
</TabControl>
TabControlAttached.cs
public class TabControlAttached : DependencyObject
{
public static bool GetIsLeftRightDisabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsLeftRightDisabledProperty);
}
public static void SetIsLeftRightDisabled(DependencyObject obj, bool value)
{
obj.SetValue(IsLeftRightDisabledProperty, value);
}
// Using a DependencyProperty as the backing store for IsLeftRightDisabled. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsLeftRightDisabledProperty =
DependencyProperty.RegisterAttached("IsLeftRightDisabled", typeof(bool), typeof(MainWindow), new UIPropertyMetadata(false, new PropertyChangedCallback((s, e) =>
{
// get a reference to the tab control.
TabControl targetTabControl = s as TabControl;
if (targetTabControl != null)
{
if ((bool)e.NewValue)
{
// Need some events from it.
targetTabControl.PreviewKeyDown += new KeyEventHandler(targetTabControl_PreviewKeyDown);
targetTabControl.Unloaded += new RoutedEventHandler(targetTabControl_Unloaded);
}
else if ((bool)e.OldValue)
{
targetTabControl.PreviewKeyDown -= new KeyEventHandler(targetTabControl_PreviewKeyDown);
targetTabControl.Unloaded -= new RoutedEventHandler(targetTabControl_Unloaded);
}
}
})));
static void targetTabControl_Unloaded(object sender, RoutedEventArgs e)
{
TabControl targetTabControl = sender as TabControl;
targetTabControl.PreviewKeyDown -= new KeyEventHandler(targetTabControl_PreviewKeyDown);
targetTabControl.Unloaded -= new RoutedEventHandler(targetTabControl_Unloaded);
}
static void targetTabControl_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Left || e.Key == Key.Right)
e.Handled = true;
}
}
I have a user control that is nested inside a window that is acting as a shell for a dialog display. I ignore focus in the shell window, and in the hosted user control I use the FocusManager to set the initial focus to a named element (a textbox) as shown below.
This works, setting the cursor at the beginning of the named textbox; however I want all text to be selected.
The TextBoxSelectionBehavior class (below) usually does exactly that, but not in this case. Is there an easy xaml fix to get the text in the named textbox selected on initial focus?
Cheers,
Berryl
TextBox Selection Behavior
// in app startup
TextBoxSelectionBehavior.RegisterTextboxSelectionBehavior();
/// <summary>
/// Helper to select all text in the text box on entry
/// </summary>
public static class TextBoxSelectionBehavior
{
public static void RegisterTextboxSelectionBehavior()
{
EventManager.RegisterClassHandler(typeof(TextBox), UIElement.GotFocusEvent, new RoutedEventHandler(OnTextBox_GotFocus));
}
private static void OnTextBox_GotFocus(object sender, RoutedEventArgs e)
{
var tb = (sender as TextBox);
if (tb != null)
tb.SelectAll();
}
}
The hosted UserControl
<UserControl
<DockPanel KeyboardNavigation.TabNavigation="Local"
FocusManager.FocusedElement="{Binding ElementName=tbLastName}" >
<TextBox x:Name="tbLastName" ... />
stop gap solution
Per comments with Rachel below, I ditched the FocusManger in favor of some code behind:
tbLastName.Loaded += (sender, e) => tbLastName.Focus();
Still would love a declarative approach for a simple and common chore though...
I usually use an AttachedProperty to make TextBoxes highlight their text on focus. It is used like
<TextBox local:HighlightTextOnFocus="True" />
Code for attached property
public static readonly DependencyProperty HighlightTextOnFocusProperty =
DependencyProperty.RegisterAttached("HighlightTextOnFocus",
typeof(bool), typeof(TextBoxProperties),
new PropertyMetadata(false, HighlightTextOnFocusPropertyChanged));
[AttachedPropertyBrowsableForChildrenAttribute(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static bool GetHighlightTextOnFocus(DependencyObject obj)
{
return (bool)obj.GetValue(HighlightTextOnFocusProperty);
}
public static void SetHighlightTextOnFocus(DependencyObject obj, bool value)
{
obj.SetValue(HighlightTextOnFocusProperty, value);
}
private static void HighlightTextOnFocusPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var sender = obj as UIElement;
if (sender != null)
{
if ((bool)e.NewValue)
{
sender.GotKeyboardFocus += OnKeyboardFocusSelectText;
sender.PreviewMouseLeftButtonDown += OnMouseLeftButtonDownSetFocus;
}
else
{
sender.GotKeyboardFocus -= OnKeyboardFocusSelectText;
sender.PreviewMouseLeftButtonDown -= OnMouseLeftButtonDownSetFocus;
}
}
}
private static void OnKeyboardFocusSelectText(
object sender, KeyboardFocusChangedEventArgs e)
{
var textBox = e.OriginalSource as TextBox;
if (textBox != null)
{
textBox.SelectAll();
}
}
private static void OnMouseLeftButtonDownSetFocus(
object sender, MouseButtonEventArgs e)
{
TextBox tb = FindAncestor<TextBox>((DependencyObject)e.OriginalSource);
if (tb == null)
return;
if (!tb.IsKeyboardFocusWithin)
{
tb.Focus();
e.Handled = true;
}
}
static T FindAncestor<T>(DependencyObject current)
where T : DependencyObject
{
current = VisualTreeHelper.GetParent(current);
while (current != null)
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
};
return null;
}
Edit
Based on comments below, what about just getting rid of the FocusManager.FocusedElement and setting tb.Focus() and tb.SelectAll() in the Loaded event of your TextBox?
As stated above, you can add an event handler for the Loaded event to set focus and select all text:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
base.DataContext = new Person { FirstName = "Joe", LastName = "Smith" };
base.Loaded += delegate
{
this._firstNameTextBox.Focus();
this._firstNameTextBox.SelectAll();
};
}
}
In WinForms we could specify DialogResult for buttons. In WPF we can declare in XAML only Cancel button:
<Button Content="Cancel" IsCancel="True" />
For others we need to catch ButtonClick and write code like that:
private void Button_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
}
I am using MVVM, so I have only XAML code for windows. But for modal windows I need to write such code and I don't like this. Is there a more elegant way to do such things in WPF?
You can do this with an attached behavior to keep your MVVM clean. The C# code for your attached behavior might look something like so:
public static class DialogBehaviors
{
private static void OnClick(object sender, RoutedEventArgs e)
{
var button = (Button)sender;
var parent = VisualTreeHelper.GetParent(button);
while (parent != null && !(parent is Window))
{
parent = VisualTreeHelper.GetParent(parent);
}
if (parent != null)
{
((Window)parent).DialogResult = true;
}
}
private static void IsAcceptChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var button = (Button)obj;
var enabled = (bool)e.NewValue;
if (button != null)
{
if (enabled)
{
button.Click += OnClick;
}
else
{
button.Click -= OnClick;
}
}
}
public static readonly DependencyProperty IsAcceptProperty =
DependencyProperty.RegisterAttached(
name: "IsAccept",
propertyType: typeof(bool),
ownerType: typeof(Button),
defaultMetadata: new UIPropertyMetadata(
defaultValue: false,
propertyChangedCallback: IsAcceptChanged));
public static bool GetIsAccept(DependencyObject obj)
{
return (bool)obj.GetValue(IsAcceptProperty);
}
public static void SetIsAccept(DependencyObject obj, bool value)
{
obj.SetValue(IsAcceptProperty, value);
}
}
You can use the property in XAML with the code below:
<Button local:IsAccept="True">OK</Button>
An alternative way is to use Popup Control
Try this tutorial.
Working on a TouchScreen application which also has a keyboard attached, I have the following problem:
The WPF window has a TextBox, which should receive ALL keyboard input. There are also Buttons and a ListBox, which are solely used by the TouchScreen(=Mouse).
A very simple example looks like this:
<Window x:Class="KeyboardFocusTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1">
<StackPanel>
<TextBox Text="{Binding Input, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PreviewLostKeyboardFocus="TextBox_PreviewLostKeyboardFocus"/>
<Button Click="Button_Click">Add</Button>
<ListBox ItemsSource="{Binding Strings}" />
</StackPanel>
</Window>
To keep the TextBox always focused, I just do:
private void TextBox_PreviewLostKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
{
e.Handled = true;
}
So far so good - the problem now is, that I can't select items from the ListBox anymore. This only seems to work, if the ListBox has the keyboard focus. But if I loose the keyboard focus on the TextBox, I can't enter text anymore without clicking it first.
Any ideas, comments suggestions are welcome!
There might be a more elegant solution for this, but you could always handle the PreviewKeyDown event at the Window level, and pass focus to the TextBox if it doesn't already have it, instead of preventing it from losing focus in the first place. That way, the ListBox can use focus as is normal, but as soon as a key is pressed focus jumps right to the TextBox. In addition, you can filter out keys that you don't want to switch focus - the arrow keys come to mind, which could then be used to move up and down in the ListBox.
Adding an event handler like the following should do the trick:
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (!textBox.IsFocused)
{
textBox.Focus();
}
}
Based on Nicholas' suggestion (thx!), here's a markup extension, which is used like:
<TextBox Helpers:KeyboardFocusAttractor.IsAttracted="true" />
It seems to work, and ANTS didn't show any memory leaks. But when it comes to WPF and especially events and bindings, you never know, so use with care!
public static class KeyboardFocusAttractor
{
public static readonly DependencyProperty IsAttracted = DependencyProperty.RegisterAttached("IsAttracted",
typeof (bool), typeof (KeyboardFocusAttractor), new PropertyMetadata(false, OnIsAttracted));
private static void OnIsAttracted(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var isAttracted = (bool) e.NewValue;
var controlWithInputFocus = d as Control;
if (controlWithInputFocus != null)
{
if (isAttracted)
{
new KeyboardInputFocusEventManager(controlWithInputFocus);
}
}
}
public static void SetIsAttracted(DependencyObject dp, bool value)
{
dp.SetValue(IsAttracted, value);
}
public static bool GetIsAttracted(DependencyObject dp)
{
return (bool) dp.GetValue(IsAttracted);
}
private class KeyboardInputFocusEventManager
{
private readonly Control _control;
private Window _window;
public KeyboardInputFocusEventManager(Control control)
{
_control = control;
_control.Loaded += ControlLoaded;
_control.IsVisibleChanged += ControlIsVisibleChanged;
_control.Unloaded += ControlUnloaded;
}
private void ControlLoaded(object sender, RoutedEventArgs e)
{
_window = Window.GetWindow(_control);
if (_window != null)
{
_control.Unloaded += ControlUnloaded;
_control.IsVisibleChanged += ControlIsVisibleChanged;
if (_control.IsVisible)
{
_window.PreviewKeyDown += ParentWindowPreviewKeyDown;
}
}
}
private void ControlUnloaded(object sender, RoutedEventArgs e)
{
_control.Unloaded -= ControlUnloaded;
_control.IsVisibleChanged -= ControlIsVisibleChanged;
}
private void ControlIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (_window != null)
{
_window.PreviewKeyDown -= ParentWindowPreviewKeyDown;
}
if (_control.IsVisible)
{
_window = Window.GetWindow(_control);
if (_window != null)
{
_window.PreviewKeyDown += ParentWindowPreviewKeyDown;
}
}
}
private void ParentWindowPreviewKeyDown(object sender, KeyEventArgs e)
{
Keyboard.Focus(_control);
}
}
}