Почему WPF, кажется, обходит TypeDescriptionProviderAttribute, когда реализован INotifyPropertyChanged? - PullRequest
2 голосов
/ 13 ноября 2011

Я пытаюсь использовать [TypeDescriptionProviderAttribute], чтобы дать моему классу дескриптор нестандартного типа. Это работает, но когда я реализую INotifyPropertyChanged WPF, кажется, игнорирует дескриптор пользовательского типа и сразу переходит к свойству 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", как и ожидалось.

  • Используйте ICustomTypeDescriptor и INotifyPropertyChanged

    Я вижу "MyPropertyDescriptor - TheProperty", как и ожидалось.

  • Используйте [TypeDescriptionProvider] и INotifyPropertyChanged

    Я вижу "CLR - TheProperty". Почему это так? Странно то, что пользовательские свойства без свойства CLR отображаются нормально. Мой дескриптор пользовательского типа также возвращает «MyPropertyDescriptor - AnotherProperty», который работает во всех случаях, поскольку не определено CLR AnotherProperty.

В итоге, учитывая этот XAML

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

AnotherProperty всегда работает должным образом, поскольку модель не имеет свойства CLR с именем "AnotherProperty". 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;
    }
}

1 Ответ

8 голосов
/ 17 мая 2012

Старый вопрос, но для людей, которые ищут ответ ..

Проблема в закрытом методе System.Windows.PropertyPath.ResolvePropertyName (String, Object, Type, Object, Boolean).Я нашел его в PresentationFramework.dll в .NET 4.0.

Извлечено из .NET Reflector:

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. Если элемент реализует ICustomTypeDescriptor, используйте TypeDescriptor для получения PropertyDescriptor,
  3. Если элемент реализует INotifyPropertyChanged или DependencyObject, используйте System.Отражение для получения PropertyInfo,
  4. В противном случае используйте TypeDescriptor для получения PropertyDescriptor,
  5. В противном случае используйте System.Reflection для получения PropertyInfo,
  6. В противном случае выведите исключение или верните ноль.

Таким образом, System.Reflection / PropertyInfo получает приоритет над TypeDescriptor / PropertyDescriptor, если элемент реализует интерфейс INotifyPropertyChanged.Я полагаю, что они выбирают эту стратегию по соображениям производительности, потому что PropertyInfo намного легче, чем PropertyDescriptor.

Решением вашей проблемы было бы реализовать ICustomTypeDescriptor (предпочтительно явно), чтобы он передавал вызовы метода ICustomTypeDescriptor соответствующим вызовам метода TypeDescriptor, ноне с параметром объекта, а с параметром типа (this.GetType ()).Таким образом, ваш TypeDescriptionProvider будет использоваться.

...