2008-09-28 7 views
8

Je rencontre un problème inhabituel dans mes tests unitaires. La classe que je suis en train de tester crée dynamiquement une propriété de dépendance au moment de l'exécution et le type de cette propriété de dépendance peut varier en fonction des circonstances. Lors de l'écriture de mes tests unitaires, j'ai besoin de créer la propriété de dépendance avec différents types et cela conduit à des erreurs car vous ne pouvez pas redéfinir une propriété de dépendance existante.Un moyen de désinscrire une propriété de dépendance WPF?

Alors est-il possible de désinscrire une propriété de dépendance ou de modifier le type d'une propriété de dépendance existante?

Merci!


OverrideMetadata() ne vous permet de changer très peu de choses comme valeur par défaut il est pas utile. L'approche AppDomain est une bonne idée et pourrait fonctionner, mais semble plus compliquée que ce que je voulais vraiment approfondir pour des tests unitaires.

Je n'ai jamais réussi à annuler l'enregistrement d'une propriété de dépendance, j'ai donc réorganisé soigneusement mes tests unitaires pour éviter le problème. Je reçois un peu moins de couverture de test, mais comme ce problème ne se produira jamais dans une vraie application et seulement pendant les tests unitaires, je peux vivre avec.

Merci pour l'aide!

Répondre

8

J'ai eu problème similaire juste hier en essayant de tester ma propre DependencyProperty création de classe. Je suis tombé sur cette question, et j'ai remarqué qu'il n'y avait pas vraiment de solution pour supprimer les propriétés de dépendance. J'ai donc creusé en utilisant Red Gate .NET Reflector pour voir ce que je pouvais trouver. En regardant les surcharges DependencyProperty.Register, ils ont tous semblé pointer vers DependencyProperty.RegisterCommon. Cette méthode comporte deux parties:

pour vérifier si la propriété est déjà enregistré

FromNameKey key = new FromNameKey(name, ownerType); 
lock (Synchronized) 
{ 
    if (PropertyFromName.Contains(key)) 
    { 
    throw new ArgumentException(SR.Get("PropertyAlreadyRegistered", 
     new object[] { name, ownerType.Name })); 
    } 
} 

En second lieu, l'enregistrement DependencyProperty

DependencyProperty dp = 
    new DependencyProperty(name, propertyType, ownerType, 
    defaultMetadata, validateValueCallback); 

defaultMetadata.Seal(dp, null); 
//...Yada yada... 
lock (Synchronized) 
{ 
    PropertyFromName[key] = dp; 
} 

Les deux centres de pièces autour DependencyProperty.PropertyFromName, une table de hachage. J'ai également remarqué le DependencyProperty.RegisteredPropertyList, un ItemStructList<DependencyProperty> mais je n'ai pas vu où il est utilisé. Cependant, pour des raisons de sécurité, je me suis dit que j'essaierais de m'en débarrasser si possible. Donc je me suis retrouvé avec le code suivant qui m'a permis de "désinscrire" une propriété de dépendance.

private void RemoveDependency(DependencyProperty prop) 
{ 
    var registeredPropertyField = typeof(DependencyProperty). 
    GetField("RegisteredPropertyList", BindingFlags.NonPublic | BindingFlags.Static); 
    object list = registeredPropertyField.GetValue(null); 
    var genericMeth = list.GetType().GetMethod("Remove"); 
    try 
    { 
    genericMeth.Invoke(list, new[] { prop }); 
    } 
    catch (TargetInvocationException) 
    { 
    Console.WriteLine("Does not exist in list"); 
    } 

    var propertyFromNameField = typeof(DependencyProperty). 
    GetField("PropertyFromName", BindingFlags.NonPublic | BindingFlags.Static); 
    var propertyFromName = (Hashtable)propertyFromNameField.GetValue(null); 

    object keyToRemove = null; 
    foreach (DictionaryEntry item in propertyFromName) 
    { 
    if (item.Value == prop) 
     keyToRemove = item.Key; 
    } 
    if (keyToRemove != null) 
    propertyFromName.Remove(keyToRemove); 
} 

