【问题标题】:WPF: Move focus to the next item in an ItemsControl on enterWPF:在进入时将焦点移动到 ItemsControl 中的下一个项目
【发布时间】:2015-03-13 08:58:32
【问题描述】:

WPF:当用户在 ItemsControl 中的 texbox 内按下回车键时,我想将焦点移至 ItemsControl 中下一项中的文本框,或者如果用户在最后一项中,则创建一个新文本框。

为了更清楚:

场景 1

ItemsControl items:
[ textbox in item 1 ] <- user is here
[ textbox in item 2 ]
[ textbox in item 3 ]

回车后:

[ textbox in item 1 ]
[ textbox in item 2 ] <- user is here
[ textbox in item 3 ]

场景 2

ItemsControl 项:

[ textbox in item 1 ]
[ textbox in item 2 ]
[ textbox in item 3 ] <- user is here

回车后:

[ textbox in item 1 ]
[ textbox in item 2 ]
[ textbox in item 3 ]
[ textbox in item 4 ] <- user is here

如果有帮助,这里是项目数据模板的代码:

<ItemsControl.ItemTemplate>
    <DataTemplate>
        <Grid Background="White">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="32"/>
            </Grid.ColumnDefinitions>
            <TextBox Text="{Binding Path=PartName, FallbackValue='----',TargetNullValue='----', NotifyOnSourceUpdated=True}" KeyDown="TextBox_KeyDown"/>
            <Button Grid.Column="1" FontSize="10" x:Name="DeletePartButton" Click="DeletePartButton_Click" Height="22">Usuń</Button>
        </Grid>
    </DataTemplate>
</ItemsControl.ItemTemplate>

编辑 2: 我使用 ItemsControl 是因为不需要选择功能。

编辑 3: 我找到了部分解决方案。它适用于将焦点移动到下一个元素,但不是新元素(这是这里最重要的功能)

    private void PartNameTextBox_KeyDown(object sender, KeyEventArgs e)
    {
        var box = (TextBox)sender;

        if (e.Key == Key.Enter)
        {
            var part = (PiecePart)box.DataContext;
            int index = part.ParentPiece.Parts.IndexOf(part);
            if (index == part.ParentPiece.PartCount - 1)
            {
                part.ParentPiece.Parts.Add(new PiecePart(GetNewPartName(part.ParentPiece)));
                bool success = PartListBox.ApplyTemplate();
                // try to force wpf to build a visual tree for the new item success = false :(
            }
// throws out of bounds exception if a new item was added (and wasn't added to a visual tree)
            var el = ((UIElement)VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(PartListBox, 0),0),1),0),0),++index),0),0));
            el.Focus();
        }
    }

