【问题标题】:Moving to next control on Enter keypress in WPF移动到 WPF 中 Enter 按键上的下一个控件
【发布时间】:2012-01-02 10:49:46
【问题描述】:

当我在 WPF MVVM 应用程序中按 Enter 键而不是 Tab 键时,我想移动到下一个控件。我怎样才能做到这一点?

【问题讨论】:

    标签: c# wpf mvvm


    【解决方案1】:

    以下是我专门用于此的附加属性。

    一、用法示例:

    <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));
        }
    }
    

    您还说“而不是制表符”,所以我想知道您是否想抑制以通常方式使用制表符的能力。我建议不要这样做,因为它是一种常见的、众所周知的范例,但如果是这种情况,您可以在附加属性中添加一个 PreviewKeyDown 处理程序,检查 tab 键,然后将 Handled = true 设置为事件参数。

    【讨论】:

    • +1。我需要它与基于网格的应用程序的其余部分“保持一致”。按 Enter 退出编辑模式并移至下一个字段。
    • @Jay 我需要帮助,如果我需要向上键事件而不是输入键,则此代码不起作用。您能否建议
    • @AnindyaChatterjee 如果您想要相同的行为,但使用向上键,请将Key.Enter 更改为Key.Up。还有什么,我建议你发布一个新问题。
    • @Jay 对于 DatePicker 控件 KeyDown 事件不起作用。我用 PreviewKeyDown 替换 KeyDown 比这很好用。不知道是什么问题。
    【解决方案2】:

    如果您只希望它适用于几个文本框,Jay's answer 是最好的。

    如果您希望整个应用程序都以这种方式工作,makwana.a's answer 会更好,但可以改进。

    下面是我对makwana.a's answer 的修改,我在许多应用程序中都使用过它。如果活动控件是复选框,它还支持通过 enter 移动到下一个控件。我没有使用 tag 属性来决定焦点是否应该移动,而是使用了文本框的 AcceptsReturn 属性。我这样做是因为它默认为 false,并且只会在多行文本框中设置为 true。在这种情况下,无论如何,您都不希望焦点移动到输入时的下一个控件。

    在 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;
            }
        }
    

    编辑

    如果移动成功,我更新了代码以将击键标记为已处理,并且还切换复选框,因为键已被处理并且将不再到达它。

    【讨论】:

    • 这很好用,除了我也想将它用于 ComboBoxes。但是,当 ComboBox 将 IsTextSearchEnabled 设置为 true(它可能是 IsEditable)时,上述代码会遍历两次而不是仅一次。这是因为上面的代码从不“处理”关键事件。调用 MoveToNextUIElement() 后,所有 key_down 事件的主体应将 e.Handled 设置为 true;
    • @Zamotic 不错。我更新了示例以将击键标记为已处理,但前提是焦点移动成功。
    • Jay 的答案是在您的整个应用程序中工作的正确答案。只需在您的 TextBox 控件模板中设置附加属性,它们都可以按需要工作。
    • @mcalex 除了你必须有一个文本框控件模板并且你必须将该模板应用于每个文本框。如果您不需要全部,那将是很多开销。
    • 这真是一个很棒的解决方案。整个应用程序的核心代码。奇迹般有效!谢谢!
    【解决方案3】:

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

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

    快乐编码...

    【讨论】:

    • 可靠、简短和简单的答案。我认为这是最好的通用方法
    【解决方案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">
    

    【讨论】:

      【解决方案5】:

      在应用程序文件的 onstartup 事件中编写此代码

      EventManager.RegisterClassHandler(GetType(TextBox), TextBox.KeyDownEvent, New RoutedEventHandler(AddressOf TextBox_KeyDown))
      

      然后定义TextBox_KeyDown sub为

       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
      

      我已经使用文本框的“标签”属性来跳过移动焦点。即,如果某个时候您不想在按下回车键时移动到下一个控件(在多行文本框的情况下,需要回车来创建新行)。只需将标签属性设置为 1。

      【讨论】:

        【解决方案6】:

        首先为每个元素添加触发器,当PreviewKeyDown 触发时将调用该触发器。还要添加 Dependency 属性并绑定您不会关注的 FrameworkElement。在触发器中提供设置 Focus 到绑定元素。

        【讨论】:

          【解决方案7】:

          使用代码隐藏:

          我想出了下面的代码。请注意,它没有设置 e.Handled。此外,MoveFocus_Next 不返回移动焦点是否成功,而是返回参数不为空的情况。您可以根据需要添加或删除要处理的控件类型。该代码是为应用程序的 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;
                      }
                  }
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2017-05-08
            • 2016-01-22
            • 2012-05-11
            • 1970-01-01
            • 2011-06-04
            • 2013-10-16
            • 1970-01-01
            相关资源
            最近更新 更多