Cela a fonctionné assez bien pour moi d'exécuter mes tests sans obtenir une exception "AlreadyRegistered". Cependant, je vous recommande fortement de ne pas l'utiliser dans un code de production quelconque. Il y a probablement une raison pour laquelle MSFT a choisi de ne pas avoir un moyen formel de désinscrire une propriété de dépendance, et essayer de s'y opposer ne fait que demander des problèmes.

+1

Très bon! Tu as raison de dire que je ne voudrais pas l'utiliser dans le code de production mais un très beau travail de détective! –

0

Je ne pense pas que vous pouvez désenregistrer une propriété de dépendance, mais vous pouvez le redéfinir en remplaçant les métadonnées comme ceci:

MyDependencyProperty.OverrideMetadata(typeof(MyNewType), 
        new PropertyMetadata()); 
2

Si tout le reste échoue, vous pouvez créer un nouveau AppDomain pour chaque test .

0

Si nous enregistrons le nom d'un label comme celui-ci:

Label myLabel = new Label(); 
this.RegisterName(myLabel.Name, myLabel); 

Nous pouvons facilement désenregistrer le nom en utilisant:

this.UnregisterName(myLabel.Name); 
0

Je faisais face à un scénario où j'ai créé un contrôle personnalisé qui hérite de Selector qui est censé avoir deux propriétés ItemsSource, HorizontalItemsSource et VerticalItemsSource.

Je n'utilise même pas la propriété ItemsControl et je ne veux pas que l'utilisateur puisse y accéder. J'ai donc lu statenjason's great answer, et cela m'a donné un POV énorme sur la façon d'enlever un DP.
Cependant, mon problème était que depuis que je le membre ItemsSourceProperty déclaré et la ItemsSource comme Private Shadows (private new en C#), je ne pouvais pas le charger au moment de la conception puisque l'utilisation MyControlType.ItemsSourceProperty ferait référence à la variable ombré.
En outre, lors de l'utilisation de la boucle mentionnée dans est enswer ci-dessus (foreach DictionaryEntry etc.), une exception a été levée en disant que la collection a changé pendant l'itération.

donc je suis venu avec une approche légèrement différente où le DependencyProperty est hardcodedly refered lors de l'exécution, et la collection est copiée à un tableau il est donc pas changé (VB.NET, désolé):

Dim dpType = GetType(DependencyProperty) 
Dim bFlags = BindingFlags.NonPublic Or BindingFlags.Static 

Dim FromName = 
    Function(name As String, ownerType As Type) DirectCast(dpType.GetMethod("FromName", 
    bFlags).Invoke(Nothing, {name, ownerType}), DependencyProperty) 

Dim PropertyFromName = DirectCast(dpType.GetField("PropertyFromName", bFlags). 
    GetValue(Nothing), Hashtable) 

Dim dp = FromName.Invoke("ItemsSource", GetType(DimensionalGrid)) 
Dim entries(PropertyFromName.Count - 1) As DictionaryEntry 
PropertyFromName.CopyTo(entries, 0) 
Dim entry = entries.Single(Function(e) e.Value Is dp) 
PropertyFromName.Remove(entry.Key) 

Important note: le code ci-dessus est tout entouré dans le constructeur partagé du contrôle personnalisé, et je n'ai pas besoin de vérifier s'il est enregistré, car je sais qu'une sous-classe de Selcetor DOES fournit ItemsSource dp.

0

Problème avec un ContentPresenter avec des Datatemplates différents dont l'un d'entre eux avait un DependencyProperty avec un PropertyChangedCallback Lors du changement du contenu de ContentPresenters vers un autre DataTemplate, le rappel est resté.

Dans le cas UserControls i appelé Déchargées:

BindingOperations.ClearAllBindings(this); 
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, new DispatcherOperationCallback(delegate { return null; }), null); 

Cela a fonctionné pour moi

Questions connexes