2011-07-15 32 views
29

Tengo un comportamiento extraño que no puedo resolver. Cuando repito los elementos en mi propiedad ListBox.ItemsSource, parece que no puedo obtener el contenedor? Espero ver un ListBoxItem devuelto, pero solo obtengo un nulo.ItemContainerGenerator.ContainerFromItem() devuelve nulo?

¿Alguna idea?

Aquí está la parte del código que estoy usando:

this.lstResults.ItemsSource.ForEach(t => 
    { 
     ListBoxItem lbi = this.lstResults.ItemContainerGenerator.ContainerFromItem(t) as ListBoxItem; 

     if (lbi != null) 
     { 
      this.AddToolTip(lbi); 
     } 
    }); 

El ItemsSource está establecido actualmente en un diccionario y contiene una serie de KVPS.

+0

¿No puede simplemente iterar a través de Items, que sería una colección de solo lectura (pero su contenido no sería de solo lectura)? –

+0

Lo intenté también. El uso de .ContainerFromIndex() también devuelve null. –

+0

[Comprobar el siguiente enlace para obtener la respuesta] [1] [1]: http://stackoverflow.com/questions/10591391/why-itemcontainergenerator-containerfromindex-returns-null-and-how- to-avoid-th/27792628 # 27792628 –

Respuesta

14

Finalmente resuelto el problema ... Al agregar VirtualizingStackPanel.IsVirtualizing="False" en mi XAML, todo funciona ahora como se esperaba.

En el lado negativo, no paso a todo el benefitst el rendimiento de la virtualización, por lo que he cambiado de enrutamiento de carga a asíncrono y añadido un "spinner" en mi cuadro de lista mientras se carga ...

+0

PD - Gracias por la pista HB Su comentario sobre la virtualización es lo que me llevó por ese camino. –

+1

Como dijo, no se recomienda desactivar la virtualización debido a la degradación del rendimiento. Una vez tuve unos pocos miles de filas largas que después de apagar la virtualización consumen> 1Gb de memoria. De miedo. –

+0

Esto resolvió el problema de los nulos para mí, pero al igual que el comentarista anterior, el DataGrid de 2000 filas que se cargaba instantáneamente se detenía por completo. –

6

paso a través del código con el depurador y ver si hay realmente nada retured o si el as Fundido está mal y por lo tanto se vuelve a null (usted podría usar un yeso normal para obtener una excepción adecuada).

Un problema que ocurre con frecuencia es que cuando un ItemsControl se virtualiza para la mayoría de los elementos, ningún contenedor existirá en ningún momento.

Además, no recomendaría tratar directamente con los contenedores de elementos, sino con las propiedades vinculantes y suscribirse a eventos (a través del ItemsControl.ItemContainerStyle).

39

he encontrado algo que funcionó mejor para mi caso en esta pregunta StackOverflow:

Get row in datagrid

al poner en UpdateLayout y una ScrollIntoView llama antes de llamar ContainerFromItem o ContainerFromIndex, que provoca que esta parte de la cuadrícula de datos que se dio cuenta de lo que hace posible que se devolver un valor para ContainerFromItem/Con tainerFromIndex:

dataGrid.UpdateLayout(); 
dataGrid.ScrollIntoView(dataGrid.Items[index]); 
var row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(index); 

Si no desea que la ubicación actual en la cuadrícula de datos para cambiar, esto probablemente no es una buena solución para usted, pero si eso está bien, funciona sin tener que apagar Virtualización.

+1

Gracias, esto resolvió mi problema. Estaba intentando enfocar un elemento que aún no estaba disponible. UpdateLayout() funcionó muy bien. – Tejo

+1

