【问题标题】:Updating ObservableCollection Item properties using INotifyPropertyChanged使用 INotifyPropertyChanged 更新 ObservableCollection 项属性
【发布时间】:2011-02-14 11:53:43
【问题描述】:

检查以确保我的假设是正确的。

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

我假设这是 ByDesign,并且为了触发 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;
}

在上面的例子中,纬度和经度都会触发它们的事件。

由于我的班级有很多属性,有没有比一个一个更好的方法来做到这一点?

【问题讨论】:

    标签: c# silverlight windows-phone-7


    【解决方案1】:

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

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

    item.RaisePropertyChanged("");
    

    其中 RaisePropertyChanged 是调用 INotifyPropertyChanged 实现的 PropertyChanged 事件的公共方法。

    【讨论】:

    • 我会使用String.Empty,它更干净一些。
    【解决方案2】:

    为此,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 来获取不属于基本类型或值类型(或字符串)的属性。在某些情况下可能仍然行为不端。
    您还可以在该方法中添加一个 bool deep 并控制它是否应该在需要时进行深度加载。 (只需将 || !deep 添加到那个长的 if 表达式中)

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

    Edit2:因为PropertyInfo.GetValue 返回一个object,所以我之前的代码没有递归加载,不幸的是,您必须显式传递一个类型,请参阅旧版本的修订。

    Edit3:有关在没有类型参考的情况下使其工作的方法,请参阅我问的dedicated question。然而,这并没有解决其他问题,例如循环引用和枚举对象的属性。

    【讨论】:

    • 嘿,这个效果很好。我在设备中拥有的一种类型是属性位置,它是类。所以我不得不调用 item.Load(currentDevice) 和 item.Location.Load(currentDevice.Location)
    • 我的方法也应该加载Location,但我的代码存在缺陷。有关更新,请参阅 Edit2 和新代码。当然我应该注意,如果你现在只调用item.Load(item.GetType(), currentDevice, true),这意味着Location 属性的PropertyChanged 事件将不会被触发,但根据您的代码,这可能不会有太大问题,因为@987654333 的所有属性@(希望)将被更新。您可以修改代码以首先将新对象分配给 Location,然后在出现问题时加载属性。
    • 我问了一个关于摆脱 Type 引用的方法的问题,请参阅答案中的 Edit3。
    • 很高兴有帮助,请记住我在 Edit3 中提到的缺陷,以防您修改使用该方法的类。
    【解决方案3】:

    我认为您可以重载= 运算符并在那里进行属性分配。然后将生成 PropertyChanged 事件,您仍将使用与第一个示例相同的语法。

    【讨论】:

    • @bryanbcook ha ha 可能有点.. :D.. 但我建议 OP 想在提到的语法中使用它。这只能通过这个重载来完成
    【解决方案4】:

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

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

    【讨论】:

    • 他写道:“既然我的类有一堆属性,有没有比一个一个一个更好的方法来做到这一点?”,即使事件触发的代码比赋值少一点,它仍然相当乏味。
    • 我已经在 Device 类上实现了 INotifyPropertyChanged,因此我的问题
    猜你喜欢
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-01
    • 1970-01-01
    • 2012-04-05
    • 2010-12-12
    相关资源
    最近更新 更多