2

我正在尝试使用[TypeDescriptionProviderAttribute],以便为我的类提供自定义类型描述符。这可行,但是当我实现INotifyPropertyChangedWPF 时似乎忽略了自定义类型描述符并直接使用 CLR 属性(如果它存在)。这是一个片段,稍后我将粘贴完整的示例:

//[TypeDescriptionProvider(typeof(MyProvider))]
class MyModel : Object
    //, INotifyPropertyChanged
    //, ICustomTypeDescriptor
{
    public string TheProperty { get { return "CLR - TheProperty"; } }

我将 TextBlock 绑定到 TheProperty。当我...

  • 留下所有评论

    我按预期看到“CLR - TheProperty”。

  • 采用[TypeDescriptionProvider]

    我按预期看到“MyPropertyDescriptor - TheProperty”。

  • 采用ICustomTypeDescriptor

    我按预期看到“MyPropertyDescriptor - TheProperty”。

  • 使用ICustomTypeDescriptorINotifyPropertyChanged

    我按预期看到“MyPropertyDescriptor - TheProperty”。

  • 使用[TypeDescriptionProvider]INotifyPropertyChanged

    我看到“CLR - TheProperty”。为什么是这样?奇怪的是,没有CLR 属性的自定义属性会正常显示。我的自定义类型描述符还返回一个“MyPropertyDescriptor - AnotherProperty”,它适用于所有情况,因为没有AnotherProperty定义 CLR。

总之,鉴于此 XAML

<StackPanel>
    <TextBlock Text="{Binding TheProperty}" />
    <TextBlock Text="{Binding AnotherProperty}" />
</StackPanel>

AnotherProperty始终按预期工作,因为该模型没有名为“AnotherProperty”的 CLR 属性。TheProperty按预期工作,除非同时使用和[TypeDescriptionProvider]INotifyPropertyChanged

这是完整的代码。它有点长,但大部分都无关紧要,它只是 System.ComponentModel 需要的

public partial class TestWindow : Window
{
    public TestWindow()
    {
        InitializeComponent();
        DataContext = new MyModel();
    }
}

//[TypeDescriptionProvider(typeof(MyProvider))]
class MyModel : Object
    //, INotifyPropertyChanged
    //, ICustomTypeDescriptor
{
    public string TheProperty { get { return "CLR - TheProperty"; } }

    public event PropertyChangedEventHandler PropertyChanged;

    public AttributeCollection GetAttributes()
    {
        return TypeDescriptor.GetAttributes(this);
    }

    public string GetClassName()
    {
        return TypeDescriptor.GetClassName(this);
    }

    public string GetComponentName()
    {
        return TypeDescriptor.GetComponentName(this);
    }

    public TypeConverter GetConverter()
    {
        return TypeDescriptor.GetConverter(this);
    }

    public EventDescriptor GetDefaultEvent()
    {
        return TypeDescriptor.GetDefaultEvent(this);
    }

    public PropertyDescriptor GetDefaultProperty()
    {
        return TypeDescriptor.GetDefaultProperty(this);
    }

    public object GetEditor(Type editorBaseType)
    {
        return TypeDescriptor.GetEditor(this, editorBaseType);
    }

    public EventDescriptorCollection GetEvents(Attribute[] attributes)
    {
        return TypeDescriptor.GetEvents(this, attributes);
    }

    public EventDescriptorCollection GetEvents()
    {
        return TypeDescriptor.GetEvents(this);
    }

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        return TypeDescriptor.GetProperties(this, attributes);
    }

    public PropertyDescriptorCollection GetProperties()
    {
        return MyTypeDescriptor.GetCustomProperties();
    }

    public object GetPropertyOwner(PropertyDescriptor pd)
    {
        return this;
    }
}


class MyProvider : TypeDescriptionProvider
{
    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
    {
        return new MyTypeDescriptor();
    }
}


class MyTypeDescriptor : CustomTypeDescriptor
{
    public override PropertyDescriptorCollection GetProperties()
    {
        return GetCustomProperties();
    }

    public static PropertyDescriptorCollection GetCustomProperties()
    {
        return new PropertyDescriptorCollection(
            new[] { 
                new MyPropertyDescriptor("TheProperty"),
                new MyPropertyDescriptor("AnotherProperty")
            });
    }
}


class MyPropertyDescriptor : PropertyDescriptor
{
    public MyPropertyDescriptor(string propName)
        : base(propName, null)
    {
    }

    public override bool CanResetValue(object component)
    {
        return false;
    }

    public override Type ComponentType
    {
        get { return typeof(MyModel); }
    }

    public override object GetValue(object component)
    {
        return "MyPropertyDescriptor - " + Name;
    }

    public override bool IsReadOnly
    {
        get { return true; }
    }

    public override Type PropertyType
    {
        get { return typeof(string); }
    }

    public override void ResetValue(object component)
    {
        throw new InvalidOperationException("cannot reset value");
    }

    public override void SetValue(object component, object value)
    {
        throw new InvalidOperationException("property is readonly");
    }

    public override bool ShouldSerializeValue(object component)
    {
        return true;
    }
}
4

1 回答 1

8

老问题,但对于寻找答案的人来说..

问题出在 System.Windows.PropertyPath.ResolvePropertyName(String, Object, Type, Object, Boolean) 私有方法中。我在 .NET 4.0 的 PresentationFramework.dll 中找到了它。

从 .NET 反射器中提取:

object propertyHelper = DependencyProperty.FromName(str, ownerType);
if ((propertyHelper == null) && (item is ICustomTypeDescriptor))
{
    propertyHelper = TypeDescriptor.GetProperties(item)[str];
}
if ((propertyHelper == null) && ((item is INotifyPropertyChanged) || (item is DependencyObject)))
{
    propertyHelper = this.GetPropertyHelper(ownerType, str);
}
if (propertyHelper == null)
{
    propertyHelper = TypeDescriptor.GetProperties(item)[str];
}
if (propertyHelper == null)
{
    propertyHelper = this.GetPropertyHelper(ownerType, str);
}
if ((propertyHelper == null) && throwOnError)
{
    throw new InvalidOperationException(SR.Get("PropertyPathNoProperty", new object[] { ownerType.Name, str }));
}
return propertyHelper;

如您所见,检索属性标识符(DependencyProperty / PropertyDescriptor / PropertyInfo)如下所示:

  1. 尝试获取 DependencyProperty,
  2. 如果item实现了ICustomTypeDescriptor,使用TypeDescriptor获取PropertyDescriptor,
  3. 如果item实现了INotifyPropertyChanged或者是DependencyObject,使用System.Reflection获取PropertyInfo,
  4. 否则使用 TypeDescriptor 获取 PropertyDescriptor,
  5. 否则使用 System.Reflection 获取 PropertyInfo,
  6. 否则抛出异常或返回 null。

因此,如果项目实现 INotifyPropertyChanged 接口,则 System.Reflection/PropertyInfo 优先于 TypeDescriptor/PropertyDescriptor。我相信他们选择这种策略是出于性能原因,因为 PropertyInfo 比 PropertyDescriptor 轻得多。

您的问题的解决方案是实现 ICustomTypeDescriptor(最好是显式地),以便将 ICustomTypeDescriptor 方法调用转移到适当的 TypeDescriptor 方法调用,但不是使用对象参数,而是使用类型参数 (this.GetType())。这样您的 TypeDescriptionProvider 将被使用。

于 2012-05-17T11:46:22.737 回答