También estoy haciendo misma pero después de 109 artículos que retornan NULL :( – Yawar

1

VirtualizingStackPanel.IsVirtualizing = "False" Hace que el control sea borroso. Ver la implementación a continuación. Lo cual me ayuda a evitar el mismo problema. Configure su aplicación VirtualizingStackPanel.IsVirtualizing = "Verdadero" siempre.

Véase el link para obtener información detallada

/// <summary> 
/// Recursively search for an item in this subtree. 
/// </summary> 
/// <param name="container"> 
/// The parent ItemsControl. This can be a TreeView or a TreeViewItem. 
/// </param> 
/// <param name="item"> 
/// The item to search for. 
/// </param> 
/// <returns> 
/// The TreeViewItem that contains the specified item. 
/// </returns> 
private TreeViewItem GetTreeViewItem(ItemsControl container, object item) 
{ 
    if (container != null) 
    { 
     if (container.DataContext == item) 
     { 
      return container as TreeViewItem; 
     } 

     // Expand the current container 
     if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded) 
     { 
      container.SetValue(TreeViewItem.IsExpandedProperty, true); 
     } 

     // Try to generate the ItemsPresenter and the ItemsPanel. 
     // by calling ApplyTemplate. Note that in the 
     // virtualizing case even if the item is marked 
     // expanded we still need to do this step in order to 
     // regenerate the visuals because they may have been virtualized away. 

     container.ApplyTemplate(); 
     ItemsPresenter itemsPresenter = 
      (ItemsPresenter)container.Template.FindName("ItemsHost", container); 
     if (itemsPresenter != null) 
     { 
      itemsPresenter.ApplyTemplate(); 
     } 
     else 
     { 
      // The Tree template has not named the ItemsPresenter, 
      // so walk the descendents and find the child. 
      itemsPresenter = FindVisualChild<ItemsPresenter>(container); 
      if (itemsPresenter == null) 
      { 
       container.UpdateLayout(); 

       itemsPresenter = FindVisualChild<ItemsPresenter>(container); 
      } 
     } 

     Panel itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0); 


     // Ensure that the generator for this panel has been created. 
     UIElementCollection children = itemsHostPanel.Children; 

     MyVirtualizingStackPanel virtualizingPanel = 
      itemsHostPanel as MyVirtualizingStackPanel; 

     for (int i = 0, count = container.Items.Count; i < count; i++) 
     { 
      TreeViewItem subContainer; 
      if (virtualizingPanel != null) 
      { 
       // Bring the item into view so 
       // that the container will be generated. 
       virtualizingPanel.BringIntoView(i); 

       subContainer = 
        (TreeViewItem)container.ItemContainerGenerator. 
        ContainerFromIndex(i); 
      } 
      else 
      { 
       subContainer = 
        (TreeViewItem)container.ItemContainerGenerator. 
        ContainerFromIndex(i); 

       // Bring the item into view to maintain the 
       // same behavior as with a virtualizing panel. 
       subContainer.BringIntoView(); 
      } 

      if (subContainer != null) 
      { 
       // Search the next level for the object. 
       TreeViewItem resultContainer = GetTreeViewItem(subContainer, item); 
       if (resultContainer != null) 
       { 
        return resultContainer; 
       } 
       else 
       { 
        // The object is not under this TreeViewItem 
        // so collapse it. 
        subContainer.IsExpanded = false; 
       } 
      } 
     } 
    } 

    return null; 
} 
2

Aunque deshabilitar la virtualización de XAML funciona, creo que es mejor desactivar desde el archivo .cs que utiliza ContainerFromItem

VirtualizingStackPanel.SetIsVirtualizing(listBox, false); 

De esta forma, reducir el acoplamiento entre el XAML y el código; para evitar el riesgo de que alguien rompa el código al tocar el XAML.

3

Uso esta suscripción:

TheListBox.ItemContainerGenerator.StatusChanged += (sender, e) => 
{ 
    TheListBox.Dispatcher.Invoke(() => 
    { 
    var TheOne = TheListBox.ItemContainerGenerator.ContainerFromIndex(0); 
    if (TheOne != null) 
     // Use The One 
    }); 
}; 
1

para cualquier persona que todavía tiene problemas con esto, yo era capaz de solucionar este problema al ignorar el evento cambiado primera selección y el uso de un hilo básicamente repetir la llamada. Esto es lo que terminé haciendo:

