2008-09-18 6 views
11

Dans un événement, j'aimerais mettre l'accent sur un TextBox spécifique dans le modèle de ListViewItem. Le XAML ressemble à ceci:Comment puis-je accéder aux ListViewItems d'un ListView WPF?

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

J'ai essayé ce qui suit dans le code derrière:

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

, mais il me semble avoir mal compris les FindName() docs, car elle retourne null.

Aussi le ListView.Items ne aide pas, parce que (bien sûr) contient mes objets métier liés et aucun ListViewItems.

Il en va de même pour myList.ItemContainerGenerator.ContainerFromItem(item), qui renvoie également null.

Répondre

15

Pour comprendre pourquoi ContainerFromItem ne fonctionnait pas pour moi, voici un peu d'arrière-plan. Le gestionnaire d'événements où je avais besoin de cette fonctionnalité ressemble à ceci:

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

Après la Add() la ItemContainerGenerator ne crée pas immédiatement le récipient, car l'événement CollectionChanged pourrait être traité sur un thread non-UI. Au lieu de cela, il lance un appel asynchrone et attend que le thread UI soit rappelé et exécute la génération de contrôle ListViewItem réelle.

Pour être averti lorsque cela se produit, le ItemContainerGenerator expose un événement StatusChanged qui est déclenché après la génération de tous les conteneurs.

Maintenant, je dois écouter cet événement et décider si le contrôle veut ou non définir le focus.

+1

Ceci est certainement la réponse. Pour ajouter quelques informations, j'ai remarqué que l'événement est invoqué deux fois. La première fois ContainerFromItem produit une valeur null, tandis que la deuxième fois il renvoie l'objet listviewitem attendu. Celui-ci a sauvé ma journée! – g1ga

+0

Cet événement n'est pas exposé sur WinRT –

13

Comme d'autres l'ont noté, la zone de texte myBox est introuvable en appelant FindName dans ListView. Toutefois, vous pouvez obtenir le ListViewItem qui est actuellement sélectionné et utiliser la classe VisualTreeHelper pour obtenir le TextBox à partir de ListViewItem. Pour cela ressemble à quelque chose comme ceci:

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

Croyez-le ou non, cela m'a aidé à quelque chose sans rapport que j'ai essayé de comprendre. Comment se concentrer sur la prochaine zone de texte dans une grille lorsque la touche Bas est enfoncée! Donc +1. – RichardOD

+0

voici le poste si vous êtes intéressé: http://northdownsolutionslimited.co.uk/post/How-to-focus-on-the-next-row-textbox-in-a-WPF-DataGrid.aspx – RichardOD

+0

Le problème avec c'est - en fonction de * quand * vous appelez ceci - le 'ViewItems' pourrait ne pas être encore créé. D'où la nécessité d'écouter l'événement 'StatusChanged' comme décrit dans ma réponse. –

-1

Nous utilisons une technique similaire avec une nouvelle grille de données 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 technique devrait être assez applicable pour vous, il suffit de passer votre ListViewItem une fois qu'il est généré.

-1

Ou il peut être fait simplement par

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

J'ai remarqué que le titre de la question ne se rapporte pas directement au contenu de la question, ni ne la réponse acceptée répondre. J'ai pu « accéder aux ListViewItems d'un WPF ListView » en utilisant ceci:

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

Je ne sais pas comment trépidante la récursion se, mais il semblait bien fonctionner dans mon cas. Et non, je n'ai pas utilisé yield return dans un contexte récursif avant.

+0

Le problème avec ceci est que - selon * quand * vous appelez ceci - le 'ViewItems' pourrait ne pas être encore créé. D'où la nécessité d'écouter l'événement 'StatusChanged' comme décrit dans ma réponse. –

+0

Merci! Travaillé "tel quel", et a fait exactement ce que je voulais. Bon travail :) –

0

Vous pouvez parcourir le ViewTree pour trouver l'élément 'ListViewItem' jeu d'enregistrements correspondant à la cellule déclenchée par le test de réussite.

De même, vous pouvez obtenir les en-têtes de colonnes de la vue parent pour comparer et faire correspondre la colonne de la cellule. Vous pouvez associer le nom de la cellule au nom de l'en-tête de colonne en tant que clé pour votre délégué/filtre de comparaison. Par exemple: HitResult est sur TextBlock affiché en vert. Vous souhaitez obtenir le handle du '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)); 
} 
Questions connexes