views:

31

answers:

1

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.

+1  A: 

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).

Alex Paven
@Alex Thanks for the suggestion. It's funny, I just ran into a problem which is directly related to what you've said -- too many events are getting fired, and with the dependency between the two, I'm getting caught in weird circular loops where trying to update the main combobox is then causing weird things to happen with the second. I'm going to look over ICollectionView, which strangely enough, I've used it before but can't remember anything about it. :)
Dave
@Alex Can you recommend any general strategies for the situation where selecting an item from combobox A changes the entire contents for combobox B? I normally Clear() and Add() to an ObservableCollection, which works, but as you pointed out it's nice to have more control over the events that get fired. I took an intermediate step and tried to use an ICollectionView -- I used GetDefaultView() as a means for recreating the data that appears in my combobox B, but it doesn't work that way -- the original contents remain. Any idea what I'm missing here? I tried Refresh() and that didn't help.
Dave
Umm... well then. What I normally do is have a property of type ICollectionView in my ViewModel, and instantiate it to an instance of ListCollectionView when it's first accessed, that wraps the ObservableCollection that's also a part of the ViewModel. To simplify all that, I extended ObservableCollection to do it automatically in a property called DefaultView; now I can expose the ObservableCollection in the ViewModel and bind to its DefaultView property in XAML if I need to. That way the ICollectionView should synchronize automatically when the collection behind it changes.
Alex Paven
...and also, you can use the DefaultView of the collection when you need to DeferRefresh or set the selection.
Alex Paven