2009-12-10 27 views
9

Problematornillos de virtualización ListBox WPF artículos

Necesitamos mostrar de manera eficiente un gran número (> 1000) de los objetos en un control ListBox WPF visualizado. Estamos confiando en la virtualización WPF ListBox (a través de VirtualizingStackPanel) para mostrar estos elementos de manera eficiente.

Error: El control WPF ListBox no muestra los elementos correctamente al usar la virtualización.

cómo reproducir

Hemos destilada el problema a la xaml independiente se muestra a continuación.

Copie y pegue el xaml en XAMLPad.

Inicialmente, no hay ningún elemento seleccionado en el ListBox, por lo que, como se esperaba, todos los elementos tienen el mismo tamaño y ocupan por completo el espacio disponible.

Ahora, haga clic en el primer elemento. Como era de esperar, debido a nuestro DataTemplate, el elemento seleccionado se expandirá para mostrar información adicional.

Como era de esperar, esto hace que aparezca la barra de desplazamiento horizontal, ya que el elemento seleccionado ahora es más ancho que el espacio disponible.

Ahora use el mouse para hacer clic y arrastrar la barra de desplazamiento horizontal hacia la derecha.

Error: los elementos visibles no seleccionados ya no se extienden para ocupar el espacio disponible. Todos los elementos visibles deben tener el mismo ancho.

¿Es este un error conocido? ¿Hay alguna forma de solucionar esto, ya sea a través de XAML o mediante programación?


<Page 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:sys="clr-namespace:System;assembly=mscorlib" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > 
    <Page.Resources> 

     <DataTemplate x:Key="MyGroupItemTemplate"> 
      <Border Background="White" 
        TextElement.Foreground="Black" 
        BorderThickness="1" 
        BorderBrush="Black" 
        CornerRadius="10,10,10,10" 
        Cursor="Hand" 
        Padding="5,5,5,5" 
        Margin="2" 
        > 
       <StackPanel> 
        <TextBlock Text="{Binding Path=Text, FallbackValue=[Content]}" /> 
        <TextBlock x:Name="_details" Visibility="Collapsed" Margin="0,10,0,10" Text="[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]" /> 
       </StackPanel> 
      </Border> 
      <DataTemplate.Triggers> 
       <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBoxItem}},Path=IsSelected}" 
          Value="True"> 
        <Setter Property="TextElement.FontWeight" 
          TargetName="_details" 
          Value="Bold"/> 
        <Setter Property="Visibility" 
          TargetName="_details" 
          Value="Visible"/> 
       </DataTrigger> 
      </DataTemplate.Triggers> 
     </DataTemplate> 

    </Page.Resources> 

    <DockPanel x:Name="LayoutRoot"> 

     <Slider x:Name="_slider" 
       DockPanel.Dock="Bottom" 
       Value="{Binding FontSize, ElementName=_list, Mode=TwoWay}" 
       Maximum="100" 
       ToolTip="Font Size" 
       AutoToolTipPlacement="BottomRight"/> 

     <!-- 
      I want the items in this ListBox to completly fill the available space. 
      Therefore, I set HorizontalContentAlignment="Stretch". 

      By default, the WPF ListBox control uses a VirtualizingStackPanel. 
      This makes it possible to view large numbers of items efficiently. 
      You can turn on/off this feature by setting the ScrollViewer.CanContentScroll to "True"/"False". 

      Bug: when virtualization is enabled (ScrollViewer.CanContentScroll="True"), the unselected 
       ListBox items will no longer stretch to fill the available horizontal space. 
       The only workaround is to disable virtualization (ScrollViewer.CanContentScroll="False"). 
     --> 

     <ListBox x:Name="_list" 
       ScrollViewer.CanContentScroll="True" 
       Background="Gray" 
       Foreground="White" 
       IsSynchronizedWithCurrentItem="True" 
       TextElement.FontSize="28" 
       HorizontalContentAlignment="Stretch" 
       ItemTemplate="{DynamicResource MyGroupItemTemplate}"> 
      <TextBlock Text="[1] This is item 1." /> 
      <TextBlock Text="[2] This is item 2." /> 
      <TextBlock Text="[3] This is item 3." /> 
      <TextBlock Text="[4] This is item 4." /> 
      <TextBlock Text="[5] This is item 5." /> 
      <TextBlock Text="[6] This is item 6." /> 
      <TextBlock Text="[7] This is item 7." /> 
      <TextBlock Text="[8] This is item 8." /> 
      <TextBlock Text="[9] This is item 9." /> 
      <TextBlock Text="[10] This is item 10." /> 
     </ListBox> 

    </DockPanel> 
