I've been battling a "slow" WPF ComboBox this morning, and would love to see if anyone has tips for debugging such a problem.
Let's say I have two ComboBoxes, A and B. When A changes, the items in B change as well. The ComboBoxes each have their SelectedItem and ItemsSource databound like this:
<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" />
Whenever the list in B needs to change, I do this by clearing SubNames and then re-adding the entries based on the SelectedItem in A. This is done because overwriting SubNames with a new ObservableCollection<string>
breaks the databinding.
Everything on one computer runs just as you'd expect. Select A, then click on B and the new items pop up immediately. On another computer, when I do this there is up to a 5 second pause before the ComboBox is rendered. The number of items is exactly the same. One difference is that on the slow machine, there is stuff going on in the background with hardware communication. I froze all of those threads and it didn't help.
My biggest problem is that I can't figure out where to even start looking. I need to see what the system is doing at the point that the ComboBox is clicked. I'm using databinding, so I can't put a breakpoint anywhere. I did try to change my declaration of SubNames from
public ObservableCollection<string> SubNames { get; set; }
to
private ObservableCollection<string> subnames_ = new ObservableCollection<string>();
public ObservableCollection<string> SubNames
{
get { return subnames_; }
set { subnames_ = value; }
}
and then put breakpoints in the getter and setter to see if there was excessive reading or writing going on, but there wasn't any.
Can anyone suggest a next step for me to try in determining the source of this slowdown? I don't believe it has anything to do with the ComboBox stock template, as described in this article.
While this may not directly answer your question, one suggestion would be to not bind directly to the ObservableCollection. Since the collection can raise a lot of events when manipulating its contents, it's better to bind the ItemsControl to an ICollectionView that represents that ObservableCollection, and when updating the collection use ICollectionView.DeferRefresh()
.
What I usually do is I make a class derived from ObservableCollection that exposes a DefaultView
property, which lazily instantiates the ICollectionView corresponding to the collection. Then I bind all ItemsControls to the collection.DefaultView property. Then, when I need to refresh or otherwise manipulate the items in the collection, I use:
using (collection.DefaultView.DeferRefresh()) {
collection. // add/remove/replace/clear etc
}
This refreshes the bound controls only after the object returned by DeferRefresh()
has been disposed.
Also be aware that the binding mechanisms in WPF have a default TraceSource you can use to glean more information on the bindings themselves; it doesn't trace the time, so I'm not sure how useful that is, but you can activate it with:
System.Diagnostics.PresentationTraceSources.DataBindingSource.Switch.Level = System.Diagnostics.SourceLevels.Verbose;
(or any other level you prefer).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With