【问题标题】:How to wrap Model Property in ViewModel Property如何在 ViewModel 属性中包装模型属性
【发布时间】:2016-01-08 12:14:53
【问题描述】:

我不想将整个 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的错误。

我应该如何解决这个问题?

附注我的模型不是由 INPC 接口实现的。它们只是简单的 POCO 类。

【问题讨论】:

    标签: c# wpf mvvm prism


    【解决方案1】:

    您想要的是一个外观或装饰器对象,它将在您的 VM 中充当您的模型,而不是使用 ViewModel 属性包装每个模型属性。这使您不仅可以重用您的模型(立面/装饰器),而且还可以将关注点保留在它们所属的位置。您可以像提供的芯片一样定义您的属性,但在 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); }
        }
    }
    

    【讨论】:

    • 你能给我看一个你在答案中描述的方法的简单代码示例吗?我得到了大致的想法,但我没有看到这种方法的全貌。谢谢。
    • 我添加了一个非常简单的示例来说明如何处理此问题。通过 Façade/Decorator/Adapter 类公开您想要的内容,然后将其用作 VM 的模型。
    • 很抱歉迟到接受答案。我已经在我的应用程序中尝试过这种方法并且效果很好。通过这种方式,我也可以更方便地在这个 Facade/Decorator/Adapter 类上实现 INotifyDataErrorInfo。
    • 您还可以添加模型中不存在的新属性,例如 FullName 和其他聚合/计算值。您还可以将多个模型组合成一个模型,而无需视图了解底层系统。
    【解决方案2】:

    也许这是一个旧线程,但 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 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);
        }
    }
    

    【讨论】:

      【解决方案3】:

      使用 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 做这个,还是我错过了什么?
      【解决方案4】:

      这里不需要SetProperty,你可以这样做:

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

      【讨论】:

      • 那么,谁来检查Property值是否真的改变了呢?还有,谁会触发 PropertyChangedEventHandler 的 OnPropertyChanged() 方法?
      • 嗯,我刚刚注意到这是标记为 mvvm 和 wpf,我认为这是一个 asp.net mvc 问题。我必须承认我不完全理解你的问题,所以如果其他人回答可能会更好!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-26
      相关资源
      最近更新 更多