2008-09-18 8 views
11

Dentro de un evento, me gustaría enfocar un TextBox específico dentro de la plantilla de ListViewItem. El XAML es el siguiente:¿Cómo puedo acceder a ListViewItems de un WPF ListView?

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

He intentado lo siguiente en el código detrás:

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

pero me parece haber entendido mal los documentos FindName(), porque devuelve null.

También el ListView.Items no ayuda, ya que (por supuesto) contiene mis objetos de negocio de la envolvente y no hay ListViewItems.

Tampoco myList.ItemContainerGenerator.ContainerFromItem(item), que también devuelve un valor nulo.

Respuesta

15

Para entender por qué ContainerFromItem no funcionó para mí, aquí algunos antecedentes. El controlador de eventos en el que necesitaba esta funcionalidad se ve así:

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

Después de la Add() la ItemContainerGenerator no crea rápidamente el recipiente, ya que el evento CollectionChanged podría ser manejado de manera no-UI-hilo. En su lugar, inicia una llamada asíncrona y espera a que el subproceso de la interfaz de usuario devuelva la llamada y ejecute la generación real del control ListViewItem.

Para recibir una notificación cuando esto sucede, el ItemContainerGenerator expone un evento StatusChanged que se activa después de que se generan todos los contenedores.

Ahora tengo que escuchar este evento y decidir si el control actualmente quiere establecer el foco o no.

+1

Esta es definitivamente la respuesta. Para agregar algo de información, noté que el evento se invoca dos veces. La primera vez que ContainerFromItem produce un valor nulo, mientras que la segunda vez devuelve el objeto listviewitem esperado. ¡Este me salvó el día! – g1ga

+0

Este evento no está expuesto en WinRT –

13

Como han notado otros, no se puede encontrar el cuadro de texto myBox llamando a FindName en el ListView. Sin embargo, puede obtener ListViewItem que está seleccionado actualmente, y usar la clase VisualTreeHelper para obtener el TextBox del ListViewItem. Para ello se ve algo como esto:

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

creas o no, esto me ha ayudado con algo no relacionado que he estado tratando de averiguar. ¡Cómo enfocar el siguiente cuadro de texto en una cuadrícula cuando se presiona la tecla hacia abajo! Entonces +1. – RichardOD

+0

aquí está la publicación si le interesa: http://northdownsolutionslimited.co.uk/post/How-to-focus-on-the-next-row-textbox-in-a-WPF-DataGrid.aspx – RichardOD

+0

El problema con esto es que, dependiendo de * cuándo * está llamando a esto, es posible que el 'ViewItems' aún no se haya creado. Por lo tanto, la necesidad de escuchar el evento 'StatusChanged' como se describe en mi respuesta. –

-1

se utiliza una técnica similar con la nueva cuadrícula de datos de 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 

la técnica debe ser bastante aplicables para usted, sólo tiene que pasar su ListViewItem una vez que se genera.

-1

O puede hacerse simplemente

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

me di cuenta de que el título de la pregunta no se refiere directamente al contenido de la pregunta, y tampoco la respuesta aceptada responder a ella. He sido capaz de "acceder a los ListViewItems de un WPF ListView" mediante el uso de esto:

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

No estoy seguro de cómo agitado la recursividad se pone, pero parecía funcionar bien en mi caso. Y no, no he usado yield return en un contexto recursivo antes.

+0

El problema con esto es que, dependiendo de * cuando * está llamando a esto, es posible que el 'ViewItems' aún no se haya creado. Por lo tanto, la necesidad de escuchar el evento 'StatusChanged' como se describe en mi respuesta. –

+0

¡Gracias! Trabajó "como está" e hizo exactamente lo que yo quería. Buen trabajo :) –

0

Puede recorrer el ViewTree para buscar el elemento 'ListViewItem' conjunto de registros que corresponde a la celda activada desde la prueba de aciertos.

De forma similar, puede obtener los encabezados de columna desde la vista primaria para comparar y hacer coincidir la columna de la celda. Es posible que desee vincular el nombre de la celda al nombre del encabezado de la columna como la clave para su delegado/filtro de comparación.

Por ejemplo: HitResult está en TextBlock mostrado en verde. Desea obtener el identificador del '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)); 
} 
Cuestiones relacionadas