ObservableCollection CollectionChanged 更新数据网格

Posted

技术标签:

【中文标题】ObservableCollection CollectionChanged 更新数据网格【英文标题】:ObservableCollection CollectionChanged to update datagrid 【发布时间】:2018-07-05 23:29:08 【问题描述】:

我有一个DataGrid,它绑定到ObservableCollection。我有一个动态数据列表,因此正在从列表中编辑/添加/删除项目。 起初,我正在清除并添加ObservableCollection,但后来我发现我可以刷新ObservableCollection,我需要为此实现CollectionChanged,但我不确定是否有任何机构可以提供一些指示或很棒的示例代码。

    private List<OrderList> m_OrderListData = new List<OrderList>();
    public List<OrderList> OrderListData
    
        get => m_OrderListData;
        private set => Set(ref m_OrderListData, value);
    

    private ObservableCollection<OrderList> m_OrderListDataCollection;
    public ObservableCollection<OrderList> OrderListDataCollection
    
        get => m_OrderListDataCollection;
        private set => Set(ref m_OrderListDataCollection, value);
    

    ...
    ...

    m_OrderListDataCollection = new ObservableCollection<OrderList>(m_OrderListData as List<OrderList>);

    ...
    ...


    foreach (OrderListViewModel Order in OrderList)
    
        OrderListData.Add(new OrderList(Order.Description, Order.OrderId));
    

这是我之前的经历

OrderListData.Clear();
foreach (OrderListViewModel Order in OrderList)

      OrderListData.Add(new OrderList(Order.Description, Order.OrderId));

m_OrderListDataCollection.Clear();
OrderListData.ToList().ForEach(m_OrderListDataCollection.Add);

XAML

                <Label Content="OrderList"/>
                <DataGrid Name="dgOrderList" 
                          AutoGenerateColumns="False" 
                          ItemsSource="Binding Path=OrderListDataCollection" 
                          IsReadOnly="True"
                          SelectionMode="Single"
                          SelectionUnit="FullRow">
                    <DataGrid.Columns>
                        <DataGridTextColumn Width="Auto" Header="ID" Binding="Binding OrderId"/>
                        <DataGridTextColumn Width="*" Header="Description" Binding="Binding OrderDescription"/>
                    </DataGrid.Columns>
                </DataGrid>

编辑: 订单列表类

public class OrderList : INotifyPropertyChanged


    private string m_OrderDescription;
    private string m_OrderId;

    public string OrderDescription
    
        get => m_OrderDescription;
        set => Set(ref m_OrderDescription, value);
    

    public string OrderId
    
        get => m_OrderId;
        set => Set(ref m_OrderId, value);
    

    #region Constructor
    public OrderList()
    
    
    public OrderList(string description, string id)
    
        m_OrderDescription = description;
        m_OrderId = id;
    
    #endregion

    #region INotifyPropertyChanged

    /// <summary>Updates the property and raises the changed event, but only if the new value does not equal the old value. </summary>
    /// <param name="PropName">The property name as lambda. </param>
    /// <param name="OldVal">A reference to the backing field of the property. </param>
    /// <param name="NewVal">The new value. </param>
    /// <returns>True if the property has changed. </returns>
    public bool Set<U>(ref U OldVal, U NewVal, [CallerMemberName] string PropName = null)
    
        VerifyPropertyName(PropName);
        return Set(PropName, ref OldVal, NewVal);
    

    /// <summary>Updates the property and raises the changed event, but only if the new value does not equal the old value. </summary>
    /// <param name="PropName">The property name as lambda. </param>
    /// <param name="OldVal">A reference to the backing field of the property. </param>
    /// <param name="NewVal">The new value. </param>
    /// <returns>True if the property has changed. </returns>
    public virtual bool Set<U>(string PropName, ref U OldVal, U NewVal)
    
        if (Equals(OldVal, NewVal))
        
            return false;
        

        OldVal = NewVal;
        RaisePropertyChanged(new PropertyChangedEventArgs(PropName));
        return true;
    

    /// <summary>Raises the property changed event. </summary>
    /// <param name="e">The arguments. </param>
    protected virtual void RaisePropertyChanged(PropertyChangedEventArgs e)
    
        var Copy = PropertyChanged;
        Copy?.Invoke(this, e);
    

    /// <summary>
    /// Raised when a property on this object has a new value.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Warns the developer if this object does not have
    /// a public property with the specified name. This
    /// method does not exist in a Release build.
    /// </summary>
    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    protected virtual void VerifyPropertyName(string PropertyName)
    
        // Verify that the property name matches a real,
        // public, instance property on this object.
        if (TypeDescriptor.GetProperties(this)[PropertyName] == null)
        
            string ErrorMsg = "Invalid Property Name: " + PropertyName + "!";

            if (ThrowOnInvalidPropertyName)
            
                throw new Exception(ErrorMsg);
            

            Debug.Fail(ErrorMsg);
        
    

    /// <summary>
    /// Returns whether an exception is thrown, or if a Debug.Fail() is used
    /// when an invalid property name is passed to the VerifyPropertyName method.
    /// The default value is false, but subclasses used by unit tests might
    /// override this property's getter to return true.
    /// </summary>
    protected virtual bool ThrowOnInvalidPropertyName  get;  = true;

