3
votes

Instead of directly exposing entire Model to the View, I want to have ViewModel properties which are just proxies for each Model properties. For example;

private Product _product;

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

But the above example causes the error of A property, indexer or dynamic member access may not be passed as an out or ref parameter.

How should I solve this problem?

P.S. My Models are not implemented by INPC interface. They are just simple POCO classes.

4

4 Answers

8
votes

What you want is a façade or decorator object that will act as your model in your VM, not wrapping every model property with ViewModel properties. This allows you to not only reuse your models (facades/decorators), but it also keeps the concerns where they belong. You define your properties just like chipples provided, but call OnPropertyChanged() in the setter. You can't use the SetProperty method when wrapping other properties.

Something similar to this:

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

Maybe this is an old thread but C# MVVM property makes me stressed. Imagine You need to write 200 properties a day.

I have another approach to creating 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 Usages:

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
votes

It is simple make with helper class like ViewModelBase, which simplifies raise PropertyChanged event:

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

then, simple POCO class Person:

class Person
{
    public string Name { get; set; }

    public double Age { get; set; }
}

we can wrap to ViewModel like:

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

SetProperty is not needed here, you can just do this:

private Product _product;

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