2009-11-12 4 views
3

Je suis en train d'examiner l'initialisation des membres de types génériques déclarés en XAML. Ceci cible le (reduced) generics support dans WPF 4 et le futur Silverlight. (Je l'ai essayé les scénarios ci-dessous à l'aide x:TypeArguments et XamlReader.Load dans VS2010 Beta 2, mais utilisera TestClassInt32 : TestClass<int> { } pour simplifier, car il a le même comportement que directement en utilisant le type générique, mais il est plus facile à tester en utilisant compilé XAML aujourd'hui.)TypeConverter pour le type générique utilisé dans xaml


Voici les types de tests que j'utilise.

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; } 
} 

Voici l'exemple de scénario.

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

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

Quand je coder en dur typeof(int) dans les convertisseurs pour cet exemple, tout fonctionne bien, mais cette approche ne fonctionne évidemment pas TestClass<double> ou TestClass<DateTime>. Le problème est que la méthode TypeConverter.ConvertFrom n'a pas accès au type de destination, seul le type de source. (Ce ne fut pas un problème quand TypeConverter a été créé dans .NET 1.0, car le type de destination ne peut pas être paramétrés, mais est une limitation malheureuse maintenant.)


Voici les démarches que je l'ai regardé pour se déplacer ce problème:

  1. Rendre le convertisseur de type générique; par exemple. [TypeConverter(typeof(TestPropertyConverter<T>))]
    • .NET ne prend pas en charge les paramètres de type dans les attributs
    • ont le TypeConverter retourner un type intermédiaire qui soit met en oeuvre IConvertible.ToType ou has a TypeConvert that can ConvertTo the destination type
    • analyseur XAML ne fait que les conversions en une seule étape: si l'objet retourné ne peut pas être affecté à la destination, il jette une exception
    • Définir un descripteur de type personnalisé qui renverra le convertisseur approprié en fonction du type réel
    • WPF ne tient pas compte des descripteurs de type personnalisé, et TypeDescriptor n'existe même pas dans Silverlight

Voici les alternatives que je suis venu avec pour « travailler autour de » ce numéro:

  1. Exiger le type de destination à incorporer dans la chaîne; par exemple. "sys:Double 1,2,3"
    • Dans Silverlight, doivent coder en dur un ensemble fixe de types pris en charge (en WPF, peut utiliser l'interface IXamlTypeResolver pour obtenir le type réel qui "sys:Double" correspond à)
    • Ecrire une extension de balisage personnalisé qui prend paramètre de type; par exemple."{List Type={x:Type sys:Double}, Values=1,2,3}"
    • Silverlight ne prend pas en charge les extensions personnalisées de balisage (il ne supporte pas l'extension de balisage x:Type, mais vous pouvez utiliser l'approche codée en dur de l'option 1)
    • Créer un type d'emballage qui prend un paramètre de type et réémergentes définit tous les membres génériques comme object, en transmettant tous les accès membres à l'objet sous-jacent fortement typé
    • Possible, mais en fait une expérience utilisateur très médiocre (doit être castée pour obtenir un objet générique sous-jacent, doit toujours utiliser un codage en dur) liste pour le paramètre de type sur Silverlight, doit mettre en cache des assignations de membre jusqu'à ce que l'argument de type soit assigné, etc., etc, perd généralement la plupart des avantages de typage fort avec des génériques)

serait heureux d'entendre d'autres idées pour contourner ce problème aujourd'hui, ou WPF 4.

Répondre

4

Dans .NET 4, il y a un IServiceProvider disponible à travers lequel vous pouvez obtenir IDestinationTypeProvider, et alors vous devriez être capable de faire ce dont vous avez besoin. Dans .NET 3 ou 4, le service IProvideValueTarget peut vous donner le targetObject et targetProperty. De la propriété targetProperty (un PropertyInfo, MethodInfo -pour le joint, ou un DependencyProperty), vous pouvez obtenir le type.

Pour obtenir le fournisseur de services IDestinationTypeProvider à l'intérieur ConvertFrom d'un TypeConverter par exemple:

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); 
} 
+0

Ouais, bien sûr, vous pouvez avoir plus impressionnant ... mais pas pour 4 mois. Je ne peux pas attendre .Net 4. –

+0

J'avais testé le service 'IProvideValueTarget' plus tôt dans VS2008, mais' TargetObject' et 'TargetProperty' renvoyaient toujours' null' (et le gros "ce type est utilisé en interne" dans le la documentation m'a amené à croire qu'il n'était pas utilisable). Je les ai re-testé dans VS2010/.NET 4 tout à l'heure et les deux ont retourné des résultats utiles. Je vous remercie! –

+0

J'ai ajouté des informations et un exemple de code sur la façon d'obtenir le fournisseur de services qui manquait. Si je dis quelque chose d'incorrect ici, n'hésitez pas à revenir en arrière. –

1

Rob Relyea's answer œuvres, mais malheureusement repose sur les services fournis par XAML et sera donc pas travailler pour d'autres ITypeDescriptorContext « s. Cela implique également que vous devez faire référence à System.Xaml. Ceci n'est pas souhaitable pour les définitions de type dans les assemblages qui ne doivent pas faire référence à System.Xaml.

J'ai donc opté à la place pour appliquer un TypeConverter spécial à mon type générique qui redirige son implémentation vers un convertisseur qui est chargé via TypeDescriptor. Cela permet à d'autres ensembles (par exemple en utilisant XAML) pour spécifier le convertisseur de type pour être chargé lors de l'exécution en utilisant:

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

I créé a RedirectTypeConverter base class pour de tels convertisseurs. Les éléments clés du code requis pour mettre en œuvre cette:

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 full writeup and extended discussion is available on my blog.

Questions connexes