当属性在用户界面中连接多个源时,可以使用什么模式来确保属性被更新。

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

有没有一种方法可以使title属性订阅实例属性,以便在加载实例时标题自动更新?

现在,在加载配方时,它将更新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/

10-12 00:25
查看更多