避免在每个setter中调用RaisePropertyChanged

时间:2012-12-01 21:03:41

标签: c# mvvm inotifypropertychanged proxy-classes

我想摆脱模型类中占用空间和重复性的RaisePropertyChanged-Properties。我想要我的模特课......

public class ProductWorkItem : NotificationObject
{
    private string name;
    public string Name
    {
        get { return name; }
        set { 
            if (value == name) return; 
            name = value; RaisePropertyChanged(() => Name); 
        }
    }
    private string description;
    public string Description
    {
        get { return description; }
        set { 
            if (value == description) return; 
            description = value; RaisePropertyChanged(() => Description); 
        }
    }
    private string brand;
    public string Brand
    {
        get { return brand; }
        set { 
            if (value == brand) return; 
            brand = value; RaisePropertyChanged(() => Brand); 
        }
    }
}

...再次看起来很简单:(但是当属性发生变化时通知视图)

public class ProductWorkItem
{
    public string Name{ get; set; }
    public string Description{ get; set; }
    public string Brand{ get; set; }
}

这可以通过某种代理类来实现吗?

我想避免为每个模型类编写代理。

6 个答案:

答案 0 :(得分:5)

我知道在“vanilla”C#中没有简单易用的方法,但你可以用 aspects 来实现这一点。我已经使用了 PostSharp ,这样做的缺点是作为付费的第三方产品,但有免费版本,您也可以这样做。 PostSharp利用 attributes 的优势,如目标指定,继承等,并将它们扩展到各个方面。

然后,您可以定义LocationInterceptionAspect,它会覆盖OnSetValue方法来呼叫您的RaisePropertyChanged代表。然后,您可以使用使用aspect属性修饰的自动生成的属性。

PostSharp的付费版本允许您在类级别执行此操作,因此您只需要一个属性(如果您装饰基类并将属性定义为可继承,则不需要)。这是PostSharp网站上的 described ,作为InstanceLevelAspect

的用例

答案 1 :(得分:2)

我来了 NotifyPropertyWeaver 扩展程序,并且从那时起定期使用它。它是一个Visual Studio扩展,在编译代码之前为您实现始终相同的INPC内容。你没有注意到这一点。

您需要安装扩展程序,然后您的模型需要如下所示:

public class ProductWorkItem : INotifyPropertyChanged
{
    public string Name{ get; set; }
    public string Description{ get; set; }
    public string Brand{ get; set; }

    public event PropertyChangedEventHandler PropertyChanged;
}

扩展名为您添加所有其余内容。我喜欢这种方法的是,你的类仍然“正式”实现INPC接口,你也可以在非WPF上下文中使用它(因为INPC根本不是WPF的东西),但仍然没有用你所有的东西乱丢你的课。它会为依赖于属性的只读属性引发通知。

当然,它有点假,因为它只是自动化写作,并没有改变任何有关基础概念的东西。但也许这是妥协......

以下是更多信息:Link

答案 2 :(得分:1)

我们可以避免在WPF中的每个属性设置器上编写RaisePropertyChanged的重复代码。

使用Postsharp的免费版本。

通过使用以下代码,我们只能将Virtual属性绑定到view。

namespace Test
{
[Serializable]
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)]
public sealed class RaisePropertyChangedAttribute : MethodInterceptionAspect
{
    private string propertyName;

    /// <summary>
    /// Compiles the time validate.
    /// </summary>
    /// <param name="method">The method.</param>
    public override bool CompileTimeValidate(MethodBase method)
    {
        return IsPropertySetter(method) && !method.IsAbstract && IsVirtualProperty(method);
    }

    /// <summary>
    /// Method invoked at build time to initialize the instance fields of the current aspect. This method is invoked
    /// before any other build-time method.
    /// </summary>
    /// <param name="method">Method to which the current aspect is applied</param>
    /// <param name="aspectInfo">Reserved for future usage.</param>
    public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
    {
        base.CompileTimeInitialize(method, aspectInfo);
        propertyName = GetPropertyName(method);
    }

    /// <summary>
    /// Determines whether [is virtual property] [the specified method].
    /// </summary>
    /// <param name="method">The method.</param>
    /// <returns>
    ///   <c>true</c> if [is virtual property] [the specified method]; otherwise, <c>false</c>.
    /// </returns>
    private static bool IsVirtualProperty(MethodBase method)
    {
        if (method.IsVirtual)
        {
            return true;
        }

        var getMethodName = method.Name.Replace("set_", "get_");
        var getMethod = method.DeclaringType.GetMethod(getMethodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

        return getMethod != null && getMethod.IsVirtual;
    }

    private static string GetPropertyName(MethodBase method)
    {
        return method.Name.Replace("set_", string.Empty);
    }

    /// <summary>
    /// Determines whether [is property setter] [the specified method].
    /// </summary>
    /// <param name="method">The method.</param>
    /// <returns>
    /// <c>true</c> if [is property setter] [the specified method]; otherwise, <c>false</c>.
    /// </returns>
    private static bool IsPropertySetter(MethodBase method)
    {
        return method.Name.StartsWith("set_", StringComparison.OrdinalIgnoreCase);
    }

    /// <summary>
    /// Method invoked <i>instead</i> of the method to which the aspect has been applied.
    /// </summary>
    /// <param name="args">Advice arguments.</param>
    public override void OnInvoke(MethodInterceptionArgs args)
    {
        var arg = args as MethodInterceptionArgsImpl;

        if ((arg != null) && (arg.TypedBinding == null))
        {
            return;
        }

        // Note ViewModelBase is base class for ViewModel
        var target = args.Instance as ViewModelBase;

        args.Proceed();

        if (target != null)
        {
            target.OnPropertyChanged(propertyName);                    
        }
    }
}
}

