如何在ViewModel属性中封装Model属性

4

与其直接将整个Model暴露给View,我希望有ViewModel属性,这些属性只是每个Model属性的代理。例如;

private Product _product;

public string ProductName
{
     get { return _product.ProductName; }
     set
     {
          SetProperty(ref _product.ProductName, value);
     }
}

但上面的例子会导致错误:属性、索引器或动态成员访问可能不能作为 out 或 ref 参数传递

我该如何解决这个问题?

附言:我的模型没有实现 INPC 接口。它们只是简单的 POCO 类。

4个回答

9
您需要的是一个外观或装饰器对象,它将作为您在VM中的模型,而不是用ViewModel属性包装每个模型属性。这不仅允许您重用您的模型(外观/装饰器),而且还使关注点保持在其所属的位置。您可以像 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); }
    }
}

你能给我展示一下你在回答中描述的方法的简单代码示例吗?我大致明白了,但我没有看到整个方法的全貌。谢谢。 - Steve.NayLinAung
1
我添加了一个非常简单的示例,展示您可能如何处理此问题。通过您的Facade/Decorator/Adapter类公开所需内容,然后将其用作VM的模型。 - user5420778
抱歉晚了才接受答案。我已经在我的应用程序中尝试了这种方法,并且效果很好。通过这种方法,我还可以更方便地在这个Facade / Decorator / Adapter类上实现INotifyDataErrorInfo。 - Steve.NayLinAung
1
您还可以添加模型中不存在的新属性,例如FullName和其他聚合/计算值。您还可以将多个模型组合成单个模型,而视图永远不会知道底层系统的存在。 - user5420778

2
也许这是一个旧的话题,但C# MVVM属性让我感到很有压力。想象一下,你需要每天编写200个属性。
我有另一种创建基类的方法。
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 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);
    }
}

0

使用类似 ViewModelBase 的帮助类很容易实现,它简化了 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类Person:

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);
        }
    }
}

你不能使用CallerMemberNameAttribute来完成这个任务吗?还是我漏掉了什么? - Adam H

-1

这里不需要使用SetProperty,你可以直接这样做:

private Product _product;

public string ProductName
{
     get { return _product.ProductName; }
     set
     {
          _product.ProductName = value;
     }
}

1
那么,谁来检查属性值是否真的已经被改变了呢?另外,谁会触发PropertyChanged()方法来激活PropertyChangedEventHandler? - Steve.NayLinAung
哦,我刚注意到这个标签是mvvm和wpf,我原以为这是一个asp.net mvc的问题。我必须承认我并不完全理解你的问题,也许最好由其他人来回答! - chipples

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接