2009-12-30 23 views
25

Mi prototipo muestra "documentos" que contienen "páginas" que son representadas por imágenes en miniatura. Cada documento puede tener cualquier número de páginas. Por ejemplo, podría haber 1000 documentos con 5 páginas cada uno, o 5 documentos con 1000 páginas cada uno, o en algún lugar entremedio. Los documentos no contienen otros documentos. En mi marcado xaml tengo un ListBox, cuyo ItemsTemplate hace referencia a un innerItemsTemplate que también tiene un ListBox. Quiero 2 niveles de elementos seleccionados para que pueda realizar varias operaciones en documentos o páginas (eliminar, fusionar, mover a una nueva ubicación, etc.). innerItemsTemplate ListBox usa un WrapPanel como ItemsPanelTemplate.WPF ListBox con ListBox - Virtualización de UI y Desplazamiento

Para el escenario en el que tengo un gran número de documentos con un par de páginas cada uno (digamos, 10000 documentos con 5 páginas cada uno), el desplazamiento funciona muy bien gracias a la interfaz de usuario de virtualización por el VirtualizingStackPanel. Sin embargo, tengo problemas si tengo una gran cantidad de páginas. Un documento con 1000 páginas solo mostrará aproximadamente 50 a la vez (lo que cabe en la pantalla), y cuando me desplazo hacia abajo, el ListBox externo se mueve al documento siguiente, omitiendo las páginas 950 que no estaban visibles. Junto con eso, no hay VirtualzingWrapPanel por lo que la memoria de la aplicación realmente aumenta.

Me pregunto si estoy haciendo esto de la manera correcta, especialmente ¡ya que es un poco difícil de explicar! Me gustaría poder mostrar 10000 documentos con 1000 páginas cada uno (solo se muestra lo que cabe en la pantalla), usando UI Virtualization, y también desplazamiento suave.

¿Cómo puedo asegurarme de que el desplazamiento se mueve a través de todas las páginas del documento antes de que se muestre el documento siguiente, y aún así mantener la virtualización de la interfaz de usuario? La barra de desplazamiento parece moverse al siguiente documento.

Parece lógico representar "documentos" y "páginas" - con mi método actual de usar un ListBox dentro de un ListBox?

Agradecería mucho cualquier idea que tenga. Gracias.

Respuesta

24

La respuesta es sorprendente:

  • Si utiliza ItemsControl o ListBox obtendrá el comportamiento que está experimentando, en donde el control se desplaza "por elemento" para saltar un documento completo a la vez, PERO
  • Si usa TreeView, el control se desplazará suavemente para que pueda desplazarse por el documento y pasar al siguiente, pero seguirá siendo capaz de virtualizar

Creo que la razón el equipo de WPF eligió este comportamiento es que TreeView comúnmente tiene elementos que son más grandes que el área visible, mientras que por lo general es ListBox no lo hacen.

En cualquier caso, es trivial en WPF para hacer una mirada TreeView y actuar como un ListBox o ItemsControl simplemente modificando la ItemContainerStyle. Esto es muy sencillo. Puede hacer rodar el suyo o simplemente copiar la plantilla adecuada desde el archivo de tema del sistema.

por lo que tendrá algo como esto:

<TreeView ItemsSource="{Binding documents}"> 
    <TreeView.ItemsPanel> 
    <ItemsPanelTemplate> 
     <VirtualizingStackPanel /> 
    </ItemsPanelTemplate> 
    </TreeView.ItemsPanel> 
    <TreeView.ItemContainerStyle> 
    <Style TargetType="{x:Type TreeViewItem}"> 
     <Setter Property="Template"> 
     <Setter.Value> 
      <ControlTemplate TargetType="{x:Type TreeViewItem}"> 
      <ContentPresenter /> <!-- put your desired container style here with a ContentPresenter inside --> 
      </ControlTemplate> 
     </Setter.Value> 
     </Setter> 
    </Style> 
    </TreeView.ItemContainerStyle> 
    <TreeView.ItemTemplate> 
    <DataTemplate TargetType="{x:Type my:Document}"> 
     <Border BorderThickness="2"> <!-- your document frame will be more complicated than this --> 
     <ItemsControl ItemsSource="{Binding pages}"> 
      ... 
     </ItemsControl> 
     </Border> 
    </DataTemplate> 
    </TreeView.ItemTemplate> 
</TreeView> 

Procedimientos de desplazamiento basado en píxeles y la selección múltiple de estilo ListBox a trabajar juntos

Si se utiliza esta técnica para conseguir el desplazamiento basado en píxeles, su ItemsControl externo que muestra los documentos no puede ser un ListBox (porque ListBox no es una subclase de TreeView o TreeViewItem). Por lo tanto, pierde todo el soporte multiselect de ListBox. Por lo que puedo decir, no hay forma de usar estas dos características juntas sin incluir parte de tu propio código para una característica u otra.

