在 WPF 中显示带有大量项目的 ListBox 中的边框图块

Display bordered tiles in ListBox with large number of items in WPF

我正在尝试构建一个类似于第一个屏幕截图中的 GUI:

基本上它是一个图块视图,显示在带边框的网格中,随着 window 调整大小时动态更改列数。项目是可选择的。这些可能看起来像图片,但它们只是 Unicode 字符。

我制作了一个自定义对象列表,并成功地将列表绑定到一个ListBox控件。自然地,这给了我一个垂直堆叠的字符列表。

所以我用谷歌搜索了一下,发现我可以为列表框设置一个自定义的 ItemsPanelTemplate:

<ListBox.ItemsPanel>
    <ItemsPanelTemplate>
       <WrapPanel Margin="0"/>
    </ItemsPanelTemplate>
</ListBox.ItemsPanel>

结果如下:

为了设置图块的边框和大小,我还添加了一个 ItemTemplate:

<ListBox Margin="0,0,0,0" Padding="0,0,0,0" Grid.Row="0" Grid.Column="0" x:Name="tw" 
            SelectionChanged="tw_SelectionChanged" 
            ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
            ScrollViewer.VerticalScrollBarVisibility="Visible">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel Margin="0,0,0,0"/>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Label 
                        Padding="0,0,0,0"
            Margin="0,0,0,0"
                        Width="50" 
                        Height="50" 
                        FontSize="20" 
                        Background="White"
                        HorizontalAlignment="Left" 
                        HorizontalContentAlignment="Center"
                        VerticalContentAlignment="Center"
                        BorderThickness="1"
                        BorderBrush="Black"
                    Content="{Binding Path=LiteralString}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

结果如下: 据我所知。

第一个问题是,如您所见,我无法摆脱的瓷砖之间有 spaces。您会在标记中注意到,我已尝试尽可能手动将 Margin 和 Padding 设置为 0。 space 还在。

第二个问题是,您无法真正看到选择了哪个项目。从下数第 3 个字符和从右数第 2 个字符被选中,但蓝色被标签覆盖。您只能在字符左侧的 space 中看到它。我不知道如何解决这个问题。

另一个潜在的问题是性能。该视图最终将具有过滤器,但是当所有过滤器都关闭时,将显示大约 6500 个项目。当我只有一个基本的 ListBox 时,应用程序在 运行 时占用了大约 28MB 的 RAM。当我添加带有 WrapPanel 的 ItemsPanelTemplate 时,内存使用量跃升至大约 136MB。当我添加带有标签的 ItemTemplate 时,内存使用量跃升至 183MB,更糟糕的是,window 现在需要更长的时间来加载。

我试过用 TextBlocks 替换 Labels,因为我读到它们更轻巧。事情变快了,内存使用量下降到 130MB 左右,但 TextBlocks 并不好。我不能将文本居中放置,也不能给它们加边框。正如您在屏幕截图中所见,所选项目变得一团糟:

诚然,我的电脑速度很慢,183MB 也不算死,但还是有点低效。我打算在这个应用程序中加入更多的功能,如果这一件事占用了这么多的 RAM,那么最终的应用程序很可能是一个无法使用的资源吸血鬼。另外,我相信我正在模仿的应用程序也是用 .NET 编写的(虽然我不确定它是否是 WPF)并且那个应用程序在显示视图时只使用 17.5MB 的 RAM。

是否有另一种方法可以做到这一点,一种既更高效又更容易定制的方法?

谢谢。

编辑:

我找到了稍微好一点的方法。我完全删除了 ItemTemplate,而是使用 ItemContainerStyle:

 <ListBox.ItemContainerStyle>
                <Style TargetType="ListBoxItem">
                    <Style.Setters>
                        <Setter Property="BorderBrush" Value="Black"/>
                        <Setter Property="BorderThickness" Value="1"/>
                        <Setter Property="Width" Value="50"/>
                        <Setter Property="Height" Value="50"/>
                        <Setter Property="FontSize" Value="20"/>
                        <Setter Property="HorizontalContentAlignment" Value="Center" />
                        <Setter Property="VerticalContentAlignment" Value="Center" />
                    </Style.Setters>
                    <Style.Triggers>
                        <Trigger Property="IsSelected" Value="True" >
                            <Setter Property="FontWeight" Value="Bold" />
                            <Setter Property="Background" Value="SteelBlue" />
                        </Trigger>
                    </Style.Triggers>

                </Style>
            </ListBox.ItemContainerStyle>

这会产生更好的结果。瓷砖之间没有可见的 spaces,我可以自定义选定和取消选择的瓷砖的外观。

我唯一不知道该怎么做的是有一条粗细为 1 像素的边框线。我得到两条粗细为 1 的线,导致粗细为 2。我尝试将边距和填充设置为 0 和 -1。没有运气。奇怪的是,将 BorderBrush 设置为 Black 并将 BorderThickness 设置为 0.5 不会生成粗细为 1 的组合黑线,而是粗细为 2 的灰线。

与我使用标签时相比,内存使用量下降了约 150MB,但加载时间仍然很糟糕,所以我仍在寻找更好的解决方案。

编辑 2:

我玩了一会儿边距,终于搞定了。通过将 ListBoxItem 的边距设置为 -0.5,我得到了正确的边框厚度,将瓷砖稍微压在一起。我想保留这个问题,希望有人能提出更有效的解决方案。 UI 一旦变为 运行 就会响应,但加载时间很糟糕。在我的应用程序的使用过程中,随着过滤器的调整和查询的进行,列表框将被反复重新填充。我希望这能尽快发生。另外,我认为内存使用率过高。

由于我没有足够的代表来 post 发表评论,我将尝试回答,或至少指出正确的方向。 1) 你得到 Thickness of 2 的原因是相邻的边界加入了它们各自的厚度。您使用的 -0.5 边距使它们相交,使合成厚度为 1。相反,您可以尝试设置右侧和底部的边框厚度,例如:

<Setter Property="BorderThickness" Value="0,0,1,1"/>

这意味着 LeftSide=0,Top=0,RightSide=1,Bottom=1 厚。所以最左边的项目不会有左边框,最上面的项目不会有顶部边框。因此相邻的边界不会合并,厚度加倍。

2) 以及内存使用过多和整体性能低下。当您将 WrapPanel 用于 ItemsPanel 时,您将放弃 UI 虚拟化。因为通常非 UI 计算不会花费太多时间,所以首先要考虑的是 UI 性能。尝试加载您的数据并在没有 UI 的情况下进行计算,我认为差异会很明显。 我建议您对 ListBox ItemsPanel 使用某种 VirtualizingWrapPanel。我个人使用这个,并根据更有经验的编码员的建议进行更正:

Original implementation

First correction

Second correction