刷新ListCollectionView将ComboBox中所选项的值设置为null

时间:2013-04-28 06:12:10

标签: c# .net wpf mvvm listcollectionview

我有一个ListBox和两个ComboBox的视图。当我在ListBox中选择项目时,ComboBox es的内容/值会根据所选项目的属性值进行刷新。在我的方案中,ListBox包含一个客户列表,第一个ComboBox包含一个国家/地区列表。所选项目是客户的原产国。第二个ComboBox包含城市列表。选定的城市是客户的起源城市。

第二个ItemsSource的{​​{1}}属性绑定到ComboBox,基于使用过滤器的所有城市的ListViewCollection。当国家/地区ObservableCollection中的选择发生更改时,我会刷新过滤器以仅显示属于所选国家/地区的城市。

我们假设客户A来自新西兰奥克兰,客户B来自加拿大多伦多。当我选择A时,一切正常。第二个ListBox仅包含新西兰城市,并选择了奥克兰。现在我选择B,所选国家现在是加拿大,城市列表只包含加拿大城市,多伦多被选中。如果现在我回到A,新西兰在这些国家/地区被选中,则城市列表仅包含来自新西兰的城市,但未选择奥克兰。

当我调试这个场景时,我注意到当我选择B时,对ComboBox的调用将客户端A上城市的值设置为最初选择为ListCollectionView.Refresh()(在通话时设置断点)刷新和模型上的城市设置器上的另一个,请参阅下面的代码。

猜测 - 虽然我不是百分百肯定 - 它发生了,因为我对null城市TwoWaySelectedItem约束当过滤器将列表更新到加拿大城市时,奥克兰消失,此信息将被发送回属性,然后更新为ComboBox。在某种程度上,这是有道理的。

我的问题是:我怎样才能避免这种情况发生?当null刷新时,如何防止模型上的属性更新?

下面是我的代码(尽管我尝试使其尽可能少的代码使问题可重现,但它有点长):

ItemsSource

现在这是XAML:

                                                

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
}

如果有任何帮助,这里也是我的自定义 <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

应该可以通过简单地复制/粘贴所有上述代码来重现该问题。我正在使用.NET 4.0。

最后,我阅读了this article(和其他一些人),并试图将给定的建议适用于我的案例,但没有取得任何成功。我想我做错了事:

我也读过this question但是如果我的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; } 变得越来越大,我可能最终必须明确地跟踪数百个项目,如果可能的话我不想这样做。

1 个答案:

答案 0 :(得分:2)

你有一点冗余模型。您有国家/地区列表,每个国家/地区都有城市列表。然后组成整个城市列表,在选择更改时更新。如果您要更改城市 ComboBox 的数据来源,您将获得所需的行为:

    <ComboBox Grid.Column="1" Grid.Row="1" SelectedItem="{Binding City}"
              ItemsSource="{Binding Country.Cities}">
        <ComboBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}"/>
            </DataTemplate>
        </ComboBox.ItemTemplate>
    </ComboBox>

您可以正确猜测城市设置为 null 的原因。

但是如果你想保持你上面描述的模型 - 你应该改变方法调用的顺序。为此,您应该使用Application.Current.Dispatcher属性(并且您无需更改上面提到的 ComboBox ):

private void OnCountryChanged()
{
    var uiDispatcher = System.Windows.Application.Current.Dispatcher;
    uiDispatcher.BeginInvoke(new Action(this.CitiesView.Refresh));
}