private int _hackyfix = 0; 
    private void OnMediaSelectionChanged(object sender, SelectionChangedEventArgs e) 
    { 
     //HACKYFIX:Hacky workaround for an api issue 
     //Microsoft's api for getting item controls for the flipview item fail on the very first media selection change for some reason. Basically we ignore the 
     //first media selection changed event but spawn a thread to redo the ignored selection changed, hopefully allowing time for whatever is going on 
     //with the api to get things sorted out so we can call the "ContainerFromItem" function and actually get the control we need I ignore the event twice just in case but I think you can get away with ignoring only the first one. 
     if (_hackyfix == 0 || _hackyfix == 1) 
     { 
      _hackyfix++; 
      Dispatcher.RunAsync(CoreDispatcherPriority.Normal,() => 
     { 
      OnMediaSelectionChanged(sender, e); 
     }); 
     } 
     //END OF HACKY FIX//Actual code you need to run goes here} 

EDITAR 10/29/2014: En realidad ni siquiera es necesario el código despachador hilo. Puedes configurar lo que necesites para anular el evento de la primera selección modificada y luego regresar al evento para que los eventos futuros funcionen como se espera.

 private int _hackyfix = 0; 
    private void OnMediaSelectionChanged(object sender, SelectionChangedEventArgs e) 
    { 
     //HACKYFIX: Daniel note: Very hacky workaround for an api issue 
     //Microsoft's api for getting item controls for the flipview item fail on the very first media selection change for some reason. Basically we ignore the 
     //first media selection changed event but spawn a thread to redo the ignored selection changed, hopefully allowing time for whatever is going on 
     //with the api to get things sorted out so we can call the "ContainerFromItem" function and actually get the control we need 
     if (_hackyfix == 0) 
     { 
      _hackyfix++; 
      /* 
      Dispatcher.RunAsync(CoreDispatcherPriority.Normal,() => 
     { 
      OnMediaSelectionChanged(sender, e); 
     });*/ 
      return; 
     } 
     //END OF HACKY FIX 
     //Your selection_changed code here 
     } 
3

estoy un poco tarde a la fiesta, pero aquí hay otra solución que es a prueba de fallos en mi caso,

Después de probar muchas soluciones que sugiere añadir IsExpanded y IsSelected al objeto subyacente y unirse a ellos en TreeViewItem estilo, mientras que esto su mayoría obras en algunos casos sigue sin ...

Nota: mi objetivo era escribir una vista de mini/encargo similar al Explorador, donde al hacer clic en una carpeta en el panel derecho se selecciona en el TreeView, al igual que en el Explorador.

private void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e) 
{ 
    var item = sender as ListViewItem; 
    var node = item?.Content as DirectoryNode; 
    if (node == null) return; 

    var nodes = (IEnumerable<DirectoryNode>)TreeView.ItemsSource; 
    if (nodes == null) return; 

    var queue = new Stack<Node>(); 
    queue.Push(node); 
    var parent = node.Parent; 
    while (parent != null) 
    { 
     queue.Push(parent); 
     parent = parent.Parent; 
    } 

    var generator = TreeView.ItemContainerGenerator; 
    while (queue.Count > 0) 
    { 
     var dequeue = queue.Pop(); 
     TreeView.UpdateLayout(); 
     var treeViewItem = (TreeViewItem)generator.ContainerFromItem(dequeue); 
     if (queue.Count > 0) treeViewItem.IsExpanded = true; 
     else treeViewItem.IsSelected = true; 
     generator = treeViewItem.ItemContainerGenerator; 
    } 
} 

múltiples trucos utilizados aquí:

  • una pila para la expansión de cada artículo de arriba a abajo
  • garantizar el uso actual nivel generador para encontrar el artículo (muy importante)
  • el hecho de que el generador para artículos de nivel superior nunca devuelve null

Hasta ahora funciona muy bien,

  • sin necesidad de contaminar sus tipos con nuevas propiedades
  • hay necesidad de desactivar la virtualización en absoluto.
+0

funciona muy bien. Podría ser mejor para migrar a un pregunta (tal vez una nueva pregunta) específicamente sobre 'TreeView's, ya que éste está a punto ListBox, para dar .! esta respuesta impresionante un poco más la visibilidad – Lauraducky

0

Lo más probable es que esto es un problema relacionado con la virtualización de modo ListBoxItem contenedores consiguen generan solamente para los artículos visibles en ese momento (ver https://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel(v=vs.110).aspx#Anchor_9)

Si está utilizando ListBox me gustaría sugerir el cambio a ListView lugar - que es heredera ListBox y es compatible con el método ScrollIntoView() que puede utilizar para controlar la virtualización;

targetListView.ScrollIntoView(itemVM); 
DoEvents(); 
ListViewItem itemContainer = targetListView.ItemContainerGenerator.ContainerFromItem(itemVM) as ListViewItem; 

(el ejemplo anterior también utiliza el método estático DoEvents() explica con más detalle aquí; WPF how to wait for binding update to occur before processing more code?)

Hay algunas otras diferencias menores entre los controles ListBox y ListView (What is The difference between ListBox and ListView) - que no debe esencialmente afecta tu caso de uso

5
object viewItem = list.ItemContainerGenerator.ContainerFromItem(item); 
if (viewItem == null) 
{ 
    list.UpdateLayout(); 
    viewItem = list.ItemContainerGenerator.ContainerFromItem(item); 
    Debug.Assert(viewItem != null, "list.ItemContainerGenerator.ContainerFromItem(item) is null, even after UpdateLayout"); 
} 
+1

esto funcionó perfectamente a mi caso Usted salvó mi día;). En mi caso, este problema ocurrió justo después de que se usó CustomSort, y la solución de Misa resolvió el problema. – Aki24x

+0

¡La mejor respuesta, gracias por compartir! – Shimmy

Cuestiones relacionadas