6

我想摆脱模型类上占用空间和重复的 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; }
}

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

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

4

6 回答 6

6

我知道在“vanilla”C# 中没有简单且可维护的方法,但您可以通过aspect实现这一点。我为此使用了PostSharp,它的缺点是作为付费的 3rd 方产品,但有一个免费版本,您也可以在其中执行此操作。PostSharp 利用目标指定、继承等属性的优势,并将它们扩展到方面。

然后,您可以定义一个LocationInterceptionAspect,它覆盖OnSetValue调用您的RaisePropertyChanged委托的方法。然后你可以使用用你的 aspect 属性装饰的自动生成的属性。

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

于 2012-12-01T23:06:58.503 回答
3

我来到了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 的东西),但仍然没有用所有这些东西在你的课堂上乱扔垃圾。它为依赖于属性的只读属性引发通知。

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

这里有更多信息:链接

于 2012-12-02T10:56:01.607 回答
2

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

使用 Postsharp 的免费版本。

通过使用以下代码,我们可以仅将虚拟属性绑定到视图。

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);                    
        }
    }
}
}
于 2013-07-27T20:19:54.100 回答
0

Coming from the other side (and if you don't have a fancy Extension) you can "autospecify" changed properties with the extension methods outlined in my answer here: WCF service proxy not setting "FieldSpecified" property

Specifically, you could use the 'non-reflection method' to configure specific "OnPropertyChanged" handling per class to do other things than just set a specified field.

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

And then each class themselves defines the behavior:

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 */ }
    }
}

The downside to this is, of course, magic strings for property names making refactoring difficult, which you could get around with Expression parsing?

于 2014-01-15T18:40:12.057 回答
0

我在命名空间中找到了这个类System.Dynamic......它可以让您拦截绑定目标上的实际DataBinding调用,以及绑定源上的。DependencyObjectProperty

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

所以现在可以做的是实现一个类(让我们称之为DynamicNpcProxy),它实现INotifyPropertyChanged、派生DynamicObject并覆盖了TryGetMemberTrySetMember方法。

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(...)
}

为了让它工作,将代理包裹在你当前的绑定源周围,替换它们,然后让DynamicObject剩下的事情去做。

在 ViewModel.cs 中:

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

在 View.xaml 中:

<TextBox Text="{Binding Products.CurrentItem.Name}" /> 
<TextBox Text="{Binding Products.CurrentItem.Description}" /> 

你最终得到的是:

另请查看这篇文章code project其中提供了更多信息......

于 2012-12-02T02:42:51.397 回答
0

这已经是老东西了,只是没有人提到:

https://marketplace.visualstudio.com/items?itemName=AlexeyLavnikov.KindOfMagic

您可以使用类上的 1 个属性为 ViewModel 中的每个属性打开自动通知。

于 2017-01-31T14:00:24.140 回答