2009-12-10 83 views
9

問題WPF列表框虛擬化螺絲了顯示的項目

我們需要在WPF列表框控件有效地顯示對象的大(> 1000)號。 我們依靠WPF ListBox的虛擬化(通過VirtualizingStackPanel)來高效地顯示這些項目。

錯誤:使用虛擬化時,WPF ListBox控件無法正確顯示項目。

如何重現

我們提煉的問題如下所示的獨立XAML。

將xaml複製並粘貼到XAMLPad中。

最初,是在列表框中沒有選擇項,從而預期,所有的項目都是相同的大小,他們完全填充的可用空間。

現在,點擊第一項。 正如所料,由於我們的DataTemplate,所選項目將展開以顯示更多信息。

正如預期的那樣,這將導致水平滾動條出現,因爲所選擇的項目是現在比可用空間更寬。

現在使用鼠標單擊並向右拖動水平滾動條。

錯誤:未選擇可見的項目不再拉伸以填充可用空間。所有可見的項目應該是相同的寬度。

這是一個已知的錯誤嗎? 有沒有辦法通過XAML或編程方式解決這個問題?


<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

謝謝! 有關更多詳細信息,請參見下面的「答案」。 – 2009-12-12 20:23:49

回答

3

我花更多時間來嘗試這方面比我大概應該有,並不能得到它的工作。我理解這裏發生了什麼,但在純粹的XAML中,我很難找出如何解決問題。我想我看到如何解決這個問題,但它涉及到一個轉換器。

警告:隨着我解釋我的結論,事情會變得複雜。

根本問題來自這樣一個事實,即控件的寬度伸展到其容器的寬度。虛擬化啓用時,寬度不會更改。在ListBox內部的底層ScrollViewer中,ViewportWidth屬性對應於您看到的寬度。當另一個控件進一步伸展(選擇它)時,ViewportWidth仍然相同,但ExtentWidth顯示全寬。將所有控件的寬度綁定到ExtentWidth的寬度應該可行...

但它沒有。我將FontSize設置爲100,以便在我的情況下更快地進行測試。當選擇一個項目時,ExtentWidth="4109.13。沿樹到你的ControlTemplate的Border,我看到ActualWidth="4107.13"。爲什麼2像素差異?ListBoxItem包含一個帶有2像素填充的邊框,使ContentPresenter渲染得稍小。

添加以下Stylehelp from here,讓我直接訪問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> 

注意我添加了一個名字ScrollViewer用於這一目的。

於是,我試圖綁定你的邊界到ExtentWidth的寬度:

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

然而,因爲2像素的填充物,該控件將在一個無限循環調整,以填充增加2個像素到ExtentWidth,它調整邊框寬度的大小,這會在ExtentWidth等中增加2個像素,直到您刪除代碼並刷新。

如果您添加了一個從ExtentWidth中減去2的Converter,我認爲這可能會起作用。但是,當滾動條不存在(您沒有選擇任何東西),ExtentWidth="0"。因此,結合MinWidth代替Width可以更好地工作,使項目正常顯示時,沒有滾動條可見:

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

一個更好的解決方案是,如果你可以直接將數據綁定本身ListBoxItemMinWidth。你可以直接綁定到ExtentWidth,並且不需要轉換器。不過,我不知道如何訪問該項目。

編輯:爲了組織的緣故,這裏是需要做的剪輯。使一切不必要:

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

迴應「這是一個錯誤?」,我不知道,但考慮到對虛擬化的內置支持,我希望它是,特別是如果沒有比這更簡單的解決方案正在提出。 – 2009-12-12 08:17:26

2

感謝威爾的偉大分析!

基於意志的建議:「一個更好的解決辦法是,如果你可以直接將數據綁定本身ListBoxItem的......不過我不知道怎麼去該項目訪問的MinWidth」,我能實現一個使用純XAML,如下所示:

<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> 

我從亞當森的偉大的書的想法, 「的Windows Presentation Foundation偷跑」。

所以,這似乎解決了原來的問題。

新問題

你注意到沒有在XAML一個滑塊控件讓你增加/減少列表框字體的。這裏的想法是讓用戶能夠向上或向下縮放ListBox內容以便更容易看到。

如果您先將拖到右邊的以增加字體大小,這將使水平滾動條出現,如預期。新問題是,如果將滑塊向左拖動以縮小FontSize,則水平滾動條永不消失。

任何想法?

+0

你最好問一個新問題,因爲此時你不會收到很多意見。自從我發現原始問題感興趣以後,我可能會稍後再看一下。另外,如果我的回覆是有幫助的upvote或接受的答案,將不勝感激:) – 2009-12-13 03:30:24

+0

對不起,這...我是新來這個論壇的概念,所以只是想出瞭如何實際註冊,等 – 2009-12-13 16:02:52

+0

顯然,這是一個wpf錯誤,並在.NET 4.0中修復。 – 2010-02-22 23:06:29