ObservableCollection的CollectionChanged如何更新DataGrid

4
我有一个绑定了ObservableCollection的DataGrid。我的数据列表是动态的,因此列表中的项目正在被编辑/添加/删除。 起初,我清除并添加了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>

编辑: OrderList 类

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绑定到DataGridSource,那么它应该按预期工作。每当添加新项或删除项时,视图将收到通知以更新其数据。要跟踪实际更改,您不需要实现列表的CollectionChanged事件,但必须使列表中的实际对象可观察。一旦对象是可观察的,并且属性发送PropertyChanged通知,可观察集合将捕获此通知。 - Oceans
它与数据网格绑定,但是使用该实现时,数据网格中没有任何内容。我之前的实现至少可以在填充数据和更新数据方面工作,但我想避免清除集合,因为我不能在数据网格中选择项目。 - mit
你可以发布你的OderList类吗?它应该像这样声明:public class OrderList : INotifyPropertyChanged,并包含一个私有void NotifyPropertyChanged方法(如Daniele在下面的答案中所示)。然后,将该类放入ObservableCollection中,您就可以使用它了。 - Paul Gibson
@PaulGibson 请查看帖子编辑。 - mit
3个回答

9
如果你只是将ObservableCollection绑定到DataGridSource上,那么它应该按预期工作。
每当添加新项或删除项时,视图都会收到通知以更新其数据。
要跟踪实际更改,您不需要实现列表的CollectionChanged事件,但必须使列表中的实际对象observable。 要使对象可观察,您必须实现INotifyPropertyChanged接口。
一旦对象是可观察的,并且属性发送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<T>方法?我从未为这种对象设置模板,通常直接实现INotifyPropertyChanged。那么,对于模板基类,SetProperty是否是必需的? - Paul Gibson
@PaulGibson - SetProperty 完全不是必要的,你仍然可以直接设置属性并使用 OnPropertyChanged("PropertyName"); 但我喜欢使用这种实现方式,因为它只会在属性实际更改时触发,而返回的布尔值在某些情况下也很有用,比如你可以将其放入 if 语句中,在此基础上执行其他操作链。 - Oceans
@Oceans 如果我对myData列表进行更改(添加/删除),那么它会更新UI吗?如果代码每秒执行一次,那么难道我不会创建新的ObservableCollection实例吗? - mit
@mitp - 一旦您初始化了 OrderList,则需要使用此属性而不再是 myData 列表。只需像 OrderList.Add(new Order() { OrderId = 3, Description = "Test3"}); 这样添加新项目即可。添加/删除的任何项目都会通知更改,而集合中更改的任何对象(例如 Decription 属性)现在也将通知更改。 - Oceans

0

你应该直接向绑定的集合中添加。向 OrderListData 添加不会影响你正在绑定的那个集合:

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

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

是的,我以前做过这个,但列表数据不断变化,代码会被重复运行。这就是我以前的做法,但我认为可能有更好的方法来解决这个问题。 请查看帖子,我已经编辑了以显示我以前所做的内容。 - mit
你目前的方法需要每次重新加载“OrderListData”时至少将“ObservableCollection”设置为一个新实例。 - DonBoitnott

0

你需要先在你的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");
        }
}

是的,我已经完成了这个。我只是想避免清除和添加到集合中。有没有办法做到这一点? - mit
检查编辑。您不需要清除并重新添加。只需执行您的操作(添加、删除、编辑)并将其用作属性即可。 - Daniele Sartori

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接