1
votes

I have a WPF combo box bound to an obvserable collection (OC):

    <ComboBox Name="cbCombination" ItemsSource="{Binding Combinations}" 
              SelectedIndex="0" />

Elsewhere, in the object set as data context:

    public ObservableCollection<Combination> Combinations { get; set; }

Combination overrides its ToString and everything is peachy: The combo-box's drop-down displays all Combination items in the Combinations OC. The combo-box's selection box displays the value of the first Combination.

Now, the data-context object has to change the values in its Combinations OC:

    var combinationsList = CombinationsManager.CombinationsFor(someParam);
    this.Combinations.Clear();
    foreach (var combination in combinationsList)
        this.Combinations.Add(combination);
    NotifyPropertyChanged(@"Combinations");

This causes the combo-box's selection box shows an empty string. (The drop-down is closed. However, when I make it drop down, it does show the correct new Combinations, so it is bound to the updated collection).

I tried to capture both SourceUpdated and (in my dispair) TargetUpdated events (thining of setting the SelectedIndex there), but my event handlers didn't get called!

So my question is: How do I make a WPF ComboBox refresh the value of its selection-box when the observable collection it is bound-to changes?

Update:

I've totally forgotten to mention, and I don't know whether it's important, but the combo-box is within a UserControl. The UserControl's XAML looks like this:

<UserControl x:Class="...CombinationsToolBar"
         .... mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<ToolBarTray Name="toolBarTray1" >
    <ToolBar Name="toolBar1">
        <ComboBox Name="cbCombination" 
         ItemsSource="{Binding Path=Combinations, NotifyOnSourceUpdated=True, 
                                                                     IsAsync=True}"
            SelectedIndex="0" IsEditable="False"  
            SelectionChanged="CbCombinationSelectionChanged"
            SourceUpdated="CbCombinationSourceUpdated"
            TargetUpdated="CbCombinationTargetUpdated"></ComboBox>
    </ToolBar>
</ToolBarTray>

In the UserControl's code I have breakpoints on CbCombinationSelectionChanged, CbCombinationSourceUpdated and CbCombinationTargetUpdated.

The CbCombinationSelectionChanged fires once when the form containing the user control is first loaded. It is indeed called a second time when the Combinations collection is cleared, as @asktomsk said.

The source updated and target updated are not triggered - CbCombinationSourceUpdated and CbCombinationTargetUpdated are not called.

As the combo box is inside a usercontrol and the Combinations collection is within the view model, and as the view model doesn't have access to the combo box, I have no opportunity to set the selected index of the combo unless the events fire.

:(

1

1 Answers

3
votes

The problem is in your this.Combinations.Clear();
When you do it, it sets SelectedItem = null and SelectedIndex = -1

So you should set selection again. If you have an access to your ComboBox then just write this cbCombination.SelectedIndex = 0; after filling Combinations list.
Of course you can bind SelectedItem/SelectedIndex to some property in code behind too.

BTW
Also it is not required to call NotifyPropertyChanged(@"Combinations"); after filling the collection because Combinations already implements INotifyPropertyChanged.

Update

To detect that your ObservableCollection was changed, subscribe to CollectionChanged event in your UserControl code behind. Make sure that you subscribed before collection changed!

Combinations.CollectionChanged += (s, e) =>
{
    if (cbCombination.Items.Count > 0)
        cbCombination.SelectedIndex = 0;
};

Another suggestion

Approach above is works when you do not need a smarter logic than just select zero index in combobox. But often you will need a more complex logic to select some item. In this case you may add new property to your model:

public Combination SelectedCombination
{
    get{ return _selectedCombination; }
    set
    {
        _selectedCombination = value;
        NotifyPropertyChanged("SelectedCombination");
    }
}

and bind this property to your combobox:

<ComboBox Name="cbCombination" ItemsSource="{Binding Combinations}" 
          SelectedIndex="0" SelectedItem={Bindings SelectedCombination} />

In this case you can select any item when filling the Combinations collection and it will be automatically selected in combobox:

var combinationsList = CombinationsManager.CombinationsFor(someParam);
this.Combinations.Clear();
foreach (var combination in combinationsList)
    this.Combinations.Add(combination);

if (Combinations.Count > 0)
    SelectedCombination = Combinations[0];