在 WPF 中轻松将通知传播到 UI
Easily propagating notifications up to the UI in WPF
所以我看到了一些对与此类似问题的回答,但我想知道我正在考虑的某种范例在 C# 中是否可行。首先,我将列出问题:
我有一个用 C# 开发的 MVVM 应用程序。模型的属性会发生变化,当模型中的单个 属性 发生变化时,它通常会影响视图模型中的多个属性。所以视图模型监听模型的变化。视图会监听视图模型的变化。
在我的视图模型中,我最终得到一些如下所示的代码:
private void OnModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
string prop_name = e.PropertyName;
if (prop_name.Equals("some_property_on_the_model"))
{
NotifyPropertyChanged("some_property_on_the_view_model");
NotifyPropertyChanged("some_property_on_the_view_model");
NotifyPropertyChanged("some_property_on_the_view_model");
NotifyPropertyChanged("some_property_on_the_view_model");
NotifyPropertyChanged("some_property_on_the_view_model");
}
else if (...)
{
... etc ...
}
}
这很烦人,因为它看起来很乱。如果我在向视图模型添加新的 属性 后忘记编辑此函数,那么它很容易导致错误。
所以这是我喜欢做的事情,但我不知道这是否可行。所以我希望你们中的一位帮助我了解是否可能。
如果我可以使用 C# 的 "attributes" 功能来处理 属性 更改的传播,那就太棒了。
所以也许是这样的:
[ListenToModelProperty("some_property_on_the_model")]
[OnPropertyChanged("MyButtonVisibility")]
public Visibility MyButtonVisibility
{
get
{
if (model.some_property_on_the_model == true)
{
return Visibility.Visible;
}
else
{
return Visibility.Hidden;
}
}
}
private void OnModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
prop_name = e.PropertyName;
foreach (var property in view_model)
{
var attributes = property.GetCustomAttributes(typeof(ListenToModelPropertyAttribute));
var descriptions = attributes.Select(x => x.Description);
if (descriptions.Contains(prop_name))
{
notification_to_make = property.GetCustomAttributes(typeof(OnPropertyChangedAttribute));
string notification_string = notification_to_make[0].Description;
NotifyPropertyChanged(notification_string);
}
}
}
请注意,以上代码并不是真正的代码。它肯定不会编译,也不会工作。但我想看看在 C# 中是否可以实现类似上面的内容。是否可以使用属性来做这样的事情?或者是否有一个图书馆可以让这样的事情成为可能?
我知道怎么做了!这很简单。我将post相关代码放在这里,有兴趣的可以在我刚刚制作的这个github仓库中找到所有代码:https://github.com/davepruitt/model-subscribe
首先,我创建了一个自定义属性 class。它是一个简单的 class,它将字符串数组作为其构造函数的参数。这允许您监听模型上的多个属性以进行更改。它看起来像这样:
using System;
using System.Collections.Generic;
namespace TestPropagationOfPropertyChanges
{
[AttributeUsage(AttributeTargets.All)]
public class ListenForModelPropertyChangedAttribute : System.Attribute
{
public List<string> ModelPropertyNames = new List<string>();
public ListenForModelPropertyChangedAttribute (string [] propertyNames)
{
ModelPropertyNames.AddRange (propertyNames);
}
}
}
然后我创建了我的模型。为了简单起见,它只包含两个属性。它们是存储 "first name" 和 "last name":
的字符串
using System;
using System.ComponentModel;
namespace TestPropagationOfPropertyChanges
{
public class Model : NotifyPropertyChangedObject
{
#region Constructors
public Model ()
{
}
#endregion
#region Private data members
private string _first = string.Empty;
private string _last = string.Empty;
#endregion
#region Public properties
public string FirstName
{
get
{
return _first;
}
set
{
_first = value;
NotifyPropertyChanged ("FirstName");
}
}
public string LastName
{
get
{
return _last;
}
set
{
_last = value;
NotifyPropertyChanged ("LastName");
}
}
#endregion
}
}
在这种情况下,视图模型有一个 "full name" 属性。所以它想要监听模型中名字或姓氏发生的任何变化,然后对其中任何一个的变化做出反应。我意识到这不是使用这种系统的最佳 "real world" 场景,但它确实有助于说明这个概念。我的视图模型的第一部分如下:
using System;
namespace TestPropagationOfPropertyChanges
{
public class ViewModel : NotifyPropertyChangedObject
{
#region Private data members
//This is public for testing purposes
public Model _model = new Model();
#endregion
#region Constructors
public ViewModel ()
{
_model.PropertyChanged += ReactToModelPropertyChanged;
}
#endregion
#region Properties
[ListenForModelPropertyChangedAttribute(new string [] {"FirstName", "LastName"})]
public string FullName
{
get
{
return _model.FirstName + _model.LastName;
}
}
最后,视图模型以响应模型更改的方法结束。通常,在大型复杂的应用程序中,此方法可能包含一个大型 if-else 语句,其中包含对 NotifyPropertyChanged 的大量调用。相反,我们现在只是遍历视图模型的属性,看看哪些订阅了已更改的模型的 属性。见下文:
void ReactToModelPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
//Get the name of the property that was changed on the model
string model_property_changed = e.PropertyName;
//Get a System.Type object representing the current view-model object
System.Type t = typeof(ViewModel);
//Retrieve all property info for the view-model
var property_info = t.GetProperties();
//Iterate through each property
foreach (var property in property_info)
{
//Get the custom attributes defined for this property
var attributes = property.GetCustomAttributes (false);
foreach (var attribute in attributes)
{
//If the property is listening for changes on the model
var a = attribute as ListenForModelPropertyChangedAttribute;
if (a != null)
{
//If the property that was changed on the model matches the name
//that this view-model property is listening for...
if (a.ModelPropertyNames.Contains(model_property_changed))
{
//Notify the UI that the view-model property has been changed
NotifyPropertyChanged (property.Name);
}
}
}
}
}
总的来说,它非常好用,正是我所需要的。这段代码可以很容易地扩展为对那些感兴趣的人来说更实用。
所以我看到了一些对与此类似问题的回答,但我想知道我正在考虑的某种范例在 C# 中是否可行。首先,我将列出问题:
我有一个用 C# 开发的 MVVM 应用程序。模型的属性会发生变化,当模型中的单个 属性 发生变化时,它通常会影响视图模型中的多个属性。所以视图模型监听模型的变化。视图会监听视图模型的变化。
在我的视图模型中,我最终得到一些如下所示的代码:
private void OnModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
string prop_name = e.PropertyName;
if (prop_name.Equals("some_property_on_the_model"))
{
NotifyPropertyChanged("some_property_on_the_view_model");
NotifyPropertyChanged("some_property_on_the_view_model");
NotifyPropertyChanged("some_property_on_the_view_model");
NotifyPropertyChanged("some_property_on_the_view_model");
NotifyPropertyChanged("some_property_on_the_view_model");
}
else if (...)
{
... etc ...
}
}
这很烦人,因为它看起来很乱。如果我在向视图模型添加新的 属性 后忘记编辑此函数,那么它很容易导致错误。
所以这是我喜欢做的事情,但我不知道这是否可行。所以我希望你们中的一位帮助我了解是否可能。
如果我可以使用 C# 的 "attributes" 功能来处理 属性 更改的传播,那就太棒了。
所以也许是这样的:
[ListenToModelProperty("some_property_on_the_model")]
[OnPropertyChanged("MyButtonVisibility")]
public Visibility MyButtonVisibility
{
get
{
if (model.some_property_on_the_model == true)
{
return Visibility.Visible;
}
else
{
return Visibility.Hidden;
}
}
}
private void OnModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
prop_name = e.PropertyName;
foreach (var property in view_model)
{
var attributes = property.GetCustomAttributes(typeof(ListenToModelPropertyAttribute));
var descriptions = attributes.Select(x => x.Description);
if (descriptions.Contains(prop_name))
{
notification_to_make = property.GetCustomAttributes(typeof(OnPropertyChangedAttribute));
string notification_string = notification_to_make[0].Description;
NotifyPropertyChanged(notification_string);
}
}
}
请注意,以上代码并不是真正的代码。它肯定不会编译,也不会工作。但我想看看在 C# 中是否可以实现类似上面的内容。是否可以使用属性来做这样的事情?或者是否有一个图书馆可以让这样的事情成为可能?
我知道怎么做了!这很简单。我将post相关代码放在这里,有兴趣的可以在我刚刚制作的这个github仓库中找到所有代码:https://github.com/davepruitt/model-subscribe
首先,我创建了一个自定义属性 class。它是一个简单的 class,它将字符串数组作为其构造函数的参数。这允许您监听模型上的多个属性以进行更改。它看起来像这样:
using System;
using System.Collections.Generic;
namespace TestPropagationOfPropertyChanges
{
[AttributeUsage(AttributeTargets.All)]
public class ListenForModelPropertyChangedAttribute : System.Attribute
{
public List<string> ModelPropertyNames = new List<string>();
public ListenForModelPropertyChangedAttribute (string [] propertyNames)
{
ModelPropertyNames.AddRange (propertyNames);
}
}
}
然后我创建了我的模型。为了简单起见,它只包含两个属性。它们是存储 "first name" 和 "last name":
的字符串using System;
using System.ComponentModel;
namespace TestPropagationOfPropertyChanges
{
public class Model : NotifyPropertyChangedObject
{
#region Constructors
public Model ()
{
}
#endregion
#region Private data members
private string _first = string.Empty;
private string _last = string.Empty;
#endregion
#region Public properties
public string FirstName
{
get
{
return _first;
}
set
{
_first = value;
NotifyPropertyChanged ("FirstName");
}
}
public string LastName
{
get
{
return _last;
}
set
{
_last = value;
NotifyPropertyChanged ("LastName");
}
}
#endregion
}
}
在这种情况下,视图模型有一个 "full name" 属性。所以它想要监听模型中名字或姓氏发生的任何变化,然后对其中任何一个的变化做出反应。我意识到这不是使用这种系统的最佳 "real world" 场景,但它确实有助于说明这个概念。我的视图模型的第一部分如下:
using System;
namespace TestPropagationOfPropertyChanges
{
public class ViewModel : NotifyPropertyChangedObject
{
#region Private data members
//This is public for testing purposes
public Model _model = new Model();
#endregion
#region Constructors
public ViewModel ()
{
_model.PropertyChanged += ReactToModelPropertyChanged;
}
#endregion
#region Properties
[ListenForModelPropertyChangedAttribute(new string [] {"FirstName", "LastName"})]
public string FullName
{
get
{
return _model.FirstName + _model.LastName;
}
}
最后,视图模型以响应模型更改的方法结束。通常,在大型复杂的应用程序中,此方法可能包含一个大型 if-else 语句,其中包含对 NotifyPropertyChanged 的大量调用。相反,我们现在只是遍历视图模型的属性,看看哪些订阅了已更改的模型的 属性。见下文:
void ReactToModelPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
//Get the name of the property that was changed on the model
string model_property_changed = e.PropertyName;
//Get a System.Type object representing the current view-model object
System.Type t = typeof(ViewModel);
//Retrieve all property info for the view-model
var property_info = t.GetProperties();
//Iterate through each property
foreach (var property in property_info)
{
//Get the custom attributes defined for this property
var attributes = property.GetCustomAttributes (false);
foreach (var attribute in attributes)
{
//If the property is listening for changes on the model
var a = attribute as ListenForModelPropertyChangedAttribute;
if (a != null)
{
//If the property that was changed on the model matches the name
//that this view-model property is listening for...
if (a.ModelPropertyNames.Contains(model_property_changed))
{
//Notify the UI that the view-model property has been changed
NotifyPropertyChanged (property.Name);
}
}
}
}
}
总的来说,它非常好用,正是我所需要的。这段代码可以很容易地扩展为对那些感兴趣的人来说更实用。