TypeConverter для универсального типа, используемого в xaml - PullRequest
3 голосов
/ 12 ноября 2009

Я смотрю на инициализацию членов универсальных типов, объявленных в XAML. Это нацелено на поддержку (уменьшенная) обобщенных шаблонов в WPF 4 и будущем Silverlight. (Я пробовал приведенные ниже сценарии с использованием x:TypeArguments и XamlReader.Load в VS2010 Beta 2, но для простоты буду использовать TestClassInt32 : TestClass<int> { }, так как он работает так же, как и с использованием универсального типа напрямую, но его проще протестировать с помощью скомпилированного xaml сегодня.)


Вот типы тестов, которые я использую.

public class TestClass<T> {
  [TypeConverter( typeof(StringListToItemsConverter) )]
  public IEnumerable<T> Items { get; set; }
  public TestProperty<T> Property { get; set; }
}

[TypeConverter( typeof(TestPropertyConverter) )]
public struct TestProperty<T> {
  public TestProperty( T value ) : this() { Value = value; }
  public T Value { get; }
}

Вот пример сценария.

<StackPanel>
  <StackPanel.DataContext>
    <test:TestClassInt32 Items="1,2,3" Property="6" />
  </StackPanel.DataContext>

  <TextBox Text="{Binding Property.Value}" />
  <ItemsControl ItemsSource="{Binding Items}" />
</StackPanel>

Когда я жестко кодирую typeof(int) в преобразователях для этого примера, все работает нормально, но этот подход, очевидно, не работает для TestClass<double> или TestClass<DateTime>. Проблема в том, что метод TypeConverter.ConvertFrom не имеет доступа к типу назначения, только к типу источника. (Это не было проблемой, когда TypeConverter был создан в .NET 1.0, потому что тип назначения не мог быть параметризован, но сейчас это нежелательное ограничение.)


Вот подходы, которые я рассмотрел, чтобы обойти эту проблему:

  1. Сделать конвертер типов общим; например [TypeConverter( typeof(TestPropertyConverter<T>) )]
    • .NET не поддерживает параметры типа в атрибутах
    • Пусть TypeConverter возвращает промежуточный тип, который либо реализует IConvertible.ToType, либо имеет TypeConvert, который может ConvertTo тип назначения
    • Парсер XAML выполняет только одношаговое преобразование: если возвращаемый объект не может быть назначен месту назначения, он генерирует исключение
    • Определите пользовательский дескриптор типа, который будет возвращать соответствующий конвертер на основе фактического типа
    • WPF игнорирует дескрипторы пользовательских типов, а TypeDescriptor даже не существует в Silverlight

Вот альтернативы, которые я предложил для «обхода» этой проблемы:

  1. Требовать, чтобы тип назначения был встроен в строку; например "sys:Double 1,2,3"
    • В Silverlight необходимо жестко закодировать фиксированный набор поддерживаемых типов (в WPF можно использовать интерфейс IXamlTypeResolver для получения фактического типа, которому "sys:Double" соответствует)
    • Написать пользовательское расширение разметки, которое принимает параметр типа; например "{List Type={x:Type sys:Double}, Values=1,2,3}"
    • Silverlight не поддерживает настраиваемые расширения разметки (также не поддерживается расширение разметки x:Type, но вы можете использовать жестко заданный подход из варианта 1)
    • Создайте тип оболочки, который принимает параметр типа и переопределяет все универсальные члены как object, перенаправляя все обращения к элементам в базовый строго типизированный объект
    • Возможно, но создает очень плохой пользовательский интерфейс (приходится приводить для получения базового универсального объекта, все равно приходится использовать жестко закодированный список для параметра типа в Silverlight, приходится кэшировать назначения членов до тех пор, пока не будет назначен аргумент типа, и т. Д. и т. д .; обычно теряет большинство преимуществ строгой типизации с помощью дженериков)

Был бы рад услышать любые другие идеи для решения этой проблемы сегодня или в WPF 4.

Ответы [ 2 ]

4 голосов
/ 12 ноября 2009

В .NET 4 есть IServiceProvider, через который вы можете получить IDestinationTypeProvider, и тогда вы сможете делать то, что вам нужно. В .NET 3 или 4 служба IProvideValueTarget может предоставить вам targetObject и targetProperty. Из targetProperty (a PropertyInfo, MethodInfo для прикрепленного или DependencyProperty) вы можете получить тип.

Чтобы получить IDestinationTypeProvider поставщика услуг из ConvertFrom из TypeConverter, например:

public override object ConvertFrom(
    ITypeDescriptorContext context,
    CultureInfo culture,
    object value )
{
    var typeProvider =
      (IDestinationTypeProvider)context.GetService( typeof( IDestinationTypeProvider ) );
    Type targetType = typeProvider.GetDestinationType();

    // ... do stuff

    return base.ConvertFrom( context, culture, value );
}
1 голос
/ 14 февраля 2015

Ответ Роба Рельея работает, но, к сожалению, зависит от услуг, предоставляемых XAML , и, следовательно, не будет работать для других ITypeDescriptorContext. Это также означает, что вам нужно ссылаться на System.Xaml. Это нежелательно для определений типов в сборках, которые не должны ссылаться на System.Xaml.

Поэтому я решил вместо этого применить специальный TypeConverter к моему универсальному типу, который перенаправляет его реализацию на преобразователь, который загружается через TypeDescriptor. Это позволяет другим сборкам (например, используя XAML) указывать преобразователь типа, который будет загружен во время выполнения, используя:

TypeDescriptor.AddAttributes(
  typeof( SomeGenericType<> ),
  new TypeConverterAttribute( typeof( SomeGenericTypeConverter ) ) );

Я создал a RedirectTypeConverter базовый класс для таких преобразователей. Ключевые компоненты кода, необходимые для реализации этого:

protected RedirectTypeConverter( Type type )
{
    _type = type;
}

// Other methods are implemented similarly.
public override object ConvertFrom(
    ITypeDescriptorContext context,
    CultureInfo culture,
    object value )
{
    InitializeConverter();
    return _converter.ConvertFrom( context, culture, value );
}

public void InitializeConverter()
{
    if ( _converter != null )
    {
        return;
    }

    _converter = TypeDescriptor.GetConverter( _type );
    if ( _converter.GetType() == GetType() )
    {
        string message = string.Format(
          "Conversion failed. Converter for {0} is missing in TypeDescriptor.", _type );
        throw new InvalidOperationException( message );
    }
}

A Полная запись и расширенное обсуждение доступны в моем блоге .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...