答案 3 :(得分:1)

这已经很老了,没有人提到过:

https://kindofmagic.codeplex.com/

您可以为ViewModel中包含该类的1个属性的每个属性启用自动通知。

答案 4 :(得分:0)

我在System.Dynamic命名空间中找到了this class ...它可以拦截绑定目标上DataBinding发出的实际DependencyObject次调用{} 1}}在您的绑定源上。

http://i.msdn.microsoft.com/en-us/library/system.windows.data.binding.DataBindingMostBasic(v=vs.110).png?appId=Dev11IDEF1&l=EN-US&k=k(System.Windows.Data.Binding)%3bk(VS.XamlEditor)%3bk(TargetFrameworkMoniker-.NETFramework

所以现在可以做的是实现一个实现Property的类(让我们称之为DynamicNpcProxy),它来自INotifyPropertyChanged并覆盖DynamicObjectTryGetMember方法。

TrySetMember

要使其工作,请将代理程序包裹在当前绑定源周围,替换它们,让public class DynamicNpcProxy : DynamicObject, INotifyPropertyChanged { public DynamicNpcProxy(object proxiedObject) { ProxiedObject = proxiedObject; } //... public object ProxiedObject { get; set; } public override bool TrySetMember(SetMemberBinder binder, object value) { SetMember(binder.Name, value); return true; } protected virtual void SetMember(string propertyName, object value) { GetPropertyInfo(propertyName).SetValue(ProxiedObject, value, null); if (PropertyChanged != null) PropertyChanged(ProxiedObject, new PropertyChangedEventArgs(propertyName)); } protected PropertyInfo GetPropertyInfo(string propertyName) { return ProxiedObject.GetType().GetProperty(propertyName); } // override bool TryGetMember(...) } 完成其余工作。

在ViewModel.cs中:

DynamicObject

在View.xaml中:

IList<ProductWorkItem> items;
//... assign items
var proxies = items.Select(p => new DynamicNpcProxy(p)).ToList();
ICollectionView Products = CollectionViewSource.GetDefaultView(proxies);

你最终得到的是:

同时在<TextBox Text="{Binding Products.CurrentItem.Name}" /> <TextBox Text="{Binding Products.CurrentItem.Description}" /> 上查看this article,以提供更多信息......

答案 5 :(得分:0)

来自另一方(如果您没有花哨的扩展名),您可以使用我的答案中列出的扩展方法“自动指定”已更改的属性: WCF service proxy not setting "FieldSpecified" property

具体来说,您可以使用“非反射方法”为每个类配置特定的“OnPropertyChanged”处理,以执行除设置指定字段之外的其他操作。

public static class PropertySpecifiedExtensions2
{
    /// <summary>
    /// Bind the <see cref="INotifyPropertyChanged.PropertyChanged"/> handler to automatically call each class's <see cref="IAutoNotifyPropertyChanged.Autonotify"/> method on the property name.
    /// </summary>
    /// <param name="entity">the entity to bind the autospecify event to</param>
    public static void Autonotify(this IAutoNotifyPropertyChanged entity)
    {
        entity.PropertyChanged += (me, e) => ((IAutoNotifyPropertyChanged)me).WhenPropertyChanges(e.PropertyName);
    }

    /// <summary>
    /// Create a new entity and <see cref="Autonotify"/> it's properties when changed
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public static T Create<T>() where T : IAutoNotifyPropertyChanged, new()
    {
        var ret = new T();
        ret.Autonotify();
        return ret;
    }
}

/// <summary>
/// Used by <see cref="PropertySpecifiedExtensions.Autonotify"/> to standardize implementation behavior
/// </summary>
public interface IAutoNotifyPropertyChanged : INotifyPropertyChanged
{
    void WhenPropertyChanges(string propertyName);
}

然后每个班级自己定义行为:

public partial class MyRandomClass: IAutoNotifyPropertyChanged
{

    /// <summary>
    /// Create a new empty instance and <see cref="PropertySpecifiedExtensions.Autospecify"/> its properties when changed
    /// </summary>
    /// <returns></returns>
    public static MyRandomClass Create()
    {
        return PropertySpecifiedExtensions2.Create<MyRandomClass>();
    }

    public void WhenPropertyChanges(string propertyName)
    {
        switch (propertyName)
        {
            case "field1": this.field1Specified = true; return;
            // etc
        }

        // etc
        if(propertyName.StartsWith(...)) { /* do other stuff */ }
    }
}

当然,缺点是,属性名称的魔术字符串会使重构变得困难,您可以通过Expression解析来解决这个问题吗?