在WPF中按下Enter键后移动到下一个控件

40

在WPF MVVM应用程序中,我希望按下回车键而不是Tab键时能够移动到下一个控件。我该如何实现这一功能?

7个回答

49

下面是我用于此目的的附加属性。

首先,举个例子:

<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


+1。我需要这个与应用程序的其余部分保持一致,其中基于网格..按回车键退出编辑模式并移动到下一个字段。 - Gishu
@Jay,我需要帮助,这段代码无法工作,如果我需要使用向上键事件而不是回车键,你能否提供建议? - Anindya
1
@AnindyaChatterjee 如果你想要相同的行为,但是使用上箭头键,将 Key.Enter 更改为 Key.Up。如果有其他问题,建议您发布一个新的问题。 - Jay
@Jay,针对DatePicker控件,KeyDown事件无法正常工作。我将KeyDown替换为PreviewKeyDown,然后就可以正常工作了。不知道问题出在哪里。 - Uday

35

如果你只想让它适用于几个文本框,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;
        }
    }

编辑

我已更新代码,标记按键处理成功后的移动,并切换复选框,因为按键已被处理,并且不会再次到达它。


4
Jay的回答完美适用于在整个应用程序中使用。只需在您的TextBox控件模板中设置附加属性,它们就会按照预期工作。 - mcalex
1
@mcalex 除非你需要全部使用它,否则你必须拥有一个TextBox控件模板,并将该模板应用于每个文本框。如果不需要全部使用它,那么这会增加很多开销。 - StillLearnin
1
这个技巧非常好用。但我尝试让表单上的最后一个文本框在按下回车键时也具有提交行为。尽管上面的代码没有标记事件已处理,但是当RegisterClassHandler存在时,我的XAML中定义的按键被忽略了。我希望能够定义特定控件的其他事件行为(或更精确地说是命令绑定),并拥有应用程序范围内的默认设置。 - user2027080
1
这是一个真正的好解决方案。整个应用程序只需要一个中央代码块。运行得非常顺畅!谢谢! - SQL Police
1
我添加了这个来支持向后切换标签:FocusNavigationDirection focusDirection = (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) ? FocusNavigationDirection.Previous : FocusNavigationDirection.Next; - undefined
显示剩余6条评论

15

示例解决方案:在堆栈面板中使用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;
    }
}

这只是一个概念验证的沙盒。

愉快地编码吧...


1
坚实、简短、简单的回答。我认为这是最好的、通用的方法。 - Tronald

4
希望这可以帮助你:使用附加属性 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">

3
在您的应用程序文件的onstartup事件中编写此代码。
EventManager.RegisterClassHandler(GetType(TextBox), TextBox.KeyDownEvent, New RoutedEventHandler(AddressOf TextBox_KeyDown))

然后定义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即可。


1

首先,需要为每个元素添加触发器,当PreviewKeyDown触发时调用。还需要添加依赖属性,并绑定要聚焦的FrameworkElement。在触发器中,设置将Focus绑定到元素。


0

使用代码后台:

我想出了以下代码。请注意,它不会设置 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;
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接