WPF standard ComboBox Items Source Change Issue

Today I encountered an issue with the WPF standard CombBox where if the bound ItemsSource (collection) of a ComboBox has changed the binding on the SelectedItem of the ComboBox will fail to update to the already properly set view model property.

For example if the ItemsSource is bound to the following property of the view model which is common and presumably the best practice: 

1 public ObservableCollection<string> Items { get; private set; }

And the SelectedItem is bound to

1 public string SelectedItem
2 {
3     get { return SelectedModel.SelectedItem; }
4     set
5     { 
6         SelectedModel.SelectedItem = value;
7         OnPropertyChanged();
8     }
9 }

And the SelectedModel can be changed this way:

 1 public Model SelectedModel
 2 {
 3     get { return _selectedModel; }
 4     set
 5     {
 6         if (_selectedModel != value)
 7         {
 8             _selectedModel = value;
 9 
10             Items.Clear();
11             foreach (var item in _selectedModel.Items)
12             {
13                 Items.Add(item);
14             }
15 
16             OnPropertyChanged("SelectedItem");
17         }
18     }
19 }

where each SelectedItem on SelectedModel has already been properly set up.

One might suppose it would work. However it doesn't.

The reason is kind of explained in this article,

http://stackoverflow.com/questions/5242275/combobox-itemssource-changed-selecteditem-is-ruined

As I've done a few experiments, I have feeling that the (initial) binding update is lost due to the fact that the framework modifies the SelectedItem property and holds certain assumptions as to the modification during the collection (Items) change and neglects property changed notification afterwards if things went against its assumption (no matter when it happens, and only not when the property actually changes from its value as of the end of the collection process).

Given the above speculation, I continued playing with the code and eventually come up with a workable solution like below (complete source code).

View model:

 1 using System.Collections.ObjectModel;
 2 using System.ComponentModel;
 3 using System.Runtime.CompilerServices;
 4 using ComboBoxItemsSourceChangeTest.Annotations;
 5 
 6 namespace ComboBoxItemsSourceChangeTest
 7 {
 8     /// <summary>
 9     ///  The view model of the main UI
10     /// </summary>
11     /// <remarks>
12     ///  http://stackoverflow.com/questions/5242275/combobox-itemssource-changed-selecteditem-is-ruined
13     /// </remarks>
14     public class MainViewModel : INotifyPropertyChanged
15     {
16         private Model _selectedModel;
17 
18         private bool _isComboTransitioning;
19         
20         public MainViewModel()
21         {
22             Items = new ObservableCollection<string>();
23         }
24 
25         public Model SelectedModel
26         {
27             get { return _selectedModel; }
28             set
29             {
30                 if (_selectedModel != value)
31                 {
32                     _selectedModel = value;
33 
34                     _isComboTransitioning = true;
35                     Items.Clear();
36                     foreach (var item in _selectedModel.Items)
37                     {
38                         Items.Add(item);
39                     }
40                     _isComboTransitioning = false;
41 
42                     OnPropertyChanged("SelectedItem");
43                 }
44             }
45         }
46 
47         public ObservableCollection<string> Items { get; private set; }
48 
49         public string SelectedItem
50         {
51             get
52             {
53                 return _isComboTransitioning ? null: SelectedModel.SelectedItem;
54             }
55             set
56             {
57                 if (SelectedModel.SelectedItem != value && !_isComboTransitioning)
58                 {
59                     SelectedModel.SelectedItem = value;
60                     OnPropertyChanged();
61                 }
62             }
63         }
64 
65         public event PropertyChangedEventHandler PropertyChanged;
66 
67         [NotifyPropertyChangedInvocator]
68         protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
69         {
70             var handler = PropertyChanged;
71             if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
72         }
73 
74         public void RaisePropertyChanged(string propertyName)
75         {
76             OnPropertyChanged(propertyName);
77         }
78     }
79 }


Model:

1 namespace ComboBoxItemsSourceChangeTest
2 {
3     public class Model
4     {
5         public string[] Items { get; set; }
6 
7         public string SelectedItem { get; set; }
8     }
9 }

 

View:

 1 <Window x:Class="ComboBoxItemsSourceChangeTest.MainWindow"
 2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4         Title="MainWindow" Height="350" Width="525">
 5     <Grid>
 6         <StackPanel>
 7             <ComboBox Margin="10" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}"
 8                       Name="ComboBox1">
 9                 <ComboBox.ItemTemplate>
10                     <DataTemplate>
11                         <TextBlock Text="{Binding}"></TextBlock>
12                     </DataTemplate>
13                 </ComboBox.ItemTemplate>
14             </ComboBox>
15             <Button Margin="10" Name="Button1" Click="Button1_Click">Data Source 1</Button>
16             <Button Margin="10" Name="Button2" Click="Button2_OnClick">Data Source 2</Button>
17             <Button Margin="10" Name="Button3" Click="Button3_OnClick">Data Source 3</Button>
18             <Button Margin="10" Name="ButtonRandomSelect" Click="ButtonRandomSelect_OnClick">Random Select</Button>
19         </StackPanel>
20     </Grid>
21 </Window>

 

posted @ 2015-07-16 13:47  quanben  阅读(388)  评论(0编辑  收藏  举报