2008-09-18 8 views
11

Innerhalb eines Ereignisses möchte ich den Fokus auf eine bestimmte TextBox innerhalb der ListViewItem Vorlage legen. Die XAML sieht wie folgt aus:Wie kann ich auf die ListViewItems eines WPF-ListView zugreifen?

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

Ich habe versucht, die folgenden in der Code-behind:

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

aber ich glaube, die FindName() docs falsch verstanden zu haben, denn es null zurückgibt.

Auch die ListView.Items hilft nicht, denn das enthält (natürlich) meine gebundenen Business-Objekte und keine ListViewItems.

Keine myList.ItemContainerGenerator.ContainerFromItem(item), die auch Null zurückgibt.

Antwort

15

Um zu verstehen, warum ContainerFromItem hat nicht für mich funktioniert, hier einige Hintergrund. Der Event-Handler, wo ich diese Funktionalität benötigt sieht wie folgt aus:

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

Nachdem der Add() die ItemContainerGenerator nicht sofort den Container erstellen, weil das CollectionChanged Ereignis auf einer Nicht-UI-Thread behandelt werden könnten. Stattdessen startet es einen asynchronen Aufruf und wartet auf den Rückruf des UI-Threads und führt die eigentliche Generierung des ListViewItem-Steuerelements aus.

Um benachrichtigt zu werden, wenn dies passiert, stellt die ein Ereignis StatusChanged dar, das ausgelöst wird, nachdem alle Container generiert wurden.

Jetzt muss ich auf dieses Ereignis hören und entscheiden, ob das Steuerelement derzeit den Fokus setzen möchte oder nicht.

+1

Dies ist definitiv die Antwort. Um einige Informationen hinzuzufügen, habe ich festgestellt, dass das Ereignis zweimal aufgerufen wird. Das erste Mal, dass ContainerFromItem einen Nullwert erzeugt, während es das zweite Mal das erwartete listviewitem-Objekt zurückgibt. Dieser rettete meinen Tag! – g1ga

+0

Dieses Ereignis wird auf WinRT –

13

Wie andere bemerkt haben, kann die myBox TextBox nicht durch Aufrufen von FindName in der ListView gefunden werden. Sie können jedoch das ListViewItem abrufen, das derzeit ausgewählt ist, und die VisualTreeHelper-Klasse verwenden, um die TextBox aus dem ListViewItem abzurufen. Gehen Sie sieht so etwas wie folgt aus:

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

es glauben oder nicht, hat mich das in keinem Zusammenhang mit etwas geholfen, dass ich, um herauszufinden versucht. So konzentrieren Sie sich auf das nächste Textfeld in einem Raster, wenn die Abwärtstaste gedrückt wird! Also +1. – RichardOD

+0

Hier ist der Beitrag, wenn Sie interessiert sind: http://northdownsolutionslimited.co.uk/post/How-to-focus-on-the-next-row-textbox-in-a-WPF-DataGrid.aspx – RichardOD

+0

Das Problem mit Dies ist, dass - abhängig von * wenn * Sie dies aufrufen - die 'ViewItems' möglicherweise noch nicht erstellt wurden. Daher die Notwendigkeit, auf das 'StatusChanged'-Ereignis zu warten, wie in meiner Antwort beschrieben. –

-1

Wir verwenden eine ähnliche Technik mit WPF Der neue Datagrid:

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 

Die Technik sollte für Sie recht anwendbar sein, nur geben Ihre ListViewItem, sobald es erzeugt wird.

-1

Oder es kann einfach durch

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

Ich bemerkte getan werden, dass die Frage Titel nicht direkt auf den Inhalt der Frage bezieht sich, und auch nicht die akzeptierte Antwort darauf antworten. Ich in der Lage gewesen, „die Listviewitem eines WPF Listview-Zugriff“ durch diese Verwendung:

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

Ich bin nicht sicher, wie hektisch die Rekursion wird, aber es schien in meinem Fall gut zu funktionieren. Und nein, ich habe yield return nicht zuvor in einem rekursiven Kontext verwendet.

+0

nicht angezeigt Das Problem damit ist, dass - abhängig von * wenn * Sie dies aufrufen - die 'ViewItems' möglicherweise noch nicht erstellt wurden. Daher die Notwendigkeit, auf das 'StatusChanged'-Ereignis zu warten, wie in meiner Antwort beschrieben. –

+0

Danke! Arbeitete "wie es ist" und tat genau das, was ich wollte. Gute Arbeit :) –

0

Sie können den ViewTree durchlaufen, um den Datensatz 'ListViewItem' zu finden, der der Zelle entspricht, die aus dem Treffertest ausgelöst wurde.

In ähnlicher Weise können Sie die Spaltenüberschriften aus der übergeordneten Ansicht abrufen, um die Spalte der Zelle zu vergleichen und abzugleichen. Möglicherweise möchten Sie den Zellennamen an den Namen der Spaltenüberschrift als Schlüssel für Ihren Vergleichsdelegierten/Filter binden.

Zum Beispiel: HitResult ist auf TextBlock grün dargestellt. Sie möchten das Handle für das 'ListViewItem' erhalten.

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