如何在 ViewModel 属性 中包装模型 属性
How to wrap Model Property in ViewModel Property
而不是直接将整个 Model 暴露给 View,我想要 ViewModel属性只是每个 Model 属性的代理。例如;
private Product _product;
public string ProductName
{
get { return _product.ProductName; }
set
{
SetProperty(ref _product.ProductName, value);
}
}
但是上面的例子导致了A property, indexer or dynamic member access may not be passed as an out or ref parameter
的错误。
我该如何解决这个问题?
P.S。我的模型不是由 INPC 接口实现的。它们只是简单的 POCO 类.
这里不需要设置属性,你可以这样做:
private Product _product;
public string ProductName
{
get { return _product.ProductName; }
set
{
_product.ProductName = value;
}
}
您想要的是一个 façade 或 decorator 对象,它将在您的 VM 中充当您的模型,而不是用 ViewModel 属性包装每个模型 属性。这使您不仅可以重用您的模型 (facades/decorators),而且还可以将关注点保留在它们所属的位置。您可以像提供的 chipples 一样定义您的属性,但在 setter 中调用 OnPropertyChanged()。包装其他属性时不能使用 SetProperty 方法。
与此类似的内容:
class Person
{
public string Name { get; set; }
}
class PersonFacade : BindableBase
{
Person _person;
public string Name
{
get { return _person.Name; }
set
{
_person.Name = value;
OnPropertyChanged();
}
}
}
class ViewModel : BindableBase
{
private PersonFacade _person;
public PersonFacade Person
{
get { return _person; }
set { SetProperty(ref _person, value); }
}
}
使用像 ViewModelBase 这样的助手 class 很简单,它简化了引发 PropertyChanged 事件的过程:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
PropertyChanged(this, new PropertyChangedEventArgs(ExtractPropertyName(propertyExpression)));
}
private static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
{
if (propertyExpression == null)
throw new ArgumentNullException("propertyExpression");
var memberExpression = propertyExpression.Body as MemberExpression;
if (memberExpression == null)
throw new ArgumentException("memberExpression");
var property = memberExpression.Member as PropertyInfo;
if (property == null)
throw new ArgumentException("property");
var getMethod = property.GetGetMethod(true);
if (getMethod.IsStatic)
throw new ArgumentException("static method");
return memberExpression.Member.Name;
}
}
然后,简单的 POCO class 人:
class Person
{
public string Name { get; set; }
public double Age { get; set; }
}
我们可以像这样包装到 ViewModel:
public class PersonViewModel : ViewModelBase
{
private readonly Person person = new Person();
public string Name
{
get { return person.Name; }
set
{
person.Name = value;
OnPropertyChanged(() => Name);
}
}
public double Age
{
get { return person.Age; }
set
{
person.Age = value;
OnPropertyChanged(() => Age);
}
}
}
也许这是一个旧线程,但 C# MVVM 属性 让我感到压力。假设您每天需要编写 200 个属性。
我有另一种方法来创建 BaseClass。
public abstract class NotifyPropertiesBase : INotifyPropertyChanged, INotifyPropertyChanging
{
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
readonly Dictionary<string, object> _propertyStore = new Dictionary<string, object>();
public PropertyChangingEventArgs NotifyChanging([CallerMemberName] string propertyName = null)
{
var arg = new PropertyChangingEventArgs(propertyName);
PropertyChanging?.Invoke(this, arg);
return arg;
}
public PropertyChangedEventArgs NotifyChanged([CallerMemberName] string propertyName = null)
{
var arg = new PropertyChangedEventArgs(propertyName);
PropertyChanged?.Invoke(this, arg);
return arg;
}
public void SetPropValue(object newValue, [CallerMemberName] string propertyName = null)
{
if (GetType().GetMember(propertyName).Count() != 1)
throw new NotSupportedException($"\"{propertyName}\" Not Supported or maybe its not a Property");
var member = GetType().GetMember(propertyName).FirstOrDefault();
if (member.MemberType != System.Reflection.MemberTypes.Property)
throw new NotSupportedException($"Not Support Member Type {member.MemberType}");
var pInfo = member.DeclaringType.GetProperties().First();
NotifyChanging(propertyName);
if (!_propertyStore.ContainsKey(propertyName))
_propertyStore.Add(propertyName, newValue);
else
_propertyStore[propertyName] = newValue;
NotifyChanged(propertyName);
}
public T GetPropertyValue<T>([CallerMemberName] string propertyName = null)
{
return (T)GetPropertyValue(propertyName);
}
public object GetPropertyValue([CallerMemberName] string propertyName = null)
{
if (GetType().GetMember(propertyName).Count() != 1)
throw new NotSupportedException($"\"{propertyName}\" Not Supported or maybe its not a Property");
var member = GetType().GetMember(propertyName).FirstOrDefault();
if (member.MemberType != System.Reflection.MemberTypes.Property)
throw new NotSupportedException($"Not Support Member Type {member.MemberType}");
var pInfo = member.DeclaringType.GetProperties().First();
if (!_propertyStore.ContainsKey(propertyName))
{
_propertyStore.Add(propertyName, GetDefault(pInfo.PropertyType));
}
return _propertyStore[propertyName];
}
object GetDefault(Type t)
{
if (t.IsValueType)
{
return Activator.CreateInstance(t);
}
return null;
}
}
Class 用法:
class Program
{
static void Main(string[] args)
{
var t = new Test();
t.PropertyChanged += T_PropertyChanged;
t.ValueTest = "Hello World!";
var data = t.GetPropertyValue(nameof(t.ValueTest));
Console.Write(data);
}
private static void T_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine(e.PropertyName);
}
}
public class Test : NotifyPropertiesBase
{
public string ValueTest
{
get => GetPropertyValue<string>();
set => SetPropValue(value);
}
}
而不是直接将整个 Model 暴露给 View,我想要 ViewModel属性只是每个 Model 属性的代理。例如;
private Product _product;
public string ProductName
{
get { return _product.ProductName; }
set
{
SetProperty(ref _product.ProductName, value);
}
}
但是上面的例子导致了A property, indexer or dynamic member access may not be passed as an out or ref parameter
的错误。
我该如何解决这个问题?
P.S。我的模型不是由 INPC 接口实现的。它们只是简单的 POCO 类.
这里不需要设置属性,你可以这样做:
private Product _product;
public string ProductName
{
get { return _product.ProductName; }
set
{
_product.ProductName = value;
}
}
您想要的是一个 façade 或 decorator 对象,它将在您的 VM 中充当您的模型,而不是用 ViewModel 属性包装每个模型 属性。这使您不仅可以重用您的模型 (facades/decorators),而且还可以将关注点保留在它们所属的位置。您可以像提供的 chipples 一样定义您的属性,但在 setter 中调用 OnPropertyChanged()。包装其他属性时不能使用 SetProperty 方法。
与此类似的内容:
class Person
{
public string Name { get; set; }
}
class PersonFacade : BindableBase
{
Person _person;
public string Name
{
get { return _person.Name; }
set
{
_person.Name = value;
OnPropertyChanged();
}
}
}
class ViewModel : BindableBase
{
private PersonFacade _person;
public PersonFacade Person
{
get { return _person; }
set { SetProperty(ref _person, value); }
}
}
使用像 ViewModelBase 这样的助手 class 很简单,它简化了引发 PropertyChanged 事件的过程:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
PropertyChanged(this, new PropertyChangedEventArgs(ExtractPropertyName(propertyExpression)));
}
private static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
{
if (propertyExpression == null)
throw new ArgumentNullException("propertyExpression");
var memberExpression = propertyExpression.Body as MemberExpression;
if (memberExpression == null)
throw new ArgumentException("memberExpression");
var property = memberExpression.Member as PropertyInfo;
if (property == null)
throw new ArgumentException("property");
var getMethod = property.GetGetMethod(true);
if (getMethod.IsStatic)
throw new ArgumentException("static method");
return memberExpression.Member.Name;
}
}
然后,简单的 POCO class 人:
class Person
{
public string Name { get; set; }
public double Age { get; set; }
}
我们可以像这样包装到 ViewModel:
public class PersonViewModel : ViewModelBase
{
private readonly Person person = new Person();
public string Name
{
get { return person.Name; }
set
{
person.Name = value;
OnPropertyChanged(() => Name);
}
}
public double Age
{
get { return person.Age; }
set
{
person.Age = value;
OnPropertyChanged(() => Age);
}
}
}
也许这是一个旧线程,但 C# MVVM 属性 让我感到压力。假设您每天需要编写 200 个属性。
我有另一种方法来创建 BaseClass。
public abstract class NotifyPropertiesBase : INotifyPropertyChanged, INotifyPropertyChanging
{
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
readonly Dictionary<string, object> _propertyStore = new Dictionary<string, object>();
public PropertyChangingEventArgs NotifyChanging([CallerMemberName] string propertyName = null)
{
var arg = new PropertyChangingEventArgs(propertyName);
PropertyChanging?.Invoke(this, arg);
return arg;
}
public PropertyChangedEventArgs NotifyChanged([CallerMemberName] string propertyName = null)
{
var arg = new PropertyChangedEventArgs(propertyName);
PropertyChanged?.Invoke(this, arg);
return arg;
}
public void SetPropValue(object newValue, [CallerMemberName] string propertyName = null)
{
if (GetType().GetMember(propertyName).Count() != 1)
throw new NotSupportedException($"\"{propertyName}\" Not Supported or maybe its not a Property");
var member = GetType().GetMember(propertyName).FirstOrDefault();
if (member.MemberType != System.Reflection.MemberTypes.Property)
throw new NotSupportedException($"Not Support Member Type {member.MemberType}");
var pInfo = member.DeclaringType.GetProperties().First();
NotifyChanging(propertyName);
if (!_propertyStore.ContainsKey(propertyName))
_propertyStore.Add(propertyName, newValue);
else
_propertyStore[propertyName] = newValue;
NotifyChanged(propertyName);
}
public T GetPropertyValue<T>([CallerMemberName] string propertyName = null)
{
return (T)GetPropertyValue(propertyName);
}
public object GetPropertyValue([CallerMemberName] string propertyName = null)
{
if (GetType().GetMember(propertyName).Count() != 1)
throw new NotSupportedException($"\"{propertyName}\" Not Supported or maybe its not a Property");
var member = GetType().GetMember(propertyName).FirstOrDefault();
if (member.MemberType != System.Reflection.MemberTypes.Property)
throw new NotSupportedException($"Not Support Member Type {member.MemberType}");
var pInfo = member.DeclaringType.GetProperties().First();
if (!_propertyStore.ContainsKey(propertyName))
{
_propertyStore.Add(propertyName, GetDefault(pInfo.PropertyType));
}
return _propertyStore[propertyName];
}
object GetDefault(Type t)
{
if (t.IsValueType)
{
return Activator.CreateInstance(t);
}
return null;
}
}
Class 用法:
class Program
{
static void Main(string[] args)
{
var t = new Test();
t.PropertyChanged += T_PropertyChanged;
t.ValueTest = "Hello World!";
var data = t.GetPropertyValue(nameof(t.ValueTest));
Console.Write(data);
}
private static void T_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine(e.PropertyName);
}
}
public class Test : NotifyPropertiesBase
{
public string ValueTest
{
get => GetPropertyValue<string>();
set => SetPropValue(value);
}
}