【问题讨论】:

如果您只是将ObservableCollection 绑定到SourceDataGrid,那么它应该可以按预期工作。每当添加新项目或删除项目时,您的视图将收到通知以更新其数据。要跟踪实际更改,您不需要需要实现列表的 CollectionChanged 事件,但您必须使列表中的实际对象可观察。一旦对象是可观察的,并且一个属性发送了一个PropertyChangednotification,可观察的集合就会捕捉到这个。 它绑定到数据网格,但通过实现,数据网格中没有填充任何内容。我至少之前的实现确实可以填充数据并更新它,但我想避免清除集合,因为我无法在数据网格中选择一个项目。 你能发布你的 OderList 类吗?它应该这样声明: public class OrderList : INotifyPropertyChanged 并包含一个私有的 void NotifyPropertyChanged 方法(如下面 Daniele 的回答所示)。然后在 ObservableCollection 中使用该类,您将被覆盖。 @PaulGibson 查看帖子编辑 【参考方案1】:

您应该直接添加到绑定集合中。添加到OrderListData 不会影响您绑定的那个:

OrderListDataCollection.Add(new OrderList(Order.Description, Order.OrderId));

老实说,另一个似乎毫无价值,至少在它影响你的绑定控制方面。它所做的只是初始化ObservableCollection。它不能作为数据的持续来源。

【讨论】:

是的,我以前做过,但列表数据不断变化,代码重复运行。这是我以前所拥有的,但我想也许有更好的方法来做到这一点。看到帖子,我已经编辑过我之前做过的事情。 您当前的方式要求您每次重新加载 OrderListData 时至少将 ObservableCollection 设置为一个新实例。【参考方案2】:

你需要首先在你的 OrderList 类中实现 INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    // This method is called by the Set accessor of each property.
    // The CallerMemberName attribute that is applied to the optional propertyName
    // parameter causes the property name of the caller to be substituted as an argument.
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    
        if (PropertyChanged != null)
        
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        
    

那么您可以直接使用该集合。如果您想制作自定义事件,则使用 CollectionChangedEvent,但默认情况下,ObservableCollection 已经通知 UI 其项目数量的更改。您只需要通知 UI 单个项目的更改

编辑:将集合用作视图模型中的属性,该视图模型也实现了 INotifyPropertyChanged

private ObservableCollection<MyItem> _myCollection = new ObservableCollection<MyItem>();

public ObservableCollection<MyItem> MyCollection

    get return _myCollection;
    set  
        
           _myCollection = value;
           OnPropertyChanged("MyCollection");
        

【讨论】:

是的,我已经完成了。我只是想避免清除和添加到集合中。有没有办法做到这一点。 检查编辑。您无需清除并重新添加。只需做你的事情(添加、删除、编辑)并将其用作属性【参考方案3】:

如果您只是将ObservableCollection 绑定到SourceDataGrid,那么它应该可以按预期工作。 每当添加新项目或删除项目时,您的视图将收到通知以更新其数据。

要跟踪实际更改,您不需要实现列表的 CollectionChanged 事件,但您必须使列表中的实际对象可观察。要使对象可观察,您必须实现INotifyPropertyChangedinterface。 一旦对象是可观察的,并且属性发送了一个PropertyChanged通知,可观察的集合就会捕捉到这个。

这里有一些快速示例代码可以帮助您入门:

1。为可观察对象制作自己的实现

public class ObservableObject : INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propName = null)
    
        var handler = PropertyChanged;
        if (handler != null)
        
            handler(this, new PropertyChangedEventArgs(propName));
        
    

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    
        if (Equals(storage, value)) return false;
        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    

2。让您的实际对象类扩展此实现,并确保必要的属性发出 PropertyChanged 通知

public class Order : ObservableObject 

    private long _orderId;
    public long OrderId
    
        get  return _orderId; 
        set  SetProperty(ref _orderId, value); 
     

    private string _description;
    public string Description
    
        get  return _description; 
        set  SetProperty(ref _description, value); 
     

3。确保您的 ViewModel 具有 ObservableCollection

(ViewModel 也应该是可观察的。我希望你已经是这样,否则很难让 MVVM 工作。)

public class MyViewModel : ObservableObject 


    public MyViewModel()
    
        //this is just an example of some test data:
        var myData = new List<Order> 
            new Order  OrderId = 1, Description = "Test1",
            new Order  OrderId = 2, Description = "Test2",
            new Order  OrderId = 3, Description = "Test3"
        ;
        //Now add the data to the collection:
        OrderList = new ObservableCollection<Order>(myData);

    
    private ObservableCollection<Order> _orderList;
    public ObservableCollection<Order> OrderList
    
        get  return _orderList; 
        set  SetProperty(ref _orderList, value); 
     

4。将 DataGrid 的源绑定到 ObservableCollection

<DataGrid Name="dgOrderList" 
        AutoGenerateColumns="False" 
        ItemsSource="Binding OrderList, Mode=TwoWay" 
        IsReadOnly="True"
        SelectionMode="Single"
        SelectionUnit="FullRow">
    <DataGrid.Columns>
        <DataGridTextColumn Width="Auto" Header="ID" Binding="Binding OrderId"/>
        <DataGridTextColumn Width="*" Header="Description" Binding="Binding OrderDescription"/>
    </DataGrid.Columns>
</DataGrid>

【讨论】:

这是一个非常好的答案。您能否详细说明 ObservableObject 中的 SetProperty 方法?我从来没有对这种Object进行模板化,我通常直接实现INotifyPropertyChanged。那么模板化基础是否需要 SetProperty? @PaulGibson - SetProperty 根本不需要,您仍然可以直接设置属性并使用 OnPropertyChanged("PropertyName"); 但我喜欢这个实现,因为它只会在属性被触发时触发实际发生了变化,并且返回的布尔值在某些情况下也很有用,因为您可以将其放在 if 语句中,并从中包含一些其他操作链。 @Oceans 通过这种实现,如果我对 myData 列表进行更改(添加/删除),它会更新 UI 吗?如果代码每秒执行一次,我不会每次都创建 ObservableCollection 的新实例吗? @mitp - 一旦你初始化了OrderList,那么你需要使用这个属性并且not myData 列表了。只需添加新项目,如 OrderList.Add(new Order() OrderId = 3, Description = "Test3"); 任何添加/删除的项目都会通知更改,集合中任何被更改的对象,例如 Decription 属性现在也会通知更改。

以上是关于ObservableCollection CollectionChanged 更新数据网格的主要内容,如果未能解决你的问题,请参考以下文章

Listview 双向绑定与 ObservableCollection

如何将标签的 ItemsControl 绑定到 ObservableCollection

WPF ObservableCollection 异步调用问题

Xamarin 表单:分组 ObservableCollection

合并的 ObservableCollection

为啥在 DataGrid 中刷新 ObservableCollection 很慢