</Page> 
+0

¡Gracias Will! Consulte "Responder" a continuación para obtener más detalles. –

Respuesta

3

pasé más tiempo de intentar esto de lo que probablemente debería tener, y no podía conseguir que funcione. Entiendo lo que está sucediendo aquí, pero en XAML puro, tengo problemas para encontrar la manera de resolver el problema. Creo que veo cómo resolver el problema, pero implica un convertidor.

Advertencia: Las cosas se van a complicar a medida que explico mis conclusiones.

El problema subyacente proviene del hecho de que el ancho de los controles se extiende al ancho de su contenedor. Cuando la virtualización está habilitada, el ancho no cambiará. En el ScrollViewer subyacente dentro de ListBox, la propiedad ViewportWidth corresponde al ancho que ve. Cuando otro control se extiende aún más (lo selecciona), el ViewportWidth sigue siendo el mismo, pero el ExtentWidth muestra el ancho completo. Encuadernar el ancho de todos los controles al de ExtentWidth debería funcionar ...

Pero no es así. Configuré FontSize en 100 para una prueba más rápida en mi caso. Cuando se selecciona un artículo, ExtentWidth="4109.13. Bajando por el árbol a su ControlTemplate Border, veo ActualWidth="4107.13". ¿Por qué la diferencia de 2 píxeles?ListBoxItem contiene un borde con relleno de 2 píxeles, lo que hace que el ContentPresenter se vea un poco más pequeño.

que añade la siguiente Style con help from here que me permita acceder directamente a los ExtentWidth:

<Style x:Key="{x:Type ListBox}" TargetType="ListBox"> 
    <Setter Property="Template"> 
    <Setter.Value> 
     <ControlTemplate TargetType="ListBox"> 
     <Border 
      Name="Border" 
      Background="White" 
      BorderBrush="Black" 
      BorderThickness="1" 
      CornerRadius="2"> 
      <ScrollViewer 
      Name="scrollViewer" 
      Margin="0" 
      Focusable="false"> 
      <StackPanel IsItemsHost="True" /> 
      </ScrollViewer> 
     </Border> 
     <ControlTemplate.Triggers> 
      <Trigger Property="IsEnabled" Value="false"> 
      <Setter TargetName="Border" Property="Background" 
        Value="White" /> 
      <Setter TargetName="Border" Property="BorderBrush" 
        Value="Black" /> 
      </Trigger> 
      <Trigger Property="IsGrouping" Value="true"> 
      <Setter Property="ScrollViewer.CanContentScroll" Value="false"/> 
      </Trigger> 
     </ControlTemplate.Triggers> 
     </ControlTemplate> 
    </Setter.Value> 
    </Setter> 
</Style> 

Nota añadí un nombre a ScrollViewer para este propósito.

Entonces, me trató de obligar a la anchura de su borde a la ExtentWidth:

Width="{Binding ElementName=scrollViewer, Path=ExtentWidth}" 

Sin embargo, debido a que el acolchado de 2 píxeles, los controles cambiarán de tamaño en un bucle infinito, con el relleno de la adición de 2 píxeles al ExtentWidth, que cambia el ancho del borde, que agrega 2 píxeles más al ExtentWidth, etc. hasta que elimine el código y actualice.

Si agregó un convertidor que restó 2 de ExtentWidth, creo que esto podría funcionar. Sin embargo, cuando la barra de desplazamiento no existe (no ha seleccionado nada), ExtentWidth="0". De este modo, la unión a MinWidth en lugar de Width puede funcionar mejor para que los elementos aparecen correctamente cuando no hay barra de desplazamiento es visible:

