反映序列化对象以设置 PropertyChanged 事件

发布于 2024-12-07 14:48:44 字数 3832 浏览 6 评论 0原文

我有一个通过反序列化某些 XML 创建的对象。我使用 Visual Studio 的工具从供应商模型 XML 生成 XSD。然后使用 XSD 工具我从他们那里得到了课程。我设置了 XSD 工具,使其生成的类成为 INotifyPropertyChanged。

现在我尝试在 WPF 应用程序的“选项卡”中显示此对象。每当有人做出改变时,我想要一个“脏”指示器。问题是这个对象(从生成的 XSD 生成类)并不是最漂亮的结构。该对象的显示并不模仿其数据结构。我考虑创建显示对象(不使用 MMVM ATM)并使用它们来绑定我的更改,然后将这些更改作为数据对象持久保存到对象中。我基本上只是扔掉当前的显示来执行此操作,因为我现在只是添加检查是否有编辑。

我的想法是反映整个对象并为我遇到的每个属性设置 PropertyChanged 事件(遍历对象和属性的图表)。我的反思失败了,我也可能犯了一些短视的错误。

这是我到目前为止编写的代码:

void SetupPropertyChanged(INotifyPropertyChanged component)
    {
        component.PropertyChanged += CAMConfig_PropertyChanged;

        Type componentType = component.GetType();

        foreach (PropertyInfo info in componentType.GetProperties())
        {
            Type[] types =
                info.PropertyType.FindInterfaces((a, b) => { return a.ToString() == b.ToString(); }, typeof(INotifyPropertyChanged));

            bool isINotify = types.Contains(typeof(INotifyPropertyChanged));


            if (isINotify)
                this.SetupPropertyChanged((INotifyPropertyChanged)info.GetValue(component, new object[] { }));
        }
    }

我认为我遇到了 Observable 集合属性类型问题,因为它在遍历对象时抛出异常。我还突然意识到,我不知道这个对象结构是否会有循环引用。

有人可以帮我计算出这段代码,以便我可以遍历对象图。现在我不太担心循环引用的可能性,但如果有一个解决方案能够防止这种情况,那将非常非常有帮助!

根据卡雷尔的回答,我创建了这个“助手”类:

    public static class NotifyPropertyChangedHelper
{
    public delegate void ChangeOccuredHandler(object sender);

    public static void SetupPropertyChanged(INotifyPropertyChanged component, ChangeOccuredHandler changedHandler)
    {
        SetupPropertyChanged(new List<object>(), component, changedHandler);
    }

    static void SetupPropertyChanged(IList<object> closed, INotifyPropertyChanged component, ChangeOccuredHandler changedHandler)
    {
        if (closed.Contains(component)) return; // event was already registered

        closed.Add(component); //adds the property that is to be processed

        //sets the property changed event if the property isn't a collection
        if (!(component is INotifyCollectionChanged))
            component.PropertyChanged += (sender, e) =>
                {
                    changedHandler(sender);
                };

        /*
         * If the component is an enumerable there are two steps. First check to see if it supports the INotifyCollectionChanged event.
         * If it supports it add and handler on to this object to support notification.  Next iterate through the collection of objects
         * to add hook up their PropertyChangedEvent.
         * 
         * If the component isn't a collection then iterate through its properties and attach the changed handler to the properties.
         */
        if (component is IEnumerable<object>)
        {
            if (component is INotifyCollectionChanged)
            {
                //((INotifyCollectionChanged)component).CollectionChanged += collectionHandler;
                ((INotifyCollectionChanged)component).CollectionChanged += (sender, e) =>
                    {
                        changedHandler(sender);
                    };
            }

            foreach (object obj in component as IEnumerable<object>)
            {
                if (obj is INotifyPropertyChanged)
                    SetupPropertyChanged(closed, (INotifyPropertyChanged)obj, changedHandler);
            }
        }
        else
        {
            foreach (PropertyInfo info in component.GetType().GetProperties())
            {
                var propertyValue = info.GetValue(component, new object[] { });
                var inpc = propertyValue as INotifyPropertyChanged;
                if (inpc == null) continue;
                SetupPropertyChanged(closed, inpc, changedHandler);
            }
        }
    }
}

I have a object that is created through deserialization of some XML. I used Visual Studio's tool to generate the XSD from the vendor mockup XML. Then using the XSD tool I got classes from them. I set the XSD tool so that it would make the generated classes be INotifyPropertyChanged.

Now I'm trying to display this object in a "tab" on my WPF application. I want a "dirty" indicator whenever someone makes a change. The issue is that this object, being generated classes from generated XSD is not the prettiest structure. The display for this object is does not mimic its data structure. I thought of creating display objects (not using MMVM ATM) and using those to bind my changes to and then persist those changes to the object as a data object. I would essentially just throw out my current display to do this since I'm now just adding a check for if there were edits.

