2008-09-18 16 views
11

W ramach wydarzenia chciałbym skupić się na konkretnym TextBox w szablonie ListViewItem. XAML wygląda następująco:Jak mogę uzyskać dostęp do ListViewItems z ListView WPF?

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

Próbowałem następujących w kodzie za:

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

ale wydaje mi się, że źle się FindName() dokumenty, ponieważ zwraca null.

Również ListView.Items nie pomaga, ponieważ to (oczywiście) zawiera moje powiązane obiekty biznesowe i nie ma ListViewItems.

Ani myList.ItemContainerGenerator.ContainerFromItem(item), która również zwraca wartość null.

Odpowiedz

15

Aby zrozumieć, dlaczego ContainerFromItem nie działa dla mnie, tutaj niektóre tło. Moduł obsługi zdarzeń, gdzie potrzebne tej funkcji wygląda następująco:

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

Po Add()ItemContainerGenerator nie od razu utworzyć pojemnik, ponieważ zdarzeniem CollectionChanged mogą być obsługiwane na non-UI-wątku. Zamiast tego uruchamia asynchroniczne wywołanie i czeka, aż wątek interfejsu użytkownika się oddzwoni i wykona rzeczywistą generację kontrolki ListViewItem.

Aby otrzymać powiadomienie, gdy tak się stanie, ItemContainerGenerator ujawnia zdarzenie StatusChanged, które jest uruchamiane po wygenerowaniu wszystkich kontenerów.

Teraz muszę posłuchać tego wydarzenia i zdecydować, czy kontrola ma być aktualnie ustawiona, czy nie.

+1

To zdecydowanie odpowiedź. Aby dodać trochę informacji, zauważyłem, że zdarzenie jest wywoływane dwa razy. Za pierwszym razem ContainerFromItem generuje wartość pustą, natomiast za drugim razem zwraca oczekiwany obiekt listviewitem. Ten uratował mój dzień! – g1ga

+0

To wydarzenie nie jest dostępne w WinRT –

13

Jak zauważyli inni, pola myBox TextBox nie można znaleźć, wywołując FindName w ListView. Możesz jednak pobrać obiekt ListViewItem, który jest aktualnie wybrany, i użyć klasy VisualTreeHelper, aby pobrać TextBox z ListViewItem. Aby to zrobić wygląda mniej więcej tak:

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

Wierzcie lub nie, to pomogło mi w czymś niezwiązanym że starałem się dowiedzieć. Jak ustawić ostrość na następnym polu tekstowym w siatce po naciśnięciu klawisza w dół! Więc +1. – RichardOD

+0

Oto post, jeśli jesteś zainteresowany: http://northdownsolutionslimited.co.uk/post/How-to-focus-on-the-next-row-textbox-in-a-WPF-DataGrid.aspx – RichardOD

+0

Problem z to jest - w zależności od * kiedy * wywołujesz to - 'ViewItems' może nie być jeszcze stworzony. Tak więc konieczność słuchania zdarzenia "StatusChanged", jak opisano w mojej odpowiedzi. –

-1

Używamy podobną technikę z nowym DataGrid WPF za:

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 

Technika powinna być dość zastosowanie do ciebie, tylko zdać listviewitem raz to wygenerowany.

-1

Albo można po prostu zrobić przez

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

zauważyłem, że tytuł pytanie nie odnosi się bezpośrednio do treści pytania, ani nie akceptowaną odpowiedź odpowiedzieć. I udało się „przejść ListViewItems z WPF ListView” za pomocą tego:

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

Nie jestem pewien, jak gorączkowe rekursja dostaje, ale wydawało się działać prawidłowo w moim przypadku. I nie, nie użyłem wcześniej yield return w kontekście rekursywnym.

+0

Problem polega na tym, że - w zależności od * kiedy * wywołujesz to - 'ViewItems' może nie być jeszcze utworzony. Tak więc konieczność słuchania zdarzenia "StatusChanged", jak opisano w mojej odpowiedzi. –

+0

Dziękuję! Pracowałem "jak jest" i robiłem dokładnie to, co chciałem. Dobra robota :) –

0

Możesz przejść w górę ViewTree, aby znaleźć zestaw rekordów "ListViewItem", który odpowiada komórce uruchomionej w wyniku testu trafień.

W podobny sposób można uzyskać nagłówki kolumn z widoku nadrzędnego, aby porównać i dopasować kolumnę komórki. Możesz powiązać nazwę komórki z nazwą nagłówka kolumny jako klucz do delegata/filtra komparatora.

Na przykład: HitResult jest na TextBlock pokazany na zielono. Chcesz uzyskać uchwyt do "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)); 
} 
Powiązane problemy