CollectionViewSource未使用PropertyChanged更新

时间:2016-10-08 03:46:23

标签: c# wpf xaml datagrid datagridcomboboxcolumn

我在数据网格中遇到了很多关于ComboBoxes的麻烦。 真的想要一些帮助,我想我已经对研究的数量和我尝试的事情感到困惑。这真的应该很简单,所以我必须遗漏一些东西。

简化问题

我在xaml中使用CollectionViewSource,C#将CollectionViewSource的源设置为Page的datacontext类中的ObservableCollection。 向集合中添加项不会更新包含显示视图源的DataGridComboBox列。 有关详细信息,请参见下面的行

概览

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

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

我可以通过将其绑定到单独的集合视图源来获取位置组合框的唯一方法。

问题

似乎如果页面加载事件发生在ViewModel填充ObservableCollection之前,那么locationVwSrc将为空,并且属性更改事件不会使其更改。

实施短版 Page有一个在xaml中定义的集合viewSource。

Loaded="Page_Loaded"
  Title="EquipRegPage">
<Page.Resources>
    <CollectionViewSource x:Key="locationsVwSrc"/>
</Page.Resources>

datagrid使用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");
    }
}

2 个答案:

答案 0 :(得分:3)

看起来你已经无法将问题分解成足够小的问题了。问题似乎是Datagrid中的ComboBox混合,异步设置CollectionViewSource源,从数据库加载数据。 我建议考虑

是有益的
  1. 使用最少的移动部件(即XAML文件和带有预制数据的ViewModel)重新创建问题(或灵魂)。

  2. 或解耦现有代码。看来,Page显式地了解了您的ViewModel(EquipRegVm viewModel = new EquipRegVm();),并且ViewModel明确知道数据库以及如何加载自身。哦,快照,现在我们的视图耦合到您的数据库?像MVVM这样的模式点是不是我们没有耦合?

  3. 接下来,我看一些代码并看一些(我称之为)反模式。

    • 可设置的集合属性
    • 页面背后的代码(所有代码都可以存在于XAML中)

    但我认为,基本上如果你只是在3个地方改变你的代码就应该没问题。

    更改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}" />
    

答案 1 :(得分:1)

设置如下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);

这就是你所需要的一切。