引言

在实际开发场景中,当ViewModel内的一个属性是一个 ObservableCollection<T> 或者是一个多层级 class 的时候,有可能有的需求需要 ObservableCollection<T>内的元素的子属性或多层级 class 的子属性,甚至子属性的子属性,变化,需要通知到ViewModel,该怎么做呢?

例如我有一个设置功能模块,十几个模型,一两百个属性参数,模型之间是2~3层的嵌套关系,最后得到一个大模型表示Model,我想要在子属性的值变化的是通知到ViewModel,记录日志或其他操作。

现有的MVVM框架,例如 MVVMLightPrism , 我好像都没有找到这样的功能,如果有更好的方案或实现,烦请告之。

现在手动实现一个这样的辅助类。接下来看一下实现过程:

INotifyHolder接口

先定义 INotifyHolder 接口,用于通知 HolderViewModel ,有属性变化了。

namespace MvvmNoticeHolderLib
{
public interface INotifyHolder
{
void AfterPropertyChangedNotified(object sender, string info);
}
}

NoticeFlagAttribute特性

定义 NoticeFlagAttribute 特性,用于标记哪些属性是需要在变化时通知到 HolderViewModel 的。

namespace MvvmNoticeHolderLib
{ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class NoticeFlagAttribute : Attribute
{
public string Name { get; set; } = string.Empty; public Type Type { get; set; } public NoticeFlagAttribute(string names, Type type)
{
Name = names;
Type = type;
}
}
}

NotifyHolder的Binding管理器

namespace MvvmNoticeHolderLib
{
public class NotifyHolderBindingManager
{
private static T BindSlaveProperty<T>(T source, object root)
{
try
{
if (source != null)
{
var type = source.GetType(); var properties = type.GetProperties(); var NoticeFlags = type.GetCustomAttributes<NoticeFlagAttribute>(); if (NoticeFlags != null && NoticeFlags.Count() > 0)
{
foreach (var noticeFlag in NoticeFlags)
{
PropertyInfo info = properties.SingleOrDefault(x => x.Name == noticeFlag.Name); if (info != null)
{
BindProperty(source, root, info);
}
}
}
}
return source;
}
catch (Exception ex)
{
return source;
}
} public static T BindSelfProperty<T>(T root)
{
try
{
if (root != null)
{
var type = root.GetType(); var properties = type.GetProperties(); var NoticeFlags = type.GetCustomAttributes<NoticeFlagAttribute>(); if (NoticeFlags != null && NoticeFlags.Count() > 0)
{
foreach (var noticeFlag in NoticeFlags)
{
PropertyInfo info = properties.SingleOrDefault(x => x.Name == noticeFlag.Name); if (info != null)
{
BindSlaveProperty(root, root); var tmp = info.GetValue(root); if (root is INotifyPropertyChanged notify)
{
notify.PropertyChanged += (sender, e) =>
{
if (NoticeFlags.Any(t => t.Name == e.PropertyName))
{
var senderType = sender.GetType();
PropertyInfo senderProperty = senderType.GetProperty(e.PropertyName);
BindProperty(sender, sender, senderProperty);
}
};
}
}
}
}
}
return root;
}
catch (Exception)
{
return root;
}
} private static void BindProperty<T>(T source, object root, PropertyInfo info)
{
if (info.PropertyType.IsGenericType && info.PropertyType.GetGenericTypeDefinition() == typeof(ObservableCollection<>))
{
var tmp = info.GetValue(source); if (tmp != null && tmp is INotifyCollectionChanged notifyCollectionChanged)
{
notifyCollectionChanged.CollectionChanged += (sender, e) =>
{
if (e.NewItems != null && e.NewItems.Count > 0)
{
BindSlaveProperty(e.NewItems[0], root); if (e.NewItems[0] != null && e.NewItems[0] is INotifyPropertyChanged notify)
{
if (root is INotifyHolder notifyHolder)
{
notify.PropertyChanged += (s, e) =>
{
notifyHolder.AfterPropertyChangedNotified(s, info.Name + "." + e.PropertyName + "发生了变化");
};
}
}
}
}; var arr = (IEnumerable<object>)tmp;
foreach (var item in arr)
{
if (item is INotifyPropertyChanged notify && root is INotifyHolder notifyHolder)
{
BindSlaveProperty(item, root);
notify.PropertyChanged += (sender, e) =>
{
notifyHolder.AfterPropertyChangedNotified(sender, info.Name + "." + e.PropertyName + "发生了变化");
};
}
}
}
}
else if (info.PropertyType.GetInterfaces().Contains(typeof(INotifyPropertyChanged)))
{
var tmp = info.GetValue(source);
if (tmp != null && tmp is INotifyPropertyChanged notify)
{
BindSlaveProperty(tmp, root);
if (root is INotifyHolder notifyHolder)
{
notify.PropertyChanged += (sender, e) =>
{
notifyHolder.AfterPropertyChangedNotified(sender, info.Name + "." + e.PropertyName + "发生了变化");
};
}
}
}
}
}
}

这个类就是实现这个功能的核心,其主要原理是,通过 NoticeFlagAttribute 特性,获取你要绑定的属性,然后 监控你要绑定的属性的 INotifyPropertyChangedPropertyChanged 事件或者是 INotifyCollectionChangedCollectionChanged事件,最后通知到 HolderViewModel 中,若子属性有多层级关系,可以多层级中每个层级使用 NoticeFlagAttribute 特性,标记你想要监控的属性,然后Binding管理器通过递归方式依次绑定好,就实现了多层级的监控通知到 HolderViewModel 中。

