WPF ComboBox绑定大型集合时的性能问题

62
我试图将一个大集合绑定到ComboBox上,但是在打开ComboBox的弹出窗口时遇到了性能问题。我搜索了互联网并发现,使用VirtualizingStackPanel作为项面板模板可能有所帮助,但只在某种程度上有所帮助。如果我将一个大集合绑定到ComboBox上,我可以很快地打开弹出窗口,这没有问题,但如果在此之后我将另一个集合绑定到ComboBox上并尝试再次打开弹出窗口,则会变得非常慢。如果您打开一个空ComboBox的弹出窗口,然后绑定大集合并再次尝试打开弹出窗口,则需要几秒钟才能打开弹出窗口。

以下是XAML代码:

<ComboBox Name="cbBlah">
    <ComboBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ComboBox.ItemsPanel>
</ComboBox>

并且绑定重现问题的示例代码:

var list = new List<string>();
for (var i = 0; i < new Random().Next(9000, 10000); i++)
    list.Add(i.ToString());
cbBlah.ItemsSource = list;

我试图让虚拟化的堆栈面板看起来像这样:

<VirtualizingStackPanel VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" />

但是这并没有起到帮助的作用,似乎虚拟化模式被忽略了,所以弹出窗口只在第一次打开时非常快,然后每次绑定更改后,速度都非常慢。

更新:我考虑过不每次绑定新的集合,而是绑定一个ObservableCollection一次,然后仅更改其内容。同样的问题,一旦集合的内容发生变化,打开弹出窗口仍然需要几秒钟 :(


看一下我回答的这些问题:https://dev59.com/SFHTa4cB1Zd3GeqPQlnD#8555403 - punker76
当我对一个集合进行过滤时,我也发现了这种延迟。我将ListBox绑定到一个经过筛选的列表,并且每次我改变筛选条件并用新的列表替换筛选列表时都会出现延迟。所以我只是将ListBox绑定到源列表,并在项模板中使用布尔值Visible属性来折叠隐藏的项。这起作用得很好。虽然不是理想的解决方案,但它可以立即生效。我将尝试使用ObservableCollection来解决问题。也许我可以将其绑定到可观察的筛选列表上。但这真的很奇怪。 - zORg Alex
4个回答

154

根据该博客:http://vbcity.com/blogs/xtab/archive/2009/12/15/wpf-using-a-virtualizingstackpanel-to-improve-combobox-performance.aspx

我已经使用以下代码进行了测试:

<ComboBox Name="cbBlah" ItemsSource="{Binding}">
    <ComboBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ComboBox.ItemsPanel>
</ComboBox>

第一次和以后都可以正常工作。 不需要编写这些代码:

<VirtualizingStackPanel VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" />

2
您,先生,真是个天才!这对我来说产生了巨大的性能差异,而且修复方法既优雅又简单。谢谢! :) - dbeachy1
1
能否用C#代码实现这个功能?我正在实现一个从Combobox派生的类,并且我想在这里设置它。 - jonas
谢谢,这对我很有帮助。你不知道我尝试了多久才实现这个。谢谢! - Milan Kocic
我只是想知道为什么这不是默认设置...它要多少钱? 总之,非常感谢,这已经困扰了我一段时间,但我无法深入调查。 - lollancf37
“不需要编写这些代码” --- 你的意思是这行代码导致了性能问题吗?因为这是你的答案和问题代码之间唯一的区别。 - Lei Yang

14

我也遇到了性能缓慢的问题。但我创建了一个从Combobox继承的类,因此我想以编程方式解决这个问题。所以,以下是那个解决方案,供其他谷歌用户参考。

ItemsPanel = new ItemsPanelTemplate();
var stackPanelTemplate = new FrameworkElementFactory(typeof (VirtualizingStackPanel));
ItemsPanel.VisualTree = stackPanelTemplate;

把那段代码塞进构造函数里,运行得很好。谢谢! - Kris
1
我知道这是一篇旧文章,但对于其他谷歌用户来说,这确实是一个优雅的解决方案。能够 极大地 加速事情进展。 - Chris

1

我也遇到了这个问题。我在一个自定义组合框中使用此代码与样式模板。当我在VS调试模式下运行我的代码时,虚拟化无法正常工作。一旦我在调试外部运行它,我就可以在不锁定UI的情况下切换ObservableCollection的内容。如果您设置最大高度和最大宽度,也可能会有所帮助。

<Setter Property="ScrollViewer.CanContentScroll" Value="True"/> 
<Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True"/>
<Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Recycling"/>

<Popup>
    <Border/>
    <ScrollViewer>
      <VirtualizingStackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained"/>
    </ScrollViewer> 
  </Grid>
</Popup>

0

从可用性的角度来看,使用一个比屏幕能显示更多选项的标准组合框总是很麻烦的。它至少需要一个文本框过滤器。在许多情况下,选项可以被预先过滤(例如按部门,按首字母或范围),这样可以减少对象并且通常更加用户友好。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接