2009-03-10 13 views
6

Quiero verificar que los elementos en mi ListBox se muestran correctamente en la interfaz de usuario. Pensé que una forma de hacerlo es revisar todos los elementos secundarios del ListBox en el árbol visual, obtener su texto y compararlo con lo que esperaba que fuera el texto.Forzar WPF para crear los elementos en un ItemsControl

El problema con este enfoque es que internamente ListBox usa un VirtualizingStackPanel para mostrar sus elementos, por lo que solo se crean los elementos que están visibles. Finalmente me encontré con la clase ItemContainerGenerator, que parece que debería obligar a WPF a crear los controles en el árbol visual para el elemento especificado. Desafortunadamente, eso está causando algunos efectos secundarios extraños para mí. Aquí está mi código para generar todos los elementos en el ListBox:.

List<string> generatedItems = new List<string>(); 
IItemContainerGenerator generator = this.ItemsListBox.ItemContainerGenerator; 
GeneratorPosition pos = generator.GeneratorPositionFromIndex(-1); 
using(generator.StartAt(pos, GeneratorDirection.Forward)) 
{ 
    bool isNewlyRealized; 
    for(int i = 0; i < this.ItemsListBox.Items.Count; i++) 
    { 
     isNewlyRealized = false; 
     DependencyObject cntr = generator.GenerateNext(out isNewlyRealized); 
     if(isNewlyRealized) 
     { 
      generator.PrepareItemContainer(cntr); 
     } 

     string itemText = GetControlText(cntr); 
     generatedItems.Add(itemText); 
    } 
} 

(puedo proporcionar el código de GetItemText() si lo desea, pero simplemente atraviesa el árbol visual hasta que un TextBlock se encontró que dan cuenta hay otras maneras de tener texto en un artículo, pero lo arreglaré una vez que la generación de elementos funcione correctamente.)

En mi aplicación, ItemsListBox contiene 20 elementos, con los primeros 12 elementos inicialmente visibles. El texto de los primeros 14 elementos es correcto (probablemente porque sus controles ya se han generado). Sin embargo, para los puntos 15-20, no recibo ningún texto. Además, si me desplazo hacia la parte inferior de ItemsListBox, el texto de los elementos 15-20 también está en blanco. Parece que estoy interfiriendo con el mecanismo normal de WPF para generar controles.

¿Qué estoy haciendo mal? ¿Hay una forma diferente/mejor de forzar los elementos en un ItemsControl para ser agregados al árbol visual?

Actualización: Creo que he encontrado por qué ocurre esto, aunque no sé cómo solucionarlo. Mi suposición de que la llamada al PrepareItemContainer() generaría los controles necesarios para mostrar el artículo, y luego agregaría el contenedor al árbol visual en la ubicación correcta. Resulta que no está haciendo ninguna de estas cosas. El contenedor no se agrega al ItemsControl hasta que me desplazo hacia abajo para verlo, y en ese momento solo se crea el contenedor en sí (es decir, ListBoxItem) - sus hijos no se crean (debe haber algunos controles agregados aquí, uno de los cuales debería ser el TextBlock que mostrará el texto del elemento).

Si recorro el árbol visual del control que pasé al PrepareItemContainer(), los resultados son los mismos. En ambos casos, solo se crea el ListBoxItem y no se crea ninguno de sus elementos secundarios.

No pude encontrar una buena manera de agregar el ListBoxItem al árbol visual. Encontré el VirtualizingStackPanel en el árbol visual, pero llamando a su Children.Add() da como resultado un InvalidOperationException (no se pueden agregar elementos directamente al ItemPanel, ya que genera elementos para su ItemsControl). Solo como prueba, intenté llamar a su AddVisualChild() usando Reflection (ya que está protegido), pero tampoco funcionó.

+0

No entiendo. ¿Por qué quieres hacer esto? ¿Pruebas? –

+0

Sí, solo para fines de prueba. – Andy

+0