MinWidth="{Binding ElementName=scrollViewer, Path=ExtentWidth, Converter={StaticResource PaddingSubtractor}}" 

Una solución mejor sería si se pudiera DataBind directamente la MinWidth del ListBoxItem sí. Podría enlazar directamente a ExtentWidth, y no sería necesario ningún conversor. Sin embargo, no tengo idea de cómo acceder a ese artículo.

Editar: Por el bien de la organización, aquí está el clip necesario para hacer eso. Hace que todo lo demás sea innecesario:

<Style TargetType="{x:Type ListBoxItem}"> 
    <Setter Property="MinWidth" Value="{Binding Path=ExtentWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ScrollViewer}}}" /> 
</Style> 
+0

En respuesta a "¿Esto es un error?", No tengo ni idea, pero teniendo en cuenta el soporte integrado para la virtualización, espero que así sea, especialmente si no hay una solución más simple para esto de lo que estaba viniendo con. –

2

Gracias a Will's great analysis!

Sobre la base de la sugerencia de Will: "Una solución mejor sería si se pudiera DataBind directamente la MinWidth del propio ListBoxItem ... Sin embargo no tengo idea de cómo acceder a ese elemento", pude para poner en práctica que el uso de XAML pura, de la siguiente manera:

<ListBox x:Name="_list" 
     Background="Gray" 
     Foreground="White" 
     IsSynchronizedWithCurrentItem="True" 
     TextElement.FontSize="28" 
     HorizontalContentAlignment="Stretch" 
     ItemTemplate="{DynamicResource MyGroupItemTemplate}"> 

    <!-- Here is Will's suggestion, implemented in pure xaml. Seems to work. 
     Next problem is if you drag the Slider to the right to increase the FontSize. 
     This will make the horizontal scroll bar appear, as expected. 
     Problem: the horizontal scroll bar never goes away if you drag the Slider to the left to reduce the FontSize. 
    --> 
    <ListBox.Resources> 
     <Style TargetType="{x:Type ListBoxItem}"> 
      <Setter Property="MinWidth" Value="{Binding Path=ExtentWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ScrollViewer}}}" /> 
     </Style> 
    </ListBox.Resources> 

    <TextBlock Text="[1] This is item 1." /> 
    <TextBlock Text="[2] This is item 2." /> 
    <TextBlock Text="[3] This is item 3." /> 
    <TextBlock Text="[4] This is item 4." /> 
    <TextBlock Text="[5] This is item 5." /> 
    <TextBlock Text="[6] This is item 6." /> 
    <TextBlock Text="[7] This is item 7." /> 
    <TextBlock Text="[8] This is item 8." /> 
    <TextBlock Text="[9] This is item 9." /> 
    <TextBlock Text="[10] This is item 10." /> 
</ListBox> 

me ocurrió la idea de gran libro de Adam Nathan, "de Windows Presentation Foundation Desencadenada".

Por lo tanto, esto parece solucionar el problema original.

nuevo problema

Se nota que hay un control deslizante en el XAML que te permite aumentar/disminuir la fuente ListBox. La idea aquí era permitir al usuario la capacidad de escalar el contenido de ListBox hacia arriba o hacia abajo para una visibilidad más fácil.

Si primero arrastre el control deslizante a la derecha para aumentar FontSize, esto hará que aparezca la barra de desplazamiento horizontal, como se esperaba.El nuevo problema es que la barra de desplazamiento horizontal nunca desaparece si arrastra el control deslizante hacia la izquierda para reducir FontSize.

¿Alguna idea?

+0

Probablemente será mejor que haga una nueva pregunta, ya que no obtendrá muchos puntos de vista en este momento. Podría echarle un vistazo a esto más tarde ya que encontré el tema original interesante. Además, un voto positivo o una respuesta aceptada si mi respuesta fue útil sería apreciada :) –

+0

Perdón por eso ... Soy nuevo en este concepto de foro, así que descubrí cómo realmente registrarme, etc. –

+0

Aparentemente, esto es un error de wpf, y se soluciona en .NET 4.0. –