【问题标题】:How can I access the ListViewItems of a WPF ListView?如何访问 WPF ListView 的 ListViewItems?
【发布时间】:2010-09-10 16:14:41
【问题描述】:

在一个事件中,我想将焦点放在 ListViewItem 模板中的特定文本框上。 XAML 如下所示:

<ListView x:Name="myList" ItemsSource="{Binding SomeList}">
    <ListView.View>
        <GridView>
            <GridViewColumn>
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <!-- Focus this! -->
                        <TextBox x:Name="myBox"/>

我在后面的代码中尝试了以下内容:

(myList.FindName("myBox") as TextBox).Focus();

但我似乎误解了 FindName() 文档,因为它返回 null

ListView.Items 也无济于事,因为它(当然)包含我绑定的业务对象并且没有 ListViewItems。

myList.ItemContainerGenerator.ContainerFromItem(item) 也没有,它也返回 null。

【问题讨论】:

    标签: wpf listview internals


    【解决方案1】:

    正如其他人所指出的,无法通过在 ListView 上调用 FindName 找到 myBox TextBox。但是,您可以获取当前选中的 ListViewItem,并使用 VisualTreeHelper 类从 ListViewItem 中获取 TextBox。这样做看起来像这样:

    private void myList_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (myList.SelectedItem != null)
        {
            object o = myList.SelectedItem;
            ListViewItem lvi = (ListViewItem)myList.ItemContainerGenerator.ContainerFromItem(o);
            TextBox tb = FindByName("myBox", lvi) as TextBox;
    
            if (tb != null)
                tb.Dispatcher.BeginInvoke(new Func<bool>(tb.Focus));
        }
    }
    
    private FrameworkElement FindByName(string name, FrameworkElement root)
    {
        Stack<FrameworkElement> tree = new Stack<FrameworkElement>();
        tree.Push(root);
    
        while (tree.Count > 0)
        {
            FrameworkElement current = tree.Pop();
            if (current.Name == name)
                return current;
    
            int count = VisualTreeHelper.GetChildrenCount(current);
            for (int i = 0; i < count; ++i)
            {
                DependencyObject child = VisualTreeHelper.GetChild(current, i);
                if (child is FrameworkElement)
                    tree.Push((FrameworkElement)child);
            }
        }
    
        return null;
    }
    

    【讨论】:

    • 信不信由你,这有助于我解决一些我一直试图弄清楚的无关问题。按下向下键时如何关注网格中的下一个文本框!所以+1。
    • 如果你有兴趣,这里是帖子:northdownsolutionslimited.co.uk/post/…
    • 问题在于 - 取决于您调用它的何时 - ViewItems 可能尚未创建。因此,如我的回答中所述,有必要监听 StatusChanged 事件。
    【解决方案2】:

    我们在 WPF 的新数据网格中使用了类似的技术:

    Private Sub SelectAllText(ByVal cell As DataGridCell)
        If cell IsNot Nothing Then
            Dim txtBox As TextBox= GetVisualChild(Of TextBox)(cell)
            If txtBox IsNot Nothing Then
                txtBox.Focus()
                txtBox.SelectAll()
            End If
        End If
    End Sub
    
    Public Shared Function GetVisualChild(Of T As {Visual, New})(ByVal parent As Visual) As T
        Dim child As T = Nothing
        Dim numVisuals As Integer = VisualTreeHelper.GetChildrenCount(parent)
        For i As Integer = 0 To numVisuals - 1
            Dim v As Visual = TryCast(VisualTreeHelper.GetChild(parent, i), Visual)
            If v IsNot Nothing Then
                child = TryCast(v, T)
                If child Is Nothing Then
                    child = GetVisualChild(Of T)(v)
                Else
                    Exit For
                End If
            End If
        Next
        Return child
    End Function
    

    该技术应该相当适用于您,只需在生成后传递您的 listviewitem。

    【讨论】:

      【解决方案3】:

      要了解为什么ContainerFromItem 对我不起作用,这里有一些背景知识。我需要此功能的事件处理程序如下所示:

      var item = new SomeListItem();
      SomeList.Add(item);
      ListViewItem = SomeList.ItemContainerGenerator.ContainerFromItem(item); // returns null
      

      Add() 之后,ItemContainerGenerator 不会立即创建容器,因为CollectionChanged 事件可以在非 UI 线程上处理。相反,它会启动异步调用并等待 UI 线程回调并执行实际的 ListViewItem 控件生成。

      为了在发生这种情况时得到通知,ItemContainerGenerator 公开了一个 StatusChanged 事件,该事件在所有容器生成后触发。

      现在我必须监听这个事件并决定控件当前是否要设置焦点。

      【讨论】:

      • 这绝对是答案。为了添加一些信息,我注意到该事件被调用了两次。第一次 ContainerFromItem 产生一个空值,而第二次它返回预期的 listviewitem 对象。这个拯救了我的一天!
      • 此事件未在 WinRT 上公开
      【解决方案4】:

      或者可以简单的通过

      private void yourtextboxinWPFGrid_LostFocus(object sender, RoutedEventArgs e)
          {
             //textbox can be catched like this. 
             var textBox = ((TextBox)sender);
             EmailValidation(textBox.Text);
          }
      

      【讨论】:

        【解决方案5】:

        我注意到问题标题与问题的内容没有直接关系,接受的答案也没有回答它。我已经能够通过使用这个来“访问 WPF ListView 的 ListViewItems”:

        public static IEnumerable<ListViewItem> GetListViewItemsFromList(ListView lv)
        {
            return FindChildrenOfType<ListViewItem>(lv);
        }
        
        public static IEnumerable<T> FindChildrenOfType<T>(this DependencyObject ob)
            where T : class
        {
            foreach (var child in GetChildren(ob))
            {
                T castedChild = child as T;
                if (castedChild != null)
                {
                    yield return castedChild;
                }
                else
                {
                    foreach (var internalChild in FindChildrenOfType<T>(child))
                    {
                        yield return internalChild;
                    }
                }
            }
        }
        
        public static IEnumerable<DependencyObject> GetChildren(this DependencyObject ob)
        {
            int childCount = VisualTreeHelper.GetChildrenCount(ob);
        
            for (int i = 0; i < childCount; i++)
            {
                yield return VisualTreeHelper.GetChild(ob, i);
            }
        }
        

        我不确定递归有多忙,但在我的情况下它似乎工作正常。不,我之前没有在递归上下文中使用过yield return

        【讨论】:

        • 问题在于 - 取决于您调用它的何时 - ViewItems 可能尚未创建。因此,如我的回答中所述,有必要监听 StatusChanged 事件。
        • 谢谢! “按原样”工作,并完全按照我的意愿行事。干得好:)
        【解决方案6】:

        您可以向上遍历 ViewTree 以找到与从命中测试触发的单元格相对应的项目 'ListViewItem' 记录集。

        同样,您可以从父视图中获取列标题来比较和匹配单元格的列。您可能希望将单元格名称绑定到列标题名称作为比较器委托/过滤器的键。

        例如:HitResult 在 TextBlock 上以绿色显示。您希望获得“ListViewItem”的句柄。

        /// <summary>
        ///   ListView1_MouseMove
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ListView1_MouseMove(object sender, System.Windows.Input.MouseEventArgs e) {
          if (ListView1.Items.Count <= 0)
            return;
        
          // Retrieve the coordinate of the mouse position.
          var pt = e.GetPosition((UIElement) sender);
        
          // Callback to return the result of the hit test.
          HitTestResultCallback myHitTestResult = result => {
            var obj = result.VisualHit;
        
            // Add additional DependancyObject types to ignore triggered by the cell's parent object container contexts here.
            //-----------
            if (obj is Border)
              return HitTestResultBehavior.Stop;
            //-----------
        
            var parent = VisualTreeHelper.GetParent(obj) as GridViewRowPresenter;
            if (parent == null)
              return HitTestResultBehavior.Stop;
        
            var headers = parent.Columns.ToDictionary(column => column.Header.ToString());
        
            // Traverse up the VisualTree and find the record set.
            DependencyObject d = parent;
            do {
              d = VisualTreeHelper.GetParent(d);
            } while (d != null && !(d is ListViewItem));
        
            // Reached the end of element set as root's scope.
            if (d == null)
              return HitTestResultBehavior.Stop;
        
            var item = d as ListViewItem;
            var index = ListView1.ItemContainerGenerator.IndexFromContainer(item);
            Debug.WriteLine(index);
        
            lblCursorPosition.Text = $"Over {item.Name} at ({index})";
        
            // Set the behavior to return visuals at all z-order levels.
            return HitTestResultBehavior.Continue;
          };
        
          // Set up a callback to receive the hit test result enumeration.
          VisualTreeHelper.HitTest((Visual)sender, null, myHitTestResult, new PointHitTestParameters(pt));
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-02-16
          • 2017-11-28
          • 2014-11-04
          • 1970-01-01
          • 1970-01-01
          • 2010-11-27
          • 1970-01-01
          • 2014-09-29
          相关资源
          最近更新 更多