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

本文关键字:设置 null 选项 ListCollectionView ComboBox 刷新 | 更新日期: 2023-09-27 18:11:07

我有一个ListBox和两个ComboBox的视图。当我在ListBox中选择一个项目时,根据所选项目的属性值刷新ComboBox es的内容/值。在我的场景中,ListBox保存一个客户列表,第一个ComboBox保存一个国家列表。选中的项是客户的原产国。第二个ComboBox保存城市列表。所选城市为客户端的原籍城市。

第二个ComboBoxItemsSource属性基于所有城市的ObservableCollection绑定到ListViewCollection。当国家ListBox中的选择发生变化时,我刷新过滤器以只显示属于所选国家的城市。

假设客户A来自新西兰奥克兰,客户B来自加拿大多伦多。当我选择A时,一切正常。第二个ComboBox只包含新西兰的城市,奥克兰被选中。现在我选择B,选择的国家现在是加拿大,城市列表中只包含加拿大城市,选择多伦多。如果我现在回到A,在国家中选择了新西兰,城市列表中只有新西兰的城市,没有选择奥克兰。

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

-虽然我不是100%确定-它正在发生,因为我在ComboBoxSelectedItem上有一个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。

最后,我阅读了这篇文章(以及其他一些文章),并试图将给出的建议应用到我的案例中,但没有成功。我想我做错了:

我也读了这个问题,但是如果我的ListBox变大了,我可能最终不得不明确地跟踪数百个项目,如果可能的话,我不想这样做。

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

您的模型有点冗余。你有国家列表,每个国家都有城市列表。然后组成城市的整体列表,当选择发生变化时更新。如果您将更改城市组合框的数据源,您将得到所需的行为:

    <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));
}