Как получить доступ к ListViewItems WPF ListView?

в рамках события я хотел бы сосредоточить внимание на определенном текстовом поле в шаблоне 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() docs, потому что он возвращает null.

и ListView.Items не помогает, потому что это (конечно) содержит мои связанные бизнес-объекты и не ListViewItems.

не myList.ItemContainerGenerator.ContainerFromItem(item), которым также возвращает значение null.

6 ответов


чтобы понять, почему ContainerFromItem не работает для меня здесь. Обработчик событий, в котором мне нужна эта функциональность, выглядит так:

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

после Add() на ItemContainerGenerator не сразу создает контейнер, потому что CollectionChanged событие может быть обработано в не-UI-потоке. Вместо этого он запускает асинхронный вызов и ждет обратного вызова потока пользовательского интерфейса и выполнения фактического создания элемента управления ListViewItem.

получить уведомление, когда это произойдет, the ItemContainerGenerator предоставляет StatusChanged событие, которое запускается после создания всех контейнеров.

теперь я должен прослушать это событие и решить, хочет ли элемент управления в настоящее время установить фокус или нет.


Как отмечали другие, текстовое поле myBox не может быть найдено путем вызова FindName в ListView. Однако можно получить listviewitem, который в данный момент выбран, и использовать VisualTreeHelper класс, чтобы получить текстовое поле из ListViewItem. Сделать это выглядит примерно так:

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

Я заметил, что название вопроса не имеет прямого отношения к содержанию вопроса, и ни принятый ответ не отвечает на него. Я смог "получить доступ к ListViewItems WPF ListView", используя это:

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 в рекурсивном контексте раньше.


вы можете пройти вверх по ViewTree, чтобы найти элемент'элементы listviewitem' набор записей, соответствующий ячейке, вызванной из теста попадания.

аналогично, вы можете получить заголовки столбцов из родительского представления для сравнения и сопоставления столбца ячейки. Вы можете привязать имя ячейки к имени заголовка столбца в качестве ключа для делегата/фильтра компаратора.

например: HitResult находится на TextBlock, показанном зеленым цветом. Вы хотите получить ручку к 'элементы listviewitem'.

enter image description here

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

мы используем аналогичную технику с новым datagrid 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 после его создания.


или это может быть сделано просто

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