gpt4 book ai didi

WPF ListBox 虚拟化搞砸了显示的项目

转载 作者:行者123 更新时间:2023-12-04 04:23:28 25 4
gpt4 key购买 nike

问题

我们需要在 WPF ListBox 控件中有效地显示大量 (>1000) 对象。
我们依靠 WPF ListBox 的虚拟化(通过 VirtualizingStackPanel)来有效地显示这些项目。

错误 : WPF ListBox 控件在使用虚拟化时无法正确显示项目。

如何重现

我们已将问题提炼为如下所示的独立 xaml。

将 xaml 复制并粘贴到 XAMLPad 中。

最初,ListBox 中没有选定的项目,因此正如预期的那样,所有项目都具有相同的大小并且它们完全填满了可用空间。

现在,单击第一项。
正如预期的那样,由于我们的 DataTemplate,所选项目将展开以显示其他信息。

正如预期的那样,这会导致出现水平滚动条,因为所选项目现在比可用空间宽。

现在使用鼠标单击并向右拖动水平滚动条。

错误:未选择的可见项目不再拉伸(stretch)以填充可用空间。所有可见项目应具有相同的宽度。

这是一个已知的错误?
有没有办法通过 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>

最佳答案

我花了比我应该做的更多的时间来尝试这个,并且无法让它发挥作用。我了解这里发生了什么,但是在纯 XAML 中,我无法弄清楚如何解决这个问题。我想我知道如何解决问题,但它涉及转换器。

警告:当我解释我的结论时,事情会变得复杂。

潜在的问题来自控件的宽度拉伸(stretch)到其容器的宽度这一事实。启用虚拟化后,宽度不会改变。在底层 ScrollViewer ListBox 内部, ViewportWidth属性对应于您看到的宽度。当另一个控件进一步延伸(您选择它)时,ViewportWidth仍然是一样的,但 ExtentWidth显示全宽。将所有控件的宽度绑定(bind)到 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 添加了一个名字以此目的。

然后,我尝试将边框的宽度绑定(bind)到 ExtentWidth:
Width="{Binding ElementName=scrollViewer, Path=ExtentWidth}"

然而,由于 2 像素的填充,控件将在无限循环中调整大小,填充将 2 像素添加到 ExtentWidth。 ,它会调整边框宽度的大小,从而为 ExtentWidth 增加 2 个像素。等,直到您删除代码并刷新。

如果您添加了一个从 ExtentWidth 中减去 2 的转换器,我认为这可能会起作用。但是,当滚动条不存在(您没有选择任何内容)时, ExtentWidth="0" .因此,绑定(bind)到 MinWidth而不是 Width可能会更好,因此当没有滚动条可见时,项目会正确显示:
MinWidth="{Binding ElementName=scrollViewer, Path=ExtentWidth, Converter={StaticResource PaddingSubtractor}}"

一个 更好的解决方案如果您可以直接数据绑定(bind) MinWidthListBoxItem本身。您可以直接绑定(bind)到 ExtentWidth,并且不需要转换器。但是我不知道如何访问该项目。

编辑:为了组织起见,这是执行此操作所需的剪辑。使其他一切都变得不必要:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="MinWidth" Value="{Binding Path=ExtentWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ScrollViewer}}}" />
</Style>

关于WPF ListBox 虚拟化搞砸了显示的项目,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1882321/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com