我想摆脱模型类中占用空间和重复性的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; }
}
这可以通过某种代理类来实现吗?
我想避免为每个模型类编写代理。
答案 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)
答案 4 :(得分:0)
我在System.Dynamic
命名空间中找到了this class ...它可以拦截绑定目标上DataBinding
发出的实际DependencyObject
次调用{} 1}}在您的绑定源上。
所以现在可以做的是实现一个实现Property
的类(让我们称之为DynamicNpcProxy
),它来自INotifyPropertyChanged
并覆盖DynamicObject
和TryGetMember
方法。
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
解析来解决这个问题吗?