【问题讨论】:

    标签: c# wpf listbox


    【解决方案1】:

    PreviewKeyDown 的 TextBox 上

    private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
     if (e.Key == Key.Enter)
      {
       var txt= sender as TextBox;
       var selecteditem=FindParent<ListBoxItem>(txt);
       int index = ListBox.ItemContainerGenerator.IndexFromContainer(selecteditem);
       if(index<ListBox.Items.Count)
        {
        var afterItem=(ListBoxItem)ListBox.ItemContainerGenerator.ContainerFromIndex(index+1);
        TextBox tbFind = GetDescendantByType(afterItem, typeof (TextBox), "TextBox") as TextBox;
        if (tbFind != null)
        {
         FocusHelper.Focus(tbFind);
        }
       }
      }
    }
    
    public static Visual GetDescendantByType(Visual element, Type type, string name)
    {
     if (element == null) return null;
     if (element.GetType() == type)
     {
      FrameworkElement fe = element as FrameworkElement;
      if (fe != null)
      {
         if (fe.Name == name)
         {
            return fe;
         }
      }
     }
    Visual foundElement = null;
    if (element is FrameworkElement)
      (element as FrameworkElement).ApplyTemplate();
    for (int i = 0;
        i < VisualTreeHelper.GetChildrenCount(element);
        i++)
    {
      Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
      foundElement = GetDescendantByType(visual, type, name);
      if (foundElement != null)
         break;
    }
    return foundElement;
    }
    
    public static T FindParent<T>(DependencyObject child) where T : DependencyObject
    {
     //get parent item
    DependencyObject parentObject = VisualTreeHelper.GetParent(child);
    
    //we've reached the end of the tree
    if (parentObject == null) return null;
    
    //check if the parent matches the type we're looking for
    T parent = parentObject as T;
    if (parent != null)
        return parent;
    else
        return FindParent<T>(parentObject);
    }
    

    在 TextBox 上设置焦点的另一个助手:

    public static class FocusHelper
    {
    public static void Focus(UIElement element)
    {
     element.Dispatcher.BeginInvoke(DispatcherPriority.Input, new ThreadStart(delegate()
     {
        element.Focus();
     }));
    }
    }
    

    【讨论】:

      【解决方案2】:

      在 WPF 中将焦点移动到下一个元素的正确方法是使用 TraversalRequest 类,它表示将焦点移动到另一个控件的请求指定的 FocusNavigationDirection Enumeration用户界面 (UI) 中尝试所需焦点更改请求的方向。此示例取自 MSDN 上的TraversalRequest 类页面:

      // Creating a FocusNavigationDirection object and setting it to a 
      // local field that contains the direction selected.
      FocusNavigationDirection focusDirection = _focusMoveValue;
      
      // 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)
      {
          elementWithFocus.MoveFocus(request);
      } 
      

      【讨论】:

      • 谢谢,它可以将焦点移动到现有项目,但是当创建一个新项目时,焦点就会消失。
      • 然后在创建一个新元素后重新聚焦所需的元素,尽管我建议无论如何都要聚焦新元素。
      • 怎么样?我没有在代码中创建视觉元素,所以我没有对它的引用。我将一个新项目添加到源集合中。之后,它出现在列表中,但同时它不存在于可视化树中。如果有 ItemsControl.ItemAdded 事件或类似的...
      【解决方案3】:
      Listbox.SelectedIndex = 0;
      
      private void Listbox_OnKeyUp(object sender, KeyEventArgs e)
      {
          if (e.Key== Key.Enter)
          {
               if(Listbox.Items.Count-1>Listbox.SelectedIndex)
                   Listbox.SelectedIndex++;
               else 
                   Listbox.SelectedIndex=0;
          }
      }
      

      当用户关注你的列表框时,这个工作。

      【讨论】:

        【解决方案4】:

        我明白了。为了将焦点移到下一个元素,我使用了 Sheridan 的解决方案。要将焦点移动到新元素,我使用“添加”标志和 TextBox.Loaded 事件。

        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Grid Background="White" >
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="32"/>
                    </Grid.ColumnDefinitions>
                    <TextBox x:Name="PartNameTextbox" Text="{Binding Path=PartName, FallbackValue='----',TargetNullValue='----', NotifyOnSourceUpdated=True}" KeyDown="PartNameTextBox_KeyDown" 
                 Loaded="PartNameTextbox_Loaded"/>
                    <Button Grid.Column="1" FontSize="10" x:Name="DeletePartButton" Click="DeletePartButton_Click" Height="22">Usuń</Button>
                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        

        代码隐藏:

        bool partAdding = false;
        private void PartNameTextBox_KeyDown(object sender, KeyEventArgs e)
        {
            var box = (TextBox)sender;
        
            if (e.Key == Key.Enter)
            {
                var part = (PiecePart)box.DataContext;
                int index = part.ParentPiece.Parts.IndexOf(part);
                if (index == part.ParentPiece.PartCount - 1)
                {
                    part.ParentPiece.Parts.Add(new PiecePart(GetNewPartName(part.ParentPiece)));
                    UpdateCurrentLine(part.ParentPiece);
                    partAdding = true;
                }
                // Gets the element with keyboard focus.
                UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;
        
                // Creating a FocusNavigationDirection object and setting it to a 
                // local field that contains the direction selected.
                FocusNavigationDirection focusDirection = FocusNavigationDirection.Down;
        
                // MoveFocus takes a TraveralReqest as its argument.
                TraversalRequest request = new TraversalRequest(focusDirection);
        
                // Change keyboard focus. 
                if (elementWithFocus != null)
                {
                    elementWithFocus.MoveFocus(request);
                }
            }
        }
        
        private void PartNameTextbox_Loaded(object sender, RoutedEventArgs e)
        {
            if (partAdding)
            {
                var box = ((TextBox)sender);            
                var pp = ((PiecePart) box.DataContext);
                if (pp.IsLastPart)
                {
                    box.Focus();
                    box.SelectionStart = box.Text.Length;
                    partAdding = false;
                }
            }
        }
        

        【讨论】:

          【解决方案5】:

          C#

              private void ListBox_KeyUp(object sender, KeyEventArgs e)
              {
                  int selectIndex = listBox.SelectedIndex;
                  int listItemCount = listBox.Items.Count;
                  if (e.Key == Key.Enter)
                  {
                      if (selectIndex == listItemCount - 1)
                      {
                          ListBoxItem newItem = new ListBoxItem();
                          newItem.Content = "Your Content";
                          listBox.Items.Add(newItem);
                          listBox.SelectedItem = newItem;
                      }
                      else
                      {
                          listBox.SelectedIndex = selectIndex + 1;
                      }
                  }
              }
          

          XAML

                  <ListBox HorizontalAlignment="Left" Name="listBox" Height="92" KeyUp="ListBox_KeyUp">
                  <ListBoxItem Content="First"/>
                  <ListBoxItem Content="Second"/>
                  <ListBoxItem Content="Third"/>
                  <ListBoxItem Content="Fourth"/>
              </ListBox>
          

          首先我将列表框项目数设为listItemCount,并将所选项目索引设为selectIndex,然后检查关键事件是否为Enter,然后检查我们是否达到列表的最后一个。如果它是创建一个新的列表框项并将其添加到列表中。

          【讨论】:

          • 这不起作用,因为如果用户在文本框内,则不会选择任何项目。顺便说一句,我已经切换到 ItemsControl,因为我根本不想拥有选择功能。
          • 即使用户没有关注列表框,是否也需要选择项目?
          • 我不想选择一个项目,我想将焦点移动到所需项目中的文本框。
          【解决方案6】:

          ar.gorgin 的解决方案有一些细微的变化,主要是认识到 OP 需要它来处理 ItemsControl 而不是 ListBox:

          if (e.Key == Key.Enter)
          {
              var txt = sender as TextBox;
              var selecteditem = FindParent<ContentPresenter>(txt);
              int index = myItemsCtl.ItemContainerGenerator.IndexFromContainer(selecteditem);
          
              if (index < myItemsCtl.Items.Count)
              {
                  var afterItem = myItemsCtl.ItemContainerGenerator.ContainerFromIndex(index + 1) as Visual;
                  TextBox tbFind = GetDescendantByType(afterItem, typeof(TextBox), "txtBoxName") as TextBox;
                  if (tbFind != null)
                  {
                      FocusHelper.Focus(tbFind);
                  }
              }
          }
          

          【讨论】:

          • 自从我使用 WPF 已经很长时间了,我什至没有办法检查所有这些解决方案......但我很惊讶你需要写多少可怕的黑客做一件不是开箱即用的事情。尽管如此,还是比在 Erlang 中编写 Web 应用程序要好(哎哟)。
          猜你喜欢
          • 1970-01-01
          • 2014-12-15
          • 2014-06-30
          • 1970-01-01
          • 1970-01-01
          • 2012-05-11
          • 2019-10-12
          • 2016-09-18
          • 1970-01-01
          相关资源
          最近更新 更多