CollectionViewSource没有更新属性改变

本文关键字:属性 改变 更新 CollectionViewSource | 更新日期: 2023-09-27 18:12:03

我在使用数据网格中的组合框时遇到了很多麻烦。我真的需要一些帮助,我想我已经被大量的研究和我尝试过的东西弄糊涂了。这真的应该很简单,所以我一定错过了什么。

<<p> 简化问题/strong>

我在xaml中使用CollectionViewSource, c#将该CollectionViewSource的源设置为一个ObservableCollection类,该类是页面的数据上下文。向集合中添加项不会更新包含显示视图源的DataGridComboBox列。查看下面的行


我有一个WPF页面上的数据网格。该页将其数据上下文设置为视图模型。viewModel包含两个可观察集合。一个用于设备,一个用于位置。每个装备都有一个位置。这些是由代码优先的EF数据库填充的,但我相信这个问题超出了这个级别。

数据网格是每个装备一行。位置列需要是一个可选择的组合框,允许用户更改位置。

唯一可以让位置组合框填充的方法是将它绑定到一个单独的集合视图源。

似乎,如果页面加载事件发生在ViewModel填充ObservableCollection之前,那么locationVwSrc将是空的,属性改变的事件不会让这个改变。

实现简版页面有一个在xaml.

中定义的集合viewSource。
Loaded="Page_Loaded"
  Title="EquipRegPage">
<Page.Resources>
    <CollectionViewSource x:Key="locationsVwSrc"/>
</Page.Resources>

数据网格是用xaml定义的。

<DataGrid x:Name="equipsDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected" Margin="10,10,-118,59" 
              ItemsSource="{Binding Equips}" EnableRowVirtualization="True" AutoGenerateColumns="False">

在xaml

中定义的组合框列
<DataGridComboBoxColumn x:Name="locationColumn" Width="Auto" MaxWidth="200" Header="Location"
                                    ItemsSource="{Binding Source={StaticResource locationsVwSrc}, UpdateSourceTrigger=PropertyChanged}"
                                    DisplayMemberPath="Name"
                                    SelectedValueBinding="{Binding Location}"

设置为视图模型的页面上下文

public partial class EquipRegPage : Page
{
    EquipRegVm viewModel = new EquipRegVm();
    public EquipRegPage()
    {
        InitializeComponent();
        this.DataContext = viewModel;
    }

加载事件设置上下文

private void Page_Loaded(object sender, RoutedEventArgs e)
    {
        // Locations View Source
        System.Windows.Data.CollectionViewSource locationViewSource =
            ((System.Windows.Data.CollectionViewSource)(this.FindResource("locationsVwSrc")));
        locationViewSource.Source = viewModel.Locations;
        // Above does not work if the viewmodel populates these after this call, only works if its populated prior.
        //TODO inotifypropertychanged not correct? This occurs before the viewmodels loads, and doesn't display.
        // Therefore notify property changes aren't working.
        // Using this as cheat instead instead works, i beleive due to this only setting the source when its full
        //viewModel.Db.Locations.Load();
        //locationViewSource.Source = viewModel.Db.Locations.Local;
        //locationViewSource.View.Refresh();
    }

ViewModel类和它如何加载

public class EquipRegVm : DbWrap, INotifyPropertyChanged
{
    /// <summary>
    /// Event triggered by changes to properties. This notifys the WPF UI above which then 
    /// makes a binding to the UI.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
    /// <summary>
    /// Notify Property Changed Event Trigger
    /// </summary>
    /// <param name="propertyName">Name of the property changed. Must match the binding path of the XAML.</param>
    void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    public ObservableCollection<Equip> Equips { get; set; }
    public ObservableCollection<Location> Locations { get; set; }
    public EquipRegVm() : base()
    {
        Load();
    }
    /// <summary>
    /// Load the data from the Model.
    /// </summary>
    public async void Load()    //TODO async an issue?
    {
        // EQUIPMENT
        ObservableCollection<Equip> eqList = new ObservableCollection<Equip>();
        var eqs = await (from eq in Db.Equips
                        orderby eq.Tag
                        select eq).ToListAsync();
        foreach(var eq in eqs)
        {
            eqList.Add(eq);
        }
        Equips = eqList;
        RaisePropertyChanged("Equips");

        // LOCATIONS
        ObservableCollection<Location> locList = new ObservableCollection<Location>();
        var locs = await (from l in Db.Locations
                         orderby l.Name
                         select l).ToListAsync();
        foreach (var l in locs)
        {
            locList.Add(l);
        }
        Locations = locList;
        RaisePropertyChanged("Locations");
    }
}

CollectionViewSource没有更新属性改变

看来你还没能把问题分解成足够小的问题。问题似乎是数据网格中的组合框,异步设置CollectionViewSource源,从数据库加载数据的混合。我建议你可以考虑

  1. 用最小的移动部件重现问题(或解决方案),即一个XAML文件和一个带有预保存数据的ViewModel。

  2. 或解耦现有代码。看起来Page明确地知道你的ViewModel (EquipRegVm viewModel = new EquipRegVm();),而你的ViewModel明确地知道数据库和如何加载自己。现在我们的视图和数据库耦合了?这难道不是像MVVM这样的模式的意义吗?

接下来我看一些代码,看到更多(我称之为)反模式。

  • 可设置的收集属性
  • 页面后面的代码(都可以保存在XAML中)

但我认为基本上如果你只是改变了你的代码在三个地方,你应该没事。

改变1

    /*foreach(var eq in eqs)
    {
        eqList.Add(eq);
    }
    Equips = eqList;
    RaisePropertyChanged("Equips");*/
    foreach(var eq in eqs)
    {
        Equips.Add(eq);
    }

改变2

    /*foreach (var l in locs)
    {
        locList.Add(l);
    }
    Locations = locList;
    RaisePropertyChanged("Locations");*/
    foreach (var l in locs)
    {
        Locations.Add(l);
    }

改变3

要么删除CollectionViewSource的用法(它提供了什么?),要么使用绑定来设置源。由于您目前正在手动设置Source(即locationViewSource.Source = viewModel.Locations;),因此您已经选择在引发PropertyChanged事件时不更新该值。

所以如果你只是删除CollectionViewSource,那么你只需要绑定到Locations属性。如果你决定保留CollectionViewSource,那么我建议删除页面代码并将XAML更改为

<CollectionViewSource x:Key="locationsVwSrc" Source="{Binding Locations}" />

设置Binding:

System.Windows.Data.CollectionViewSource locationViewSource =
           ((System.Windows.Data.CollectionViewSource)(this.FindResource("locationsVwSrc")));
// locationViewSource.Source = viewModel.Locations;
Binding b = new Binding("Locations");
b.Source = viewModel;
b.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(locationViewSource, CollectionViewSource.SourceProperty, b);