如何访问WPF ListView的ListViewItems?

时间:2020-03-06 14:21:09  来源:igfitidea点击:

在一个事件中,我想将焦点放在ListViewItem模板内的特定TextBox上。 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。

解决方案

正如其他人指出的那样,无法通过在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;
}

我们对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后就通过它即可。

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

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

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

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

现在,我必须听听此事件并确定控件当前是否要设置焦点。

或者可以简单地通过

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