在 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);
                }
            }
        }
    }
}

总的来说,它非常好用,正是我所需要的。这段代码可以很容易地扩展为对那些感兴趣的人来说更实用。