Si necesita ambos conjuntos de funcionalidad en el mismo control, que tienen básicamente varias opciones:

  1. Implementar la selección múltiple a sí mismo en una subclase de TreeViewItem. Use TreeViewItem en lugar de TreeView para el control externo, ya que permite seleccionar varios elementos secundarios. En la plantilla dentro de ItemsContainerStyle: agregue un CheckBox alrededor de ContentPresenter, la plantilla vincula el CheckBox a IsSelected y el estilo del CheckBox con la plantilla de control para obtener el aspecto que desea. A continuación, agregue sus propios manejadores de eventos de mouse para manejar Ctrl-Click y Shift-Click para multiselect.

  2. Implemente la virtualización de desplazamiento de píxeles en una subclase de VirtualizingPanel. Esto es relativamente simple, ya que la mayor parte de la complejidad de VirtualizingStackPanel se relaciona con el desplazamiento sin píxeles y el reciclaje de contenedores. Dan Crevier's Blog tiene alguna información útil para comprender VirtualizingPanel.

+0

Este enfoque me funciona en cuanto a la virtualización de UI. Ahora solo necesito obtener el comportamiento similar a ListBox para seleccionar elementos (páginas o documentos en este caso). ¿Cómo puedo obtener modos de selección múltiples y ampliados similares a ListBox? –

+0

Además, estoy configurando ItemsPanelTemplate dentro de ItemsControl en un WrapPanel, que no parece ajustarse cuando cambio el tamaño de la aplicación, parece comportarse más como un stackPanel. En general, siento que la respuesta anterior de Ray me ayuda a ir en la dirección correcta. –

+0

Estoy tan contenta de haber tropezado con esta publicación que me salvó sacarme todo el pelo. – Bijington

0

Permítame comenzar esta respuesta con una pregunta: ¿El usuario tiene que ver todas y cada una de las miniaturas dentro de cada elemento de la lista en todo momento?

Si la respuesta a esa pregunta es 'no', quizás sería posible limitar el número de páginas visibles dentro de la plantilla (dado que ha indicado que el desplazamiento funciona bien con, digamos, 5 páginas) y usar una plantilla separada de "elemento seleccionado" que es más grande y muestra todas las páginas para ese documento?Billy Hollis explica cómo 'pop' de un elemento seleccionado en un cuadro de lista en dnrtv episode 115

+0

sin que el usuario no tiene que ver todos y cada miniatura dentro de cada elemento de la lista en todo momento - que sólo tiene que ser capaz de desplazarse para llegar a otros elementos –

36

Es posible lograr VirtualizingStackPanels El desplazamiento uniforme en WPF 4.0 sin sacrificar la virtualización si está preparado para utilizar la reflexión para acceder a la funcionalidad privada del VirtualizingStackPanel.Todo lo que tiene que hacer es establecer la propiedad privada IsPixelBased de VirtualizingStackPanel en true.

Tenga en cuenta que en .Net 4.5 no es necesario este hack ya que puede configurar VirtualizingPanel.ScrollUnit = "Pixel".

para que sea realmente fácil, aquí hay un código:

public static class PixelBasedScrollingBehavior 
{ 
    public static bool GetIsEnabled(DependencyObject obj) 
    { 
     return (bool)obj.GetValue(IsEnabledProperty); 
    } 

    public static void SetIsEnabled(DependencyObject obj, bool value) 
    { 
     obj.SetValue(IsEnabledProperty, value); 
    } 

    public static readonly DependencyProperty IsEnabledProperty = 
     DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(PixelBasedScrollingBehavior), new UIPropertyMetadata(false, HandleIsEnabledChanged)); 

    private static void HandleIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var vsp = d as VirtualizingStackPanel; 
     if (vsp == null) 
     { 
      return; 
     } 

     var property = typeof(VirtualizingStackPanel).GetProperty("IsPixelBased", 
                    BindingFlags.NonPublic | BindingFlags.Instance); 

     if (property == null) 
     { 
      throw new InvalidOperationException("Pixel-based scrolling behaviour hack no longer works!"); 
     } 

     if ((bool)e.NewValue == true) 
     { 
      property.SetValue(vsp, true, new object[0]); 
     } 
     else 
     { 
      property.SetValue(vsp, false, new object[0]); 
     } 
    } 
} 

Para utilizar esto en un cuadro de lista, por ejemplo, puede hacer:

<ListBox> 
    <ListBox.ItemsPanel> 
     <ItemsPanelTemplate> 
     <VirtualizingStackPanel PixelBasedScrollingBehavior.IsEnabled="True"> 
      </VirtualizingStackPanel> 
     </ItemsPanelTemplate> 
    </ListBox.ItemsPanel> 
</ListBox> 
+19

Esto fue realmente útil, +1. Para los visitantes que usen .NET 4.5 en el futuro, debe configurar 'VirtualizingPanel.ScrollUnit =" Pixel "' en su 'ListBox', no en' VirtualizingStackPanel' que contiene el contenido. –

+0

Ok, esto no funciona para mí. No sé por qué. –

+0

@ViktorLaCroix: ¿ha instalado .Net 4.5 en su máquina? Como se trata de una actualización in situ de .Net 4.0, creo que rompe este truco. –

4

Esto funcionó para mí. Parece un par de atributos simples lo hará (.NET 4,5)

<ListBox    
    ItemsSource="{Binding MyItems}" 
    VirtualizingStackPanel.IsVirtualizing="True" 
    VirtualizingStackPanel.ScrollUnit="Pixel"/>