在WPF MVVM应用程序中,我希望按下回车键而不是Tab键时能够移动到下一个控件。我该如何实现这一功能?
下面是我用于此目的的附加属性。
首先,举个例子:
<TextBox Width="100"
Text="{Binding Name, Mode=TwoWay}"
UI:FocusAdvancement.AdvancesByEnterKey="True" />
(UI是我定义以下内容的命名空间别名。)
附加属性:
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));
}
}
你还说“而不是制表符(tab)”,所以我想知道您是否希望禁止使用通常的制表符。我建议不要这样做,因为它是一种常见且众所周知的范例,但如果是这种情况,您可以添加PreviewKeyDown
处理程序到附加属性中,检查制表符键,然后为事件参数设置Handled=true
。
如果你只想让它适用于几个文本框,Jay的回答最好。
如果你希望整个应用程序都能以这种方式工作,makwana.a的回答更好但也可改进。
以下是我对makwana.a的回答进行的修改,在许多应用程序中我都使用了它。它还包括对于当前活动控件为复选框的情况下支持通过按Enter键移动到下一个控件。我不使用标签属性来确定焦点是否应该移动,而是使用文本框的 AcceptsReturn
属性。我这样做是因为它默认值为false,仅在多行文本框上设置为true。在这种情况下,您也不希望焦点在按Enter时移动到下一个控件。
在App.xaml的OnStartup void中声明这些事件处理程序。
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.KeyDownEvent, new KeyEventHandler(TextBox_KeyDown));
EventManager.RegisterClassHandler(typeof(CheckBox), CheckBox.KeyDownEvent, new KeyEventHandler(CheckBox_KeyDown));
这里是使其在整个应用中正常工作所需的剩余方法。
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;
}
}
编辑
我已更新代码,标记按键处理成功后的移动,并切换复选框,因为按键已被处理,并且不会再次到达它。
FocusNavigationDirection focusDirection = (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) ? FocusNavigationDirection.Previous : FocusNavigationDirection.Next;
- undefined示例解决方案:在堆栈面板中使用PreviewKeyDown事件。此事件是向上冒泡,因此可以在更高级别上处理事件。您可能需要针对不同的元素类型以不同的方式处理此事件,例如按钮似乎应该保留回车键并且不在回车键上更改焦点。
这是XAML:
<StackPanel PreviewKeyDown="StackPanel_PreviewKeyDown" >
<TextBox >
Hello
</TextBox>
<TextBox>
World
</TextBox>
<TextBox>
test
</TextBox>
</StackPanel>
下面是代码:
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;
}
}
这只是一个概念验证的沙盒。
愉快地编码吧...
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">
EventManager.RegisterClassHandler(GetType(TextBox), TextBox.KeyDownEvent, New RoutedEventHandler(AddressOf TextBox_KeyDown))
Private Sub TextBox_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Input.KeyEventArgs)
If e.Key = Key.Enter And TryCast(sender, TextBox).Tag <> "1" Then
' Creating a FocusNavigationDirection object and setting it to a
' local field that contains the direction selected.
Dim focusDirection As FocusNavigationDirection = FocusNavigationDirection.Next
' MoveFocus takes a TraveralReqest as its argument.
Dim request As New TraversalRequest(focusDirection)
' Gets the element with keyboard focus.
Dim elementWithFocus As UIElement = TryCast(Keyboard.FocusedElement, UIElement)
' Change keyboard focus.
If elementWithFocus IsNot Nothing Then
elementWithFocus.MoveFocus(request)
End If
End If
End Sub
我曾使用文本框的“tag”属性来跳过移动焦点。例如,当您在多行文本框中需要按enter键创建新行时,有时不希望移动到下一个控件。只需将标记属性设置为1即可。
首先,需要为每个元素添加触发器,当PreviewKeyDown
触发时调用。还需要添加依赖属性,并绑定要聚焦的FrameworkElement
。在触发器中,设置将Focus
绑定到元素。
我想出了以下代码。请注意,它不会设置 e.Handled。此外,MoveFocus_Next 不会返回移动焦点是否成功,而是如果参数不为 null,则返回 true。您可以根据需要添加或删除要处理的控件类型。该代码是为应用程序的 MainWindow 写的,但也可处理其他窗口。您还可以调整代码以从 App_Startup 事件中调用。
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
public partial class MainWindow : Window
{
private bool MoveFocus_Next(UIElement uiElement)
{
if (uiElement != null)
{
uiElement.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
return true;
}
return false;
}
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
EventManager.RegisterClassHandler(typeof(Window), Window.PreviewKeyUpEvent, new KeyEventHandler(Window_PreviewKeyUp));
}
private void Window_PreviewKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
IInputElement inputElement = Keyboard.FocusedElement;
if (inputElement != null)
{
System.Windows.Controls.Primitives.TextBoxBase textBoxBase = inputElement as System.Windows.Controls.Primitives.TextBoxBase;
if (textBoxBase != null)
{
if (!textBoxBase.AcceptsReturn)
MoveFocus_Next(textBoxBase);
return;
}
if (
MoveFocus_Next(inputElement as ComboBox)
||
MoveFocus_Next(inputElement as Button)
||
MoveFocus_Next(inputElement as DatePicker)
||
MoveFocus_Next(inputElement as CheckBox)
||
MoveFocus_Next(inputElement as DataGrid)
||
MoveFocus_Next(inputElement as TabItem)
||
MoveFocus_Next(inputElement as RadioButton)
||
MoveFocus_Next(inputElement as ListBox)
||
MoveFocus_Next(inputElement as ListView)
||
MoveFocus_Next(inputElement as PasswordBox)
||
MoveFocus_Next(inputElement as Window)
||
MoveFocus_Next(inputElement as Page)
||
MoveFocus_Next(inputElement as Frame)
)
return;
}
}
}
}
Key.Enter
更改为Key.Up
。如果有其他问题,建议您发布一个新的问题。 - Jay