My thought is to reflect across the object and set the PropertyChanged event for every property I come across (walking through the graph of objects and properties). My reflection fu is failing me and I'm also probably making some short-sighted mistakes.

Here is the code I've written thus far:

void SetupPropertyChanged(INotifyPropertyChanged component)
    {
        component.PropertyChanged += CAMConfig_PropertyChanged;

        Type componentType = component.GetType();

        foreach (PropertyInfo info in componentType.GetProperties())
        {
            Type[] types =
                info.PropertyType.FindInterfaces((a, b) => { return a.ToString() == b.ToString(); }, typeof(INotifyPropertyChanged));

            bool isINotify = types.Contains(typeof(INotifyPropertyChanged));


            if (isINotify)
                this.SetupPropertyChanged((INotifyPropertyChanged)info.GetValue(component, new object[] { }));
        }
    }

I think I'm running into Observable collection property type issues as its throwing an exception when I'm traversing my object. It also just hit me that I don't know if this object structure would have circular references.

Can someone help me work out this code so that I can traverse the object graph. Right now I'm not too concerned about the possibility of circular references, but if a solution presents itself that prevents that situation it would be very, very helpful!

Based on Karel's answer I've created this "helper" class:

    public static class NotifyPropertyChangedHelper
{
    public delegate void ChangeOccuredHandler(object sender);

    public static void SetupPropertyChanged(INotifyPropertyChanged component, ChangeOccuredHandler changedHandler)
    {
        SetupPropertyChanged(new List<object>(), component, changedHandler);
    }

    static void SetupPropertyChanged(IList<object> closed, INotifyPropertyChanged component, ChangeOccuredHandler changedHandler)
    {
        if (closed.Contains(component)) return; // event was already registered

        closed.Add(component); //adds the property that is to be processed

        //sets the property changed event if the property isn't a collection
        if (!(component is INotifyCollectionChanged))
            component.PropertyChanged += (sender, e) =>
                {
                    changedHandler(sender);
                };

        /*
         * If the component is an enumerable there are two steps. First check to see if it supports the INotifyCollectionChanged event.
         * If it supports it add and handler on to this object to support notification.  Next iterate through the collection of objects
         * to add hook up their PropertyChangedEvent.
         * 
         * If the component isn't a collection then iterate through its properties and attach the changed handler to the properties.
         */
        if (component is IEnumerable<object>)
        {
            if (component is INotifyCollectionChanged)
            {
                //((INotifyCollectionChanged)component).CollectionChanged += collectionHandler;
                ((INotifyCollectionChanged)component).CollectionChanged += (sender, e) =>
                    {
                        changedHandler(sender);
                    };
            }

            foreach (object obj in component as IEnumerable<object>)
            {
                if (obj is INotifyPropertyChanged)
                    SetupPropertyChanged(closed, (INotifyPropertyChanged)obj, changedHandler);
            }
        }
        else
        {
            foreach (PropertyInfo info in component.GetType().GetProperties())
            {
                var propertyValue = info.GetValue(component, new object[] { });
                var inpc = propertyValue as INotifyPropertyChanged;
                if (inpc == null) continue;
                SetupPropertyChanged(closed, inpc, changedHandler);
            }
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

荒芜了季节 2024-12-14 14:48:44

保留已为其注册事件的属性(组件)列表,并使函数递归。您可以将组件直接转换为INotifyPropertyChanged

void SetupPropertyChanged(IList<object> closed, INotifyPropertyChanged component)  
{
  if(closed.Contains(component)) return; // event was already registered

  closed.Add(component);

  component.PropertyChanged += CAMConfig_PropertyChanged;            
  foreach (PropertyInfo info in componentType.GetProperties())          
  {
    var propertyValue = info.GetValue(component, new object[] { });
    var inpc = propertyValue as INotifyPropertyChanged;
    if(inpc == null) continue;
    this.SetupPropertyChanged(closed, inpc);
  }  
}

Keep a list of properties (components) for which you've already registered the event and make your function recursive. And you can cast component to INotifyPropertyChanged directly.

void SetupPropertyChanged(IList<object> closed, INotifyPropertyChanged component)  
{
  if(closed.Contains(component)) return; // event was already registered

  closed.Add(component);

  component.PropertyChanged += CAMConfig_PropertyChanged;            
  foreach (PropertyInfo info in componentType.GetProperties())          
  {
    var propertyValue = info.GetValue(component, new object[] { });
    var inpc = propertyValue as INotifyPropertyChanged;
    if(inpc == null) continue;
    this.SetupPropertyChanged(closed, inpc);
  }  
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文