Почему WPF, похоже, обходит атрибут TypeDescriptionProviderAttribute, когда реализован INotifyPropertyChanged?

#wpf #data-binding #inotifypropertychanged #icustomtypedescriptor #customtypedescriptor

#wpf #привязка к данным #inotifypropertychanged #icustomtypedescriptor #customtypedescriptor

Вопрос:

Я пытаюсь использовать [TypeDescriptionProviderAttribute] для того, чтобы присвоить моему классу дескриптор пользовательского типа. Это работает, но когда я внедряю INotifyPropertyChanged WPF, похоже, игнорирует пользовательский дескриптор типа и переходит прямо к свойству CLR (если оно существует). Вот фрагмент, я вставлю полный пример позже:

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

Я привязываю текстовый блок к свойству TheProperty. Когда I…

  • Оставьте все прокомментированным

    Я вижу «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 .

Вот полный код. Это немного длинновато, но большая часть этого не имеет значения, это просто требуется системой.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. Пока я пытаюсь выяснить причину проблемы (а значит, и решение …), мне любопытно узнать, чего вы на самом деле пытаетесь здесь достичь? Динамические свойства?

2. @AngelWPF — Это нелегко описать в нескольких словах, но я опубликую ссылку, когда напишу описание через день или два.

Ответ №1:

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

Проблема в системе.Windows.PropertyPath.ResolvePropertyName(строка, объект, тип, Object, логическое значение) частный метод. Я нашел это в PresentationFramework.dll в .NET 4.0.

Извлекается из .СЕТЕВОЙ отражатель:

 object propertyHelper = DependencyProperty.FromName(str, ownerType);
if ((propertyHelper == null) amp;amp; (item is ICustomTypeDescriptor))
{
    propertyHelper = TypeDescriptor.GetProperties(item)[str];
}
if ((propertyHelper == null) amp;amp; ((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) amp;amp; 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.Отражение для получения PropertyInfo,
  6. В противном случае генерируется исключение или возвращается значение null.

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

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