c# - 使用 MVVM 更新 WPF 中的派生属性

标签 c# wpf mvvm

在 UI 中串联多个源时,可以使用什么模式来确保属性得到更新。

例如,我有一个窗口标题的字符串属性。它显示应用程序名称(常量字符串)、程序集版本(只读字符串)和根据用户输入加载的类型的实例属性。

有没有办法让标题属性订阅实例属性,以便在加载实例时标题自动更新?

现在,当菜谱被加载时,它会更新 title 属性。但我想扭转这个局面,这样食谱就不知道标题了。它只是广播它已加载,然后任何需要对正在加载的配方使用react的东西都会单独处理该事件。

哪种设计模式适用于此?

最佳答案

我在我的 MVVM 库中使用以下类来允许属性更改级联到相关属性。如果您认为它对您有用,请随意使用它:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace AgentOctal.WpfLib
{
    public class PropertyChangeCascade<T> where T : ObservableObject
    {

        public PropertyChangeCascade(ObservableObject target)
        {
            Target = target;

            Target.PropertyChanged += PropertyChangedHandler;
            _cascadeInfo = new Dictionary<string, List<string>>();
        }

        public ObservableObject Target { get; }
        public bool PreventLoops { get; set; } = false;

        private Dictionary<string, List<string>> _cascadeInfo;

        public PropertyChangeCascade<T> AddCascade(string sourceProperty,
                                                   List<string> targetProperties)
        {
            List<string> cascadeList = null;

            if (!_cascadeInfo.TryGetValue(sourceProperty, out cascadeList))
            {
                cascadeList = new List<string>();
                _cascadeInfo.Add(sourceProperty, cascadeList);
            }

            cascadeList.AddRange(targetProperties);

            return this;
        }

        public PropertyChangeCascade<T> AddCascade(Expression<Func<T, object>> sourceProperty,
                                                   Expression<Func<T, object>> targetProperties)
        {
            string sourceName = null;
            var lambda = (LambdaExpression)sourceProperty;

            if (lambda.Body is MemberExpression expressionS)
            {
                sourceName = expressionS.Member.Name;
            }
            else if (lambda.Body is UnaryExpression unaryExpression)
            {
                sourceName = ((MemberExpression)unaryExpression.Operand).Member.Name;
            }
            else
            {
                throw new ArgumentException("sourceProperty must be a single property", nameof(sourceProperty));
            }

            var targetNames = new List<string>();
            lambda = (LambdaExpression)targetProperties;

            if (lambda.Body is MemberExpression expression)
            {
                targetNames.Add(expression.Member.Name);
            }
            else if (lambda.Body is UnaryExpression unaryExpression)
            {
                targetNames.Add(((MemberExpression)unaryExpression.Operand).Member.Name);
            }
            else if (lambda.Body.NodeType == ExpressionType.New)
            {
                var newExp = (NewExpression)lambda.Body;
                foreach (var exp in newExp.Arguments.Select(argument => argument as MemberExpression))
                {
                    if (exp != null)
                    {
                        var mExp = exp;
                        targetNames.Add(mExp.Member.Name);
                    }
                    else
                    {
                        throw new ArgumentException("Syntax Error: targetProperties has to be an expression " +
                                                    "that returns a new object containing a list of " +
                                                    "properties, e.g.: s => new { s.Property1, s.Property2 }");
                    }
                }
            }
            else
            {
                throw new ArgumentException("Syntax Error: targetProperties has to be an expression " +
                                            "that returns a new object containing a list of " +
                                            "properties, e.g.: s => new { s.Property1, s.Property2 }");
            }

            return AddCascade(sourceName, targetNames);
        }

        public void Detach()
        {
            Target.PropertyChanged -= PropertyChangedHandler;
        }

        private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
        {
            List<string> cascadeList = null;

            if (_cascadeInfo.TryGetValue(e.PropertyName, out cascadeList))
            {
                if (PreventLoops)
                {
                    var cascaded = new HashSet<string>();
                    cascadeList.ForEach(cascadeTo =>
                    {
                        if (!cascaded.Contains(cascadeTo))
                        {
                            cascaded.Add(cascadeTo);
                            Target.RaisePropertyChanged(cascadeTo);
                        }
                    });
                }
                else
                {
                    cascadeList.ForEach(cascadeTo =>
                    {
                        Target.RaisePropertyChanged(cascadeTo);
                    });
                }
            }
        }
    }
}

ObservableObject 只是实现 INotifyPropertyChanged 的基类。您应该能够相当轻松地替换您自己的。

你可以这样使用它:

class CascadingPropertyVM : ViewModel
{
    public CascadingPropertyVM()
    {
        new PropertyChangeCascade<CascadingPropertyVM>(this)
            .AddCascade(s => s.Name,
            t => new { t.DoubleName, t.TripleName });
    }

    private string _name;
    public string Name
    {
        get => _name;
        set => SetValue(ref _name, value);
    }

    public string DoubleName => $"{Name} {Name}";
    public string TripleName => $"{Name} {Name} {Name}";
}

构造函数中的行将 Name 属性的更改级联到 DoubleNameTripleName 属性。默认情况下,出于性能原因,它不会检查级联中的循环,因此它依赖于您不创建循环。您可以选择将级联上的 PreventLoops 设置为 true,这将确保每个属性仅引发一次 PropertyChanged

关于c# - 使用 MVVM 更新 WPF 中的派生属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45175355/

相关文章:

c# - 对象当前正在别处使用 - 当 image.save

wpf - WPF ListBox SelectedItem问题

c# - 为什么这个绑定(bind)在 MVVM 中不起作用?

c# - 可空日期时间中的年份

c# - C# Unity3D 中的未分配引用异常

C# 将字符串转换为 int(四舍五入)

c# - 由于语法原因,带参数的 UPDATE 子句返回错误

wpf - Mouse.Capture 是什么意思?

c# - 如果模型未实现 INotifyPropertyChanged, View 模型如何从其底层模型传播更改通知?

android - 数据绑定(bind) Recyclerview 和 onClick