如何摆脱 WPF MVVM 视图模型中的重复属性
本文关键字:属性 模型 何摆脱 WPF MVVM 视图 | 更新日期: 2023-09-27 18:36:23
我正在使用具有许多属性的ViewModel
设置一个WPF应用程序。这些都是非常重复的,我想知道是否有办法摆脱这种情况。这就是一个属性的样子,我有大约 8-10 个。
public string Name
{
get
{
return this.name;
}
set
{
if (this.name != value)
{
this.name = value;
this.RaisePropertyChanged("Name");
}
}
}
如果你的要求很简单,我的建议是去第三方。这是一个已解决的问题,这要归功于一些聪明的人......
编写代码的最简单方法是完全删除INotifyPropertyChanged
实现,并以最小的方式编写属性,如下所示:
public string Name { get; set; }
然后将 Fody.PropertyChanged 添加到你的项目(它在 NuGet 上),并使用 [ImplementPropertyChanged]
属性标记你的类。
Fody 将在编译过程中做一些聪明的 IL 魔法,神奇地实现接口和所有样板代码 - 这意味着您编写的代码尽可能简单,您的最终结果正是您想要的。
请注意,如果您依赖于代码中其他位置的 INotifyPropertyChanged
接口(即,如果您在代码或类似代码中手动附加到事件),则可能需要以不同的方式使用 Fody,因为 IDE 不会意识到您已实现该接口。幸运的是,Fody 也会在其他场景中自动实现(例如:在类中实现INotifyPropertyChanged
,默认情况下,Fody 也会在你的属性中实现事件引发)。
提到的线程确实包含答案,但您需要进行一些挖掘。我将展示我在那里找到的两个最佳答案。
第一种解决方案是实现一个 ViewModelBase 类,该类将 set 方法封装到模板方法中,并使用 lamda 表达式检索属性名称,以便重构不会破坏属性名称字符串。
public class ViewModelBase: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
if (selectorExpression == null)
throw new ArgumentNullException("selectorExpression");
var body = selectorExpression.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
OnPropertyChanged(body.Member.Name);
}
protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(selectorExpression);
return true;
}
}
用法:
class ViewModel : DataBase
{
private String _prop1;
public String Prop1
{
get { return _prop1; }
set
{
SetField(ref _prop1, value, () => Prop1);
}
}
}
第二种解决方案使用字典将属性存储在基类中。这样我们就不需要传入旧值,因为它保存在基类中,也不需要创建成员字段来保存属性的值。我最喜欢这个解决方案:
public abstract class ViewModelBase : INotifyPropertyChanged
{
private readonly Dictionary<string, object> _propertyValueStorage;
#region Constructor
protected ViewModelBase()
{
this._propertyValueStorage = new Dictionary<string, object>();
}
#endregion
protected void SetValue<T>(Expression<Func<T>> property, T value)
{
var lambdaExpression = property as LambdaExpression;
if (lambdaExpression == null)
{
throw new ArgumentException("Invalid lambda expression", "Lambda expression return value can't be null");
}
var propertyName = this.getPropertyName(lambdaExpression);
var storedValue = this.getValue<T>(propertyName);
if (object.Equals(storedValue, value)) return;
this._propertyValueStorage[propertyName] = value;
this.OnPropertyChanged(propertyName);
}
protected T GetValue<T>(Expression<Func<T>> property)
{
var lambdaExpression = property as LambdaExpression;
if (lambdaExpression == null)
{
throw new ArgumentException("Invalid lambda expression", "Lambda expression return value can't be null");
}
var propertyName = this.getPropertyName(lambdaExpression);
return getValue<T>(propertyName);
}
private T getValue<T>(string propertyName)
{
object value;
if (_propertyValueStorage.TryGetValue(propertyName, out value))
{
return (T)value;
}
return default(T);
}
private string getPropertyName(LambdaExpression lambdaExpression)
{
MemberExpression memberExpression;
if (lambdaExpression.Body is UnaryExpression)
{
var unaryExpression = lambdaExpression.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else
{
memberExpression = lambdaExpression.Body as MemberExpression;
}
return memberExpression.Member.Name;
}
#region < INotifyPropertyChanged > Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
用法将是:
public class ViewModel : ViewModelBase
{
public String Prop1
{
get { return GetValue(() => Prop1); }
set { SetValue(() => Prop1, value); }
}
public bool Bool1
{
get { return GetValue(() => Bool1); }
set { SetValue(() => Bool1, value); }
}
解决方案 1 基于 https://stackoverflow.com/a/1316566/2259878 和 https://stackoverflow.com/a/1316566/2259878
解决方案 2 基于 http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx
这取决于要求,如果所有属性都用于相同的目的,则意味着名称 1、名称 2、名称 3.....name10,例如列出 10 个人的姓名,然后放入另一个类并将类类型的集合绑定到 xaml 中的 Items-control。或者简单地绑定字符串的可观察集合
但是,如果每个属性都有自己的目的,那么它就无法避免,因为属性只不过是保存不同值的变量。每个属性都有自己的意图,每个属性的操作在视图模型中会有所不同,具体取决于逻辑
我的解决方案接近uncletall的解决方案,但对使用进行了一些更改
private static readonly Properties<MainWindowViewModel> _properties = new Properties<MainWindowViewModel>();
public static Property TextProperty = _properties.Create(_ => _.Text);
private string _text;
public string Text
{
get { return _text; }
set { SetProperty(ref _text, value, TextProperty); }
}
XAML:
<Label Grid.Row="1" Content="{Model:PropertyBinding {x:Static Model:MainWindowViewModel.TextProperty}}" Width="200"/>
此示例的优点是对更改进行编译时检查。完整示例链接