Puede establecer la propiedad adjunta ['VirtualizingStackPanel.IsVirtualizing'] (http://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel.isvirtualizing.aspx) en' false' en 'ItemsListBox 'antes de agregar los artículos. Cuando lo ajuste a 'true 'de nuevo, las cosas comenzarán a virtualizarse cuando se desplace. –

Respuesta

1

Creo que me di cuenta de cómo hacer esto. El problema fue que los elementos generados no se agregaron al árbol visual.Después de algunas búsquedas, lo mejor que se me ocurre es llamar a algunos métodos protegidos de VirtualizingStackPanel en el ListBox. Si bien esto no es ideal, dado que solo es para pruebas, creo que tendré que vivir con eso.

Esto es lo que funcionó para mí:

VirtualizingStackPanel itemsPanel = null; 
FrameworkElementFactory factory = control.ItemsPanel.VisualTree; 
if(null != factory) 
{ 
    // This method traverses the visual tree, searching for a control of 
    // the specified type and name. 
    itemsPanel = FindNamedDescendantOfType(control, 
     factory.Type, null) as VirtualizingStackPanel; 
} 

List<string> generatedItems = new List<string>(); 
IItemContainerGenerator generator = this.ItemsListBox.ItemContainerGenerator; 
GeneratorPosition pos = generator.GeneratorPositionFromIndex(-1); 
using(generator.StartAt(pos, GeneratorDirection.Forward)) 
{ 
    bool isNewlyRealized; 
    for(int i = 0; i < this.ItemsListBox.Items.Count; i++) 
    { 
     isNewlyRealized = false; 
     UIElement cntr = generator.GenerateNext(out isNewlyRealized) as UIElement; 
     if(isNewlyRealized) 
     { 
      if(i >= itemsPanel.Children.Count) 
      { 
       itemsPanel.GetType().InvokeMember("AddInternalChild", 
        BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMember, 
        Type.DefaultBinder, itemsPanel, 
        new object[] { cntr }); 
      } 
      else 
      { 
       itemsPanel.GetType().InvokeMember("InsertInternalChild", 
        BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMember, 
        Type.DefaultBinder, itemsPanel, 
        new object[] { i, cntr }); 
      } 

      generator.PrepareItemContainer(cntr); 
     } 

     string itemText = GetControlText(cntr); 
     generatedItems.Add(itemText); 
    } 
} 
3

Simplemente mirando rápida, si el cuadro de lista utiliza VirtualizingStackPanel - tal vez va a ser suficiente para sustituirlo con StackPanel como

<ListBox.ItemsPanel> 
    <ItemsPanelTemplate> 
     <StackPanel/> 
    <ItemsPanelTemplate> 
<ListBox.ItemsPanel> 
+0

Si bien eso lo soluciona, prefiero no cambiar el ListBox en sí mismo si puedo evitarlo, ya que sería mejor usar un VirtualizingStackPanel en la aplicación real. – Andy

+0

Intenté esto con un Combobox y por alguna razón no funcionó. –

3

Usted puede ir sobre esto por el camino equivocado. Lo que hice es conectar el evento cargado de [el contenido de] mi DataTemplate:

<DataTemplate DataType="{x:Type local:ProjectPersona}"> 
    <Grid Loaded="Row_Loaded"> 
    <!-- ... --> 
    </Grid> 
</DataTemplate> 

... y luego procesar la fila que aparece recién en el controlador de eventos:

private void Row_Loaded(object sender, RoutedEventArgs e) 
{ 
    Grid grid = (Grid)sender; 
    Carousel c = (Carousel)grid.FindName("carousel"); 
    ProjectPersona project = (ProjectPersona)grid.DataContext; 
    if (project.SelectedTime != null) 
     c.ScrollItemIntoView(project.SelectedTime); 
} 

Este enfoque hace la inicialización/comprobación de la fila cuando se muestra por primera vez, por lo que no hará todas las filas por adelantado. Si puedes vivir con eso, quizás este sea el método más elegante.

0

Para cualquier otra persona que se pregunte sobre esto, en el caso de Andy, quizás cambiar la VirtualizingStackPanel con un StackPanel normal sería la mejor solución aquí.

El motivo por el que se llama a PrepareItemContainer en ItemContainerGenerator no funciona es que un elemento debe estar en el árbol visual para que PrepareItemContainer funcione. Con VirtualizingStackPanel, el elemento no se configurará como elemento visual del panel hasta que VirtualizingStackPanel determine que está/está por aparecer en la pantalla.

Otra solución (la que uso) es crear su propio VirtualizingPanel, para que pueda controlar cuándo se agregan elementos al árbol visual.

1

La solución de Andy es una muy buena idea, pero está incompleta. Por ejemplo, se crean los primeros 5 contenedores y en el panel. La lista tiene 300> elementos. Solicito el último contenedor, con esta lógica, ADD. Luego solicito el último índice - 1 contenedor, con este logis ¡AGREGUE! Ese es el problema. El orden de los niños dentro del panel no es válido.

una solución para esto:

private FrameworkElement GetContainerForIndex(int index) 
    { 
     if (ItemsControl == null) 
     { 
      return null; 
     } 

     var container = ItemsControl.ItemContainerGenerator.ContainerFromIndex(index -1); 
     if (container != null && container != DependencyProperty.UnsetValue) 
     { 
      return container as FrameworkElement; 
     } 
     else 
     { 

      var virtualizingPanel = FindVisualChild<VirtualizingPanel>(ItemsControl); 
      if (virtualizingPanel == null) 
      { 
       // do something to load the (perhaps currently unloaded panel) once 
      } 
      virtualizingPanel = FindVisualChild<VirtualizingPanel>(ItemsControl); 

      IItemContainerGenerator generator = ItemsControl.ItemContainerGenerator; 
      using (generator.StartAt(generator.GeneratorPositionFromIndex(index), GeneratorDirection.Forward)) 
      { 
       bool isNewlyRealized = false; 
       container = generator.GenerateNext(out isNewlyRealized); 
       if (isNewlyRealized) 
       { 
        generator.PrepareItemContainer(container); 
        bool insert = false; 
        int pos = 0; 
        for (pos = virtualizingPanel.Children.Count - 1; pos >= 0; pos--) 
        { 
         var idx = ItemsControl.ItemContainerGenerator.IndexFromContainer(virtualizingPanel.Children[pos]); 
         if (!insert && idx < index) 
         { 
          ////Add 
          virtualizingPanel.GetType().InvokeMember("AddInternalChild", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, virtualizingPanel, new object[] { container }); 
          break; 
         } 
         else 
         { 
          insert = true; 
          if (insert && idx < index) 
          { 
           break; 
          } 
         } 
        } 

        if (insert) 
        { 
         virtualizingPanel.GetType().InvokeMember("InsertInternalChild", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, virtualizingPanel, new object[] { pos + 1, container }); 
        } 
       } 

       return container as FrameworkElement; 
      } 
     } 
    } 
-1

En mi caso, he encontrado que llamar UpdateLayout() en el ItemsControl (ListBox, ListView, etc.) puso en marcha su ItemContainerGenerator, de manera que el estado del generador cambia de "NotStarted "a" GeneratingContainers "y los contenedores null ya no fueron devueltos por ItemContainerGenerator.ContainerFromItem y/o ItemContainerGenerator.ContainerFromIndex.

Por ejemplo:

public static bool FocusSelectedItem(this ListBox listbox) 
    { 
     int ix; 
     if ((ix = listbox.SelectedIndex) < 0) 
      return false; 

     var icg = listbox.ItemContainerGenerator; 
     if (icg.Status == GeneratorStatus.NotStarted) 
      listbox.UpdateLayout(); 

     var el = (UIElement)icg.ContainerFromIndex(ix); 
     if (el == null) 
      return false; 

     listbox.ScrollIntoView(el); 

     return el == Keyboard.Focus(el); 
    } 
Cuestiones relacionadas