引言
在实际开发场景中,当ViewModel内的一个属性是一个 ObservableCollection<T>
或者是一个多层级 class
的时候,有可能有的需求需要 ObservableCollection<T>
内的元素的子属性或多层级 class
的子属性,甚至子属性的子属性,变化,需要通知到ViewModel,该怎么做呢?
例如我有一个设置功能模块,十几个模型,一两百个属性参数,模型之间是2~3层的嵌套关系,最后得到一个大模型表示Model,我想要在子属性的值变化的是通知到ViewModel,记录日志或其他操作。
现有的MVVM框架,例如 MVVMLight
,Prism
等框架, 我好像都没有找到这样的功能,如果有更好的方案或实现,烦请告之。
现在手动实现一个这样的辅助类。接下来看一下实现过程:
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管理器
代码语言:javascript复制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
特性,获取你要绑定的属性,然后 监控你要绑定的属性的 INotifyPropertyChanged
的PropertyChanged
事件或者是 INotifyCollectionChanged
的 CollectionChanged
事件,最后通知到 HolderViewModel
中,若子属性有多层级关系,可以多层级中每个层级使用 NoticeFlagAttribute
特性,标记你想要监控的属性,然后Binding管理器通过递归方式依次绑定好,就实现了多层级的监控通知到 HolderViewModel
中。
我已将Demo发布到github,Readme.md中有使用说明。
❝github仓库地址 https://github.com/PeterPandefu/MvvmNoticeHolder 我的个人博客:https://niuery.com/ 欢迎关注~ ❞