2011-09-09 9 views
10

Versión cortaListBox ScrollIntoView cuando se utiliza con CollectionViewSource GroupDescriptions (es decir IsGrouping == true)

quisiera desplazar el elemento ListBox a la vista cuando se cambia la selección.

Versión larga

Tengo un ListBox con el ItemsSource unido a un CollectionViewSource con un GroupDescription, de acuerdo con el siguiente ejemplo.

<Window.Resources> 
    <CollectionViewSource x:Key="AnimalsView" Source="{Binding Source={StaticResource Animals}, Path=AnimalList}"> 
     <CollectionViewSource.GroupDescriptions> 
      <PropertyGroupDescription PropertyName="Category"/> 
     </CollectionViewSource.GroupDescriptions> 
    </CollectionViewSource> 
</Window.Resources> 

<ListBox x:Name="AnimalsListBox"ItemsSource="{Binding Source={StaticResource AnimalsView}}" ItemTemplate="{StaticResource AnimalTemplate}" SelectionChanged="ListBox_SelectionChanged"> 
    <ListBox.GroupStyle> 
     <GroupStyle HeaderTemplate="{StaticResource CategoryTemplate}" /> 
    </ListBox.GroupStyle> 
</ListBox> 

hay un evento SelectionChanged en el archivo de un código subyacente.

public List<Animal> Animals { get; set; } 

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 
{ 
    ListBox control = (ListBox)sender; 
    control.ScrollIntoView(control.SelectedItem); 
} 

Ahora. Si configuro el AnimalsListBox.SelectedItem en un artículo que actualmente no está visible, me gustaría tenerlo desplazado a la vista. Aquí es donde se vuelve complicado, ya que el ListBox se está agrupando (la propiedad IsGrouped es true) falla la llamada al ScrollIntoView.

System.Windows.Controls.ListBox a través de Reflector. Tenga en cuenta base.IsGrouping en el OnBringItemIntoView.

public void ScrollIntoView(object item) 
{ 
    if (base.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) 
    { 
     this.OnBringItemIntoView(item); 
    } 
    else 
    { 
     base.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new DispatcherOperationCallback(this.OnBringItemIntoView), item); 
    } 
} 

private object OnBringItemIntoView(object arg) 
{ 
    FrameworkElement element = base.ItemContainerGenerator.ContainerFromItem(arg) as FrameworkElement; 
    if (element != null) 
    { 
     element.BringIntoView(); 
    } 
    else if (!base.IsGrouping && base.Items.Contains(arg)) 
    { 
     VirtualizingPanel itemsHost = base.ItemsHost as VirtualizingPanel; 
     if (itemsHost != null) 
     { 
      itemsHost.BringIndexIntoView(base.Items.IndexOf(arg)); 
     } 
    } 
    return null; 
} 

Preguntas

  1. Puede alguien explicar por qué lo hace no trabajo cuando usando agrupación?
    • El ItemContainerGenerator.ContainerFromItem siempre devuelve null, aunque su estado indica que se han generado todos los contenedores.
  2. ¿Cómo puedo lograr el desplazamiento a la vista cuando usando agrupando?

Respuesta

8

He encontrado una solución a mi problema. Estaba seguro de que no era la primera persona en abordar este problema, así que seguí buscando soluciones para StackOverflow y tropecé con esta respuesta por David about how ItemContainerGenerator works with a grouped list.

La solución de David fue retrasar el acceso al ItemContainerGenerator hasta el después del proceso de renderizado.

Implementé esta solución, con algunos cambios que detallaré a continuación.

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 
{ 
    ListBox control = (ListBox)sender; 

    if (control.IsGrouping) 
    { 
     if (control.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) 
       Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(DelayedBringIntoView)); 
     else 
       control.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; 
    } 
    else 
     control.ScrollIntoView(control.SelectedItem); 
} 

private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) 
{ 
    if (ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated) 
     return; 

    ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged; 
    Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(DelayedBringIntoView)); 
} 

private void DelayedBringIntoView() 
{ 
    var item = ItemContainerGenerator.ContainerFromItem(SelectedItem) as ListBoxItem; 
    if (item != null) 
     item.BringIntoView(); 
} 

Cambios:

  • sólo utiliza el enfoque cuando se ItemContainerGeneratorIsGrouping es true, de lo contrario seguir utilizando el valor predeterminado ScrollIntoView.
  • Compruebe si el ItemContainerGenerator está listo, de ser así envíe la acción, de lo contrario, escuche el estado ItemContainerGenerator para cambiar .. Esto es importante como si estuviera listo y el evento StatusChanged nunca se disparará.
+0

Debe cambiar su respuesta a la correcta, no la de arriba. – Valentein

+0

@Valentein: He cambiado la respuesta marcada. SIN EMBARGO, como los consejos de [crazyarabian] (http://stackoverflow.com/a/7375646/73025) me ayudaron a diagnosticar el problema, sería bueno votar ** ambas ** respuestas si utilizabas mi solución final. – Dennis

+0

Usando .NET 4.5.1 y MVVM puede usar un comportamiento para hacer esto. El comportamiento funciona en ambos escenarios, ya que ya se dispara tarde. – Kelly

3
  1. El fuera de la caja VirtualizingStackPanel no soporta la virtualización de vistas conjunto agrupado. Cuando una colección agrupada se procesa en un ItemsControl, cada grupo como un todo es un elemento en lugar de cada elemento de la colección, lo que produce un desplazamiento "brusco" a cada encabezado de grupo y no a cada elemento.

  2. Probablemente necesites rodar tu propio VirtualizingStackPanel o ItemContainerGenerator para hacer un seguimiento de los contenedores que se muestran en un grupo. Suena ridículo, pero la virtualización predeterminada con agrupación en WPF es escasa, por decir lo menos.

+0

Eso es lo que pensé, sin embargo esperaba que no tuviera que hacerlo porque escribir paneles de virtualización puede ser complicado. ... Como cada grupo como un todo es el ítem, ¿es por eso que 'ItemContainerGenerator' siempre devuelve' null' cuando pasa el 'SelectedItem'? – Dennis

+1

Creo que sí. Deberías echarle un vistazo al blog de Bea Stollnitz. Tiene muchas buenas publicaciones sobre agrupación y virtualización: http://www.beacosta.com/ – sellmeadog

+0

Gracias. He leído varias de las publicaciones de Bea Stollnitz en WPF y CollectionViewSource y Agrupación. De hecho, utilicé sus ejemplos de agrupamiento en mi pregunta. – Dennis

Cuestiones relacionadas