我有一個ListBox
和兩個ComboBox
es的視圖。當我在ListBox
中選擇一個項目時,將根據所選項目的屬性值刷新ComboBox
es的內容/值。在我的場景中,ListBox
包含一個客戶列表,第一個ComboBox
包含一個國家列表。所選項目是客戶的原籍國。第二個ComboBox
擁有一個城市列表。選定的城市是客戶的來源城市。刷新ListCollectionView將ComboBox中所選項目的值設置爲空
第二ComboBox
的ItemsSource
屬性基於的ObservableCollection
所有使用濾波器城市綁定到ListViewCollection
。當國家ListBox
中的選擇發生變化時,我刷新過濾器以僅顯示屬於所選國家/地區的城市。
我們假設客戶A來自新西蘭的奧克蘭,客戶B來自加拿大多倫多。當我選擇A時,一切正常。第二個ComboBox
只填充新西蘭城市,並選擇奧克蘭。現在我選擇B,選定的國家現在是加拿大,城市名單隻包含加拿大城市,選擇多倫多。如果現在我回到A,在這些國家選擇新西蘭,城市名單隻包含來自新西蘭的城市,但奧克蘭沒有被選中。
當我調試這種情況下,我注意到,當我選擇B,調用ListCollectionView.Refresh()
設置城最初選擇null
客戶A的值(把在調用斷點刷新,並在另一個在模型上的城市二傳手,見下面的代碼)。
我猜 - 雖然我不是100%肯定 - 它正在發生的事情,因爲我對這個城市ComboBox
的SelectedItem
當過濾器更新列表的加拿大城市,奧克蘭消失和TwoWay
結合這些信息將被髮送回該物業,然後更新至null
。從某種意義上說,這是有道理的。
我的問題是:我該如何避免這種情況發生?如何防止ItemsSource
僅更新時更新我的型號的屬性?
下面是我的代碼(這是一個有點長,雖然我試圖使它的代碼,使問題重現的可能的最小量):
public class Country
{
public string Name { get; set; }
public IEnumerable<City> Cities { get; set; }
}
public class City
{
public string Name { get; set; }
public Country Country { get; set; }
}
public class ClientModel : NotifyPropertyChanged
{
#region Fields
private string name;
private Country country;
private City city;
#endregion
#region Properties
public string Name
{
get
{
return this.name;
}
set
{
this.name = value;
this.OnPropertyChange("Name");
}
}
public Country Country
{
get
{
return this.country;
}
set
{
this.country = value;
this.OnPropertyChange("Country");
}
}
public City City
{
get
{
return this.city;
}
set
{
this.city = value;
this.OnPropertyChange("City");
}
}
#endregion
}
public class ViewModel : NotifyPropertyChanged
{
#region Fields
private ObservableCollection<ClientModel> models;
private ObservableCollection<Country> countries;
private ObservableCollection<City> cities;
private ListCollectionView citiesView;
private ClientModel selectedClient;
#endregion
#region Constructors
public ViewModel(IEnumerable<ClientModel> models, IEnumerable<Country> countries, IEnumerable<City> cities)
{
this.Models = new ObservableCollection<ClientModel>(models);
this.Countries = new ObservableCollection<Country>(countries);
this.Cities = new ObservableCollection<City>(cities);
this.citiesView = (ListCollectionView)CollectionViewSource.GetDefaultView(this.cities);
this.citiesView.Filter = city => ((City)city).Country.Name == (this.SelectedClient != null ? this.SelectedClient.Country.Name : string.Empty);
this.CountryChangedCommand = new DelegateCommand(this.OnCountryChanged);
}
#endregion
#region Properties
public ObservableCollection<ClientModel> Models
{
get
{
return this.models;
}
set
{
this.models = value;
this.OnPropertyChange("Models");
}
}
public ObservableCollection<Country> Countries
{
get
{
return this.countries;
}
set
{
this.countries = value;
this.OnPropertyChange("Countries");
}
}
public ObservableCollection<City> Cities
{
get
{
return this.cities;
}
set
{
this.cities = value;
this.OnPropertyChange("Cities");
}
}
public ListCollectionView CitiesView
{
get
{
return this.citiesView;
}
}
public ClientModel SelectedClient
{
get
{
return this.selectedClient;
}
set
{
this.selectedClient = value;
this.OnPropertyChange("SelectedClient");
}
}
public ICommand CountryChangedCommand { get; private set; }
#endregion
#region Methods
private void OnCountryChanged(object obj)
{
this.CitiesView.Refresh();
}
#endregion
}
現在,這裏的XAML:
<Grid Grid.Column="0" DataContext="{Binding SelectedClient}">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="Country"/>
<local:ComboBox Grid.Column="1" Grid.Row="0" SelectedItem="{Binding Country}"
Command="{Binding DataContext.CountryChangedCommand, RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"
ItemsSource="{Binding DataContext.Countries, RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}">
<local:ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</local:ComboBox.ItemTemplate>
</local:ComboBox>
<TextBlock Grid.Column="0" Grid.Row="1" Text="City"/>
<ComboBox Grid.Column="1" Grid.Row="1" SelectedItem="{Binding City}"
ItemsSource="{Binding DataContext.CitiesView, RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<ListBox Grid.Column="1" ItemsSource="{Binding Models}" SelectedItem="{Binding SelectedClient}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
如果有任何幫助,這裏還有我自定義的ComboBox
的代碼來處理國家選擇變化的通知。
public class ComboBox : System.Windows.Controls.ComboBox, ICommandSource
{
#region Fields
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(ComboBox));
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(ComboBox));
public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register(
"CommandTarget",
typeof(IInputElement),
typeof(ComboBox));
#endregion
#region Properties
public ICommand Command
{
get { return (ICommand)this.GetValue(CommandProperty); }
set { this.SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return this.GetValue(CommandParameterProperty); }
set { this.SetValue(CommandParameterProperty, value); }
}
public IInputElement CommandTarget
{
get { return (IInputElement)this.GetValue(CommandTargetProperty); }
set { this.SetValue(CommandTargetProperty, value); }
}
#endregion
#region Methods
protected override void OnSelectionChanged(System.Windows.Controls.SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
var command = this.Command;
var parameter = this.CommandParameter;
var target = this.CommandTarget;
var routedCommand = command as RoutedCommand;
if (routedCommand != null && routedCommand.CanExecute(parameter, target))
{
routedCommand.Execute(parameter, target);
}
else if (command != null && command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
#endregion
}
對於這個簡單的例子,我創建並填充視圖模型在我Window
的構造,在這裏:
public MainWindow()
{
InitializeComponent();
Country canada = new Country() { Name = "Canada" };
Country germany = new Country() { Name = "Germany" };
Country vietnam = new Country() { Name = "Vietnam" };
Country newZealand = new Country() { Name = "New Zealand" };
List<City> canadianCities = new List<City>
{
new City { Country = canada, Name = "Montréal" },
new City { Country = canada, Name = "Toronto" },
new City { Country = canada, Name = "Vancouver" }
};
canada.Cities = canadianCities;
List<City> germanCities = new List<City>
{
new City { Country = germany, Name = "Frankfurt" },
new City { Country = germany, Name = "Hamburg" },
new City { Country = germany, Name = "Düsseldorf" }
};
germany.Cities = germanCities;
List<City> vietnameseCities = new List<City>
{
new City { Country = vietnam, Name = "Ho Chi Minh City" },
new City { Country = vietnam, Name = "Da Nang" },
new City { Country = vietnam, Name = "Hue" }
};
vietnam.Cities = vietnameseCities;
List<City> newZealandCities = new List<City>
{
new City { Country = newZealand, Name = "Auckland" },
new City { Country = newZealand, Name = "Christchurch" },
new City { Country = newZealand, Name = "Invercargill" }
};
newZealand.Cities = newZealandCities;
ObservableCollection<ClientModel> models = new ObservableCollection<ClientModel>
{
new ClientModel { Name = "Bob", Country = newZealand, City = newZealandCities[0] },
new ClientModel { Name = "John", Country = canada, City = canadianCities[1] }
};
List<Country> countries = new List<Country>
{
canada, newZealand, vietnam, germany
};
List<City> cities = new List<City>();
cities.AddRange(canadianCities);
cities.AddRange(germanCities);
cities.AddRange(vietnameseCities);
cities.AddRange(newZealandCities);
ViewModel vm = new ViewModel(models, countries, cities);
this.DataContext = vm;
}
應該可以重現該問題通過簡單的複製/粘貼所有的上面的代碼。我正在使用.NET 4.0。
最後,我讀this article(和其他一些),並試圖修改/應用給出的建議,我的情況,但沒有任何成功。我想我做錯了事:
我也讀this question但如果我的ListBox
增長很大,我可能最終不得不跟蹤數百個項目,如果可能的話我不想做。
我想這兩種解決方案和他們的工作。我會選擇第一個,它會減少代碼,更易於理解和維護。但是,我真的很想知道如何使用UI調度程序來解決問題?我沒有調試代碼,看看發生了什麼,但據我所知,一切都已經在UI線程上運行,我無法弄清楚爲什麼使用UI調度程序可以以某種方式提供幫助... – Guillaume 2013-04-28 15:08:45
方法BeginInvoke()的UI調度程序將在UI線程上調度方法調用,因此當UI線程可以自由執行某些操作時,它將執行您指定的操作。因此,這裏將首先選擇SelectedClient,並且在更改SelectedClient之後,將應用CitiesView的過濾器。 – stukselbax 2013-04-28 17:51:30
好吧,明白了。謝謝你的幫助! – Guillaume 2013-04-29 01:24:56