C# 使用MVVM更新WPF中的派生属性
当属性在UI中连接多个源时,可以使用什么模式来确保属性得到更新 例如,我有一个窗口标题的字符串属性。它显示应用程序名(常量字符串)、程序集版本(只读字符串)以及基于用户输入加载的类型的实例属性 有没有办法使title属性订阅instance属性,以便在加载实例时,title自动更新 现在,当加载配方时,它会更新title属性。但是我想把它颠倒过来,这样食谱就不知道名称了。它只是广播它已加载,然后需要对加载的配方做出反应的任何内容都将单独处理该事件C# 使用MVVM更新WPF中的派生属性,c#,wpf,mvvm,C#,Wpf,Mvvm,当属性在UI中连接多个源时,可以使用什么模式来确保属性得到更新 例如,我有一个窗口标题的字符串属性。它显示应用程序名(常量字符串)、程序集版本(只读字符串)以及基于用户输入加载的类型的实例属性 有没有办法使title属性订阅instance属性,以便在加载实例时,title自动更新 现在,当加载配方时,它会更新title属性。但是我想把它颠倒过来,这样食谱就不知道名称了。它只是广播它已加载,然后需要对加载的配方做出反应的任何内容都将单独处理该事件 什么样的设计模式适合这种情况?如果正确,您需要开
什么样的设计模式适合这种情况?如果正确,您需要开始绑定工作。快速的谷歌搜索有很多结果,但这一个似乎涵盖了您需要的内容:
不确定这是否理想,但我的解决方案是处理MVVMLight提供的属性更改事件
private Model.Recipe _recipe;
public Model.Recipe Recipe
{
get { return _recipe; }
set { Set(ref _recipe, value); }
}
public string MyProperty
{
get { return "Test " + Recipe.MyProperty; }
}
public MainViewModel()
{
PropertyChanged += MainViewModel_PropertyChanged;
Recipe = new Model.Recipe();
}
private void MainViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "Recipe": RaisePropertyChanged("MyProperty"); break;
}
}
不太喜欢MainViewModel_PropertyChanged将成为一个处理所有更改的大型switch语句。另一种方法是使用messenger
private Model.Recipe _recipe;
public Model.Recipe Recipe
{
get { return _recipe; }
set { if (Set(ref _recipe, value)) { Messenger.Default.Send(value, "NewRecipe"); } }
}
public string MyProperty
{
get { return "Test " + Recipe.MyProperty; }
}
public MainViewModel()
{
Messenger.Default.Register<Model.Recipe>(this, "NewRecipe", NewRecipe);
Recipe = new Model.Recipe();
}
private void NewRecipe(Recipe obj)
{
RaisePropertyChanged("MyProperty");
}
private Model.Recipe\u Recipe;
公共模式。配方
{
获取{return\u recipe;}
set{if(set(ref _recipe,value)){Messenger.Default.Send(value,“NewRecipe”);}
}
公共字符串MyProperty
{
获取{return“Test”+Recipe.MyProperty;}
}
公共主视图模型()
{
Messenger.Default.Register(这是“NewRecipe”,NewRecipe);
配方=新型号。配方();
}
私有无效新配方(配方obj)
{
RaiseProperty变更(“我的财产”);
}
这种方法的好处是,如果MyProperty位于不同的ViewModel中,它仍然会收到通知,并且它们不会紧密耦合。需要处理配方更改的任何内容都可以注册消息并接收通知,而无需处理每个属性更改事件的巨型方法。我在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);
});
}
}
}
}
}
构造函数中的行将Name
属性的更改级联到DoubleName
和TripleName
属性。默认情况下,出于性能原因,它不会检查级联中的循环,因此它依赖于您不创建它们。您可以选择将级联上的PreventLoops
设置为true
,并确保每个属性只引发一次PropertyChanged
但是我想把它颠倒过来,这样食谱就不知道了
关于标题。它只是简单地广播它已加载,然后是任何内容
需要对加载的配方做出反应的将在
隔离
听起来Steven的Cleary计算属性正是您所需要的:
我已经在网站上更详细地回答了类似的问题
这个图书馆很神奇。事实上,我会为任何MVVM项目推荐它,无论它是新的还是旧的,增量采用计算属性并享受即时好处都是微不足道的。Err。。你听说过绑定吗?InotifyProperty是否已更改?它们一起给出了“标题属性订阅实例属性,以便加载实例时标题自动更新”的效果我希望您不介意,但我稍微编辑了您问题的标题,使其更符合我们的指导原则,与你的实际问题更接近一点。对我来说,这更像是一个评论,而不是一个真实的答案。你给出的另一个答案非常有建设性:不过,这个答案可以通过一些解释加以改进。即使你引用了必要的文本,这也会改善你的答案,只要它确实回答了问题。我同意你对回复的评估,并感谢建设性的反馈。但是,我刚刚开始投稿,没有添加评论的权限,因此这是我就这个问题提供意见的唯一方法。
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}";
}