我已将Demo发布到github,Readme.md中有使用说明。

github仓库地址

https://github.com/PeterPandefu/MvvmNoticeHolder

MVVM --- 实现多层级通知的更多相关文章

  1. CommunityToolkit.Mvvm8.1 消息通知(4)

    本系列文章导航 https://www.cnblogs.com/aierong/p/17300066.html https://github.com/aierong/WpfDemo (自我Demo地址 ...

  2. 说不尽的MVVM(3) – 从通知属性说起

    上篇我们体验了一个从事件处理程序到MVVM程序的转变,在最后也留下了一个问题:RaisePropertyChanged的原理是什么?今天我们来一探究竟. 通过上节做的小例子我们知道,仅仅修改ViewM ...

  3. android MVC && MVP && MVVM分析和对比

    相关:http://www.cnblogs.com/wytiger/p/5305087.html 出处http://blog.csdn.net/self_study,对技术感兴趣的同鞋加群544645 ...

  4. iOS开发项目之MVC与MVVM

    MVC MVC,Model-View-Controller,我们从这个古老而经典的设计模式入手.采用 MVC 这个架构的最大的优点在于其概念简单,易于理解,几乎任何一个程序员都会有所了解,几乎每一所计 ...

  5. ReactiveCocoa 和 MVVM 入门 (转)

    翻译自ReactiveCocoa and MVVM, an Introduction. 文中引用的 Gist 可能无法显示.为了和谐社会, 请科学上网. MVC 任何一个正经开发过一阵子软件的人都熟悉 ...

  6. 【长篇高能】ReactiveCocoa 和 MVVM 入门

    翻译自ReactiveCocoa and MVVM, an Introduction. 文中引用的 Gist 可能无法显示.为了和谐社会, 请科学上网. MVC 任何一个正经开发过一阵子软件的人都熟悉 ...

  7. 【转】伟大的RAC和MVVM入门(一)

    原文:http://www.sprynthesis.com/2014/12/06/reactivecocoa-mvvm-introduction/   翻译自ReactiveCocoa and MVV ...

  8. javascript基础修炼(9)——MVVM中双向数据绑定的基本原理

    开发者的javascript造诣取决于对[动态]和[异步]这两个词的理解水平. 一. 概述 1.1 MVVM模型 MVVM模型是前端单页面应用中非常重要的模型之一,也是Single Page Appl ...

  9. 基于vue实现一个简单的MVVM框架(源码分析)

    不知不觉接触前端的时间已经过去半年了,越来越发觉对知识的学习不应该只停留在会用的层面,这在我学jQuery的一段时间后便有这样的体会. 虽然jQuery只是一个JS的代码库,只要会一些JS的基本操作学 ...

  10. 使用MVVM设计模式构建WPF应用程序

    使用MVVM设计模式构建WPF应用程序 本文是翻译大牛Josh Smith的文章,WPF Apps With The Model-View-ViewModel Design Pattern,译者水平有 ...

随机推荐

  1. Gin 路由注册与请求参数获取

    目录 一.Web应用开发的两种模式 1.前后端不分离模式 2.前后端分离模式 二.RESTful介绍 三.API接口 3.1 RESTful API设计指南 3.2 API与用户的通信协议 3.3 R ...

  2. Go 泛型发展史与基本介绍

    Go 泛型发展史与基本介绍 Go 1.18版本增加了对泛型的支持,泛型也是自 Go 语言开源以来所做的最大改变. 目录 Go 泛型发展史与基本介绍 一.为什么要加入泛型? 二.什么是泛型 三.泛型的来 ...

  3. 各大安卓模拟器的adb端口和使用对比

    在手游开发中,经常会使用模拟器来进行adb调试,本文列出了市面上主流模拟器的adb端口和使用对比. 模拟器ADB端口 市面上常见的模拟器连接ADB的端口列表: 网易MuMu模拟器 7555 夜神安卓模 ...

  4. 从零开始配置vim(21)——lsp简介与treesitter 配置

    截止到上一篇文章,我们配置了neovim的很多内容了.具备了一些编辑器的常用功能了,而且可以胜任日常的文档编辑工作了.但是想作为一个可靠的代码编辑器还缺少重要的一环,即代码语法部分的支持. 在过去的v ...

  5. NLP涉及技术原理和应用简单讲解【一】:paddle(梯度裁剪、ONNX协议、动态图转静态图、推理部署)

    参考链接: https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/advanced/gradient_clip_cn.html 1. ...

  6. 字节码编程,Javassist篇五《使用Bytecode指令码生成含有自定义注解的类和方法》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 到本章为止已经写了四篇关于字节码编程的内容,涉及了大部分的API方法.整体来说对 J ...

  7. java中“0x”表示的含义

    public static void main(String[] args) { int a = 0xff;//16进制默认是int int b = 0x000000ff; System.out.pr ...

  8. Slot 的含义

    Slot 解释 1)slot就是槽的意思,是一个资源单位,只有给task分配了一个slot之后,这个task才可以运行.slot分两种,map slot沪蓉reduce slot.另外,slot是一个 ...

  9. 【OpenGL ES】绘制立方体

    1 前言 ​ 本文主要介绍使用 OpenGL ES 绘制立方体,读者如果对 OpenGL ES 不太熟悉,请回顾以下内容: 绘制三角形 绘制彩色三角形 绘制正方形 绘制圆形 ​ 在绘制立方体的过程中, ...

  10. let与const

    let与const ES2015(ES6)新增加了两个重要的JavaScript关键字: let和const. 块级作用域 代码块内如果存在let或者const,代码块会对这些命令声明的变量从块的开始 ...