使用数据绑定的WPF ComboBox诊断性能问题

5

今天早上我一直在解决一个“慢”的WPF ComboBox问题,希望有人能给出调试此类问题的技巧。

假设我有两个ComboBox,A和B。当A改变时,B中的项目也会改变。ComboBoxes分别将它们的SelectedItem和ItemsSource数据绑定如下:

<ComboBox Grid.Column="1" ItemsSource="{Binding Names}" SelectedItem="{Binding CurrentName, Mode=TwoWay}" Margin="3" MinWidth="100" />
<ComboBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding SubNames}" SelectedItem="{Binding CurrentSubName, Mode=TwoWay}" Margin="3" MinWidth="100" />

每当需要更改B中的列表时,我会通过清除SubNames,然后根据A中的SelectedItem重新添加条目来完成。这样做是因为用新的ObservableCollection<string>覆盖SubNames会破坏数据绑定。
在一台计算机上,一切都像你期望的那样运行。选择A,然后单击B,新项目立即弹出。在另一台计算机上,当我这样做时,ComboBox渲染之前可能会有多达5秒的暂停。项目数量完全相同。一个区别是,在慢速机器上,有与硬件通信相关的后台任务正在进行。我冻结了所有这些线程,但没有帮助。
我最大的问题是我甚至不知道从哪里开始寻找。我需要看到系统在单击ComboBox时正在执行的操作。我正在使用数据绑定,因此无法在任何地方设置断点。我尝试更改SubNames的声明,从...开始。
public ObservableCollection<string> SubNames { get; set; }

to

private ObservableCollection<string> subnames_ = new ObservableCollection<string>();
public ObservableCollection<string> SubNames
{
  get { return subnames_; }
  set { subnames_ = value; }
}

然后在getter和setter中设置断点,查看是否存在过多的读写操作,但没有发现任何问题。

有人能建议我下一步该怎么做来确定这个减速的原因吗?我不认为它与ComboBox的默认模板有任何关系,就像这篇文章所描述的那样。


1
你能检查一下在调试会话之外运行时是否仍然是这种情况吗? - ArielBH
1
我完全忘记了这个问题!我找到了一篇帖子,建议只有在调试器内才会出现这种情况,果然如此。但是我已经改变了代码,将其全部使用ICollectionView,并且我认为即使在调试器内部也非常快。 - Dave
你节省了我数小时的调试时间。我使用CollectionViewSource(和List<T>),在调试模式下,设置ItemsSource后打开ComboBox需要3-4秒钟,但在调试器之外,它运行得更快。感谢您指出这一点。 - surfen
1个回答

2
虽然这可能并不能直接回答您的问题,但是一个建议是不要直接绑定到ObservableCollection。由于该集合在操作其内容时可能会引发许多事件,因此最好将ItemsControl绑定到表示该ObservableCollection的ICollectionView,并在更新集合时使用ICollectionView.DeferRefresh()
我通常做的是创建一个派生自ObservableCollection的类,该类公开一个DefaultView属性,该属性惰性实例化对应于集合的ICollectionView。然后我将所有ItemsControls绑定到collection.DefaultView属性。然后,当我需要刷新或以其他方式操作集合中的项时,我使用:
using (collection.DefaultView.DeferRefresh()) {
  collection. // add/remove/replace/clear etc
}

只有在DeferRefresh()返回的对象被处理后,这才会刷新绑定的控件。

还要注意的是,WPF中的绑定机制有一个默认的TraceSource,可以用来获取有关绑定本身的更多信息。它不会记录时间,所以我不确定它有多少用处,但您可以使用以下方式激活它:

System.Diagnostics.PresentationTraceSources.DataBindingSource.Switch.Level = System.Diagnostics.SourceLevels.Verbose;

(或者您喜欢的任何其他级别)。

谢谢你的建议。很有趣,我刚遇到了一个与你所说的直接相关的问题——太多事件被触发了,并且由于两者之间的依赖关系,我陷入了奇怪的循环中,尝试更新主下拉框后会导致第二个下拉框出现奇怪的问题。我打算研究一下ICollectionView,有趣的是,我以前用过它,但是现在完全记不起来了。 :) - Dave
你能推荐一些通用策略,用于当从组合框 A 中选择一个项目时,更改组合框 B 的整个内容的情况吗?我通常会使用 Clear() 和 Add() 到 ObservableCollection,这很有效,但正如你所指出的那样,拥有更多控制触发的事件是很好的。我采取了一个中间步骤,尝试使用 ICollectionView -- 我使用 GetDefaultView() 作为重新创建出现在我的组合框 B 中的数据的手段,但它不起作用 -- 原始内容仍然存在。你知道我错过了什么吗?我尝试了 Refresh(),但没有帮助。 - Dave
嗯...那么,我通常会在ViewModel中有一个类型为ICollectionView的属性,并在首次访问时将其实例化为一个ListCollectionView的实例,它包装了也是ViewModel一部分的ObservableCollection。为了简化所有这些,我扩展了ObservableCollection以自动执行一个名为DefaultView的属性;现在我可以在ViewModel中公开ObservableCollection,并在需要时在XAML中绑定到其DefaultView属性。这样,当其背后的集合发生变化时,ICollectionView应该自动同步。 - Alex Paven
此外,当您需要DeferRefresh或设置选择时,您还可以使用集合的DefaultView。 - Alex Paven

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