使用INotifyPropertyChanged更新ObservableCollection项属性

4

确保我的假设是正确的。

我有一个ObservableCollection类。我调用一个web服务并检索设备数组。然后,我枚举ObservableCollection并将每个项目设置为从Web服务检索到的相应设备。我检索到的设备与ObservableCollection中的项目具有不同的属性值,但PropertyChanged事件没有触发。

我认为这是按设计来的,为了让PropertyChanged事件触发,我实际上必须枚举每个属性并设置该值?

例如,在下面的情况下,任何Device类属性上都没有触发PropertyChanged事件。

ObservableCollection<Device> Items = new ObservableCollection<Device>();
Items = LoadItems();

List<Device> devices = GetDevices();

foreach (var item in Items)
{
    var currentDevice = devices.Single(d1 => d1.ID == item.ID);
    item = currentDevice;
}

然而,如果我手动更新每个属性,那就没问题了:

ObservableCollection<Device> Items = new ObservableCollection<Device>();
Items = LoadItems();

List<Device> devices = GetDevices();

foreach (var item in Items)
{
    var currentDevice = devices.Single(d1 => d1.ID == item.ID);
    item.Latitude = currentDevice.Latitude;
    item.Longitude= currentDevice.Longitude;
}

在上述情况下,纬度和经度都会触发它们的事件。
由于我的类有一堆属性,是否有比逐个修改更好的方法?
4个回答

3

在第一个示例中,将集合中的项设置会触发CollectionChanged事件,而不是单个项的PropertyChanged事件。

您可以通过将空字符串指定为PropertyChanged事件来通知所有属性。例如:

item.RaisePropertyChanged("");

RaisePropertyChanged是一个公共方法,它调用实现了INotifyPropertyChanged接口的PropertyChanged事件。


我会使用 String.Empty,这样更简洁。 - H.B.

1

这时候 Load 方法可能会很有用,这样你就不会覆盖引用,而是设置旧对象的所有属性。我刚刚写了一个通用的扩展方法,它将分配所有可写属性,但非常粗糙:

public static class ExtensionMethods
{
    public static void Load<T>(this T target, Type type, T source, bool deep)
    {
        foreach (PropertyInfo property in type.GetProperties())
        {
            if (property.CanWrite && property.CanRead)
            {
                if (!deep || property.PropertyType.IsPrimitive || property.PropertyType == typeof(String))
                {
                    property.SetValue(target, property.GetValue(source, null), null);
                }
                else
                {
                    object targetPropertyReference = property.GetValue(target, null);
                    targetPropertyReference.Load(targetPropertyReference.GetType(), property.GetValue(source, null), deep);
                }
            }
        }
    }
}

然后您应该能够调用

item.Load(item.GetType(), currentDevice, true); //false for shallow loading

将所有值分配(如果它们是属性)。

编辑:将该方法递归,以便对于不是基元类型或值类型(或字符串)的属性调用Load。可能在某些情况下仍然表现不佳。
您还可以向该方法添加一个布尔值deep,并控制是否应进行深度加载,如果需要的话。(只需将||!deep添加到那个长if表达式中)

注意:当然,您也可以覆盖对象引用并使用反射为所有不同的属性引发PropertyChanged事件。无论哪种方式,您都不需要手动处理每个属性。

编辑2:因为PropertyInfo.GetValue返回一个object,我的先前代码没有递归加载,不幸的是,您必须明确传递一个Type,请参见旧版本的修订。

编辑3:有关使此工作的方法而不需要Type引用,请参见我提出的专用问题。但是,这并未解决其他问题,例如循环引用和枚举对象的属性。


嘿,这个很好用。我在Device中有一个类型是Property Location,它是一个类。所以我必须调用item.Load(currentDevice)和item.Location.Load(currentDevice.Location)。 - Omar Shahine
我的方法本应该也加载了“位置”,但是我的代码有缺陷。请参见Edit2和新代码进行更新。当然,我应该指出,如果您现在只调用item.Load(item.GetType(), currentDevice, true),那么意味着“位置”属性的PropertyChanged事件不会被触发,但根据您的代码,这可能并不是什么大问题,因为“位置”的所有属性(希望如此)都将被更新。如果这是一个问题,您可以修改代码,先将一个新对象分配给“位置”,然后再加载属性。 - H.B.
我问了一个关于如何摆脱类型引用的问题,请参见答案中的Edit3。 - H.B.
很高兴能帮到你,只要记住我在Edit3中提到的缺陷,以防你修改使用该方法的类。 - H.B.

0

我认为你可以重载=运算符并在那里执行属性赋值。然后PropertyChanged事件将被生成,而您仍将拥有第一个示例中的相同语法。


@bryanbcook 哈哈可以有点儿…… :D……但我建议使用它在提到的语法中。这只能通过这种重载完成。 - Shekhar_Pro

0

Device类必须实现INotifyPropertyChanged接口。然后对于每个属性,像往常一样触发通知属性更改事件。

这将自动启用属性更改通知。


他写道:“由于我的类有一堆属性,是否有比一个一个赋值更好的方法?即使事件触发的代码比赋值少一些,但仍然相当繁琐。” - H.B.
我已经在Device类上实现了INotifyPropertyChanged,因此我的问题是什么。 - Omar Shahine

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