Un moyen de désenregistrer une propriété de dépendance WPF?
-
02-07-2019 - |
Question
Je rencontre un problème inhabituel dans mes tests unitaires. La classe que je teste crée une propriété de dépendance de manière dynamique 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, je dois créer la propriété de dépendance avec différents types, ce qui entraîne des erreurs car vous ne pouvez pas redéfinir une propriété de dépendance existante.
Existe-t-il un moyen de désenregistrer 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 modifier que très peu de choses, comme la valeur par défaut, afin que cela ne soit pas utile. L’approche AppDomain est une bonne idée et peut fonctionner, mais elle semble plus compliquée que ce que je voulais vraiment approfondir pour le test des unités.
Je n'ai jamais trouvé le moyen d'annuler l'enregistrement d'une propriété de dépendance. J'ai donc réorganisé et réorganisé avec soin mes tests unitaires pour éviter le problème. Je reçois un peu moins de tests, mais comme ce problème ne se poserait jamais dans une application réelle et que je ne vivrais que lors des tests unitaires.
Merci pour l'aide!
La solution
J'avais un problème similaire hier encore lorsque j'ai essayé de tester ma propre classe de création DependencyProperty. Je suis tombé sur cette question et j'ai remarqué qu'il n'y avait pas de solution réelle pour annuler les propriétés de dépendance. J'ai donc creusé avec le réflecteur Red Gate .NET pour voir ce que je pouvais trouver. avec.
En regardant les DependencyProperty.Register
surcharges, elles semblaient toutes pointer vers DependencyProperty.RegisterCommon
. Cette méthode comporte deux parties:
Tout d'abord pour vérifier si la propriété est déjà enregistrée
FromNameKey key = new FromNameKey(name, ownerType);
lock (Synchronized)
{
if (PropertyFromName.Contains(key))
{
throw new ArgumentException(SR.Get("PropertyAlreadyRegistered",
new object[] { name, ownerType.Name }));
}
}
Deuxièmement, enregistrer le DependencyProperty
DependencyProperty dp =
new DependencyProperty(name, propertyType, ownerType,
defaultMetadata, validateValueCallback);
defaultMetadata.Seal(dp, null);
//...Yada yada...
lock (Synchronized)
{
PropertyFromName[key] = dp;
}
Les deux éléments sont centrés autour de DependencyProperty.PropertyFromName
, une table de hachage. J'ai aussi remarqué le DependencyProperty.RegisteredPropertyList
, un ItemStructList<DependencyProperty>
mais je n'ai pas vu où il est utilisé. Cependant, pour des raisons de sécurité, j’ai pensé que j’essayerais d’en retirer autant que possible.
Je me suis donc 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 que je puisse exécuter mes tests sans obtenir un & "déjà enregistré &"; exception. Cependant, je vous recommande vivement de ne pas utiliser cette méthode dans aucun type de code de production. Il existe probablement une raison pour laquelle MSFT a choisi de ne pas disposer d'un moyen formel pour annuler l'enregistrement d'une propriété de dépendance, et d'essayer d'aller à l'encontre de c'est juste demander des ennuis.
Autres conseils
Si tout échoue, vous pouvez créer un nouveau domaine d'application pour chaque test.
Je ne pense pas que vous puissiez annuler l'enregistrement d'une propriété de dépendance, mais vous pouvez la redéfinir en surchargeant les métadonnées comme suit:
MyDependencyProperty.OverrideMetadata(typeof(MyNewType),
new PropertyMetadata());
Si nous enregistrons le nom d'une étiquette comme celle-ci:
Label myLabel = new Label();
this.RegisterName(myLabel.Name, myLabel);
Nous pouvons facilement annuler l’enregistrement du nom en utilisant:
this.UnregisterName(myLabel.Name);
Je faisais face à un scénario dans lequel 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.
Je lis donc La grande réponse de statenjason , et ça m’a donné un énorme point de vue sur la façon de retirer un DP.
Cependant, mon problème était que, puisque j'avais déclaré le membre ItemsSourceProperty
et le ItemsSource
<<> (Private Shadows
en C #), je ne pouvais pas le charger au moment de la conception, car private new
ferait référence à variable ombrée.
De plus, lors de l’utilisation de la boucle mentionnée dans est ci-dessus (MyControlType.ItemsSourceProperty
etc.), une exception a été émise indiquant que la collection a été modifiée au cours de l’itération.
Par conséquent, j’ai proposé une approche légèrement différente dans laquelle DependencyProperty est référencé en dur au moment de l’exécution, et la collection est copiée dans un tableau de sorte que rien n’a changé (VB.NET, excusez-moi):
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)
Remarque importante: le code ci-dessus est entouré du constructeur partagé du contrôle personnalisé. Je n'ai pas à vérifier s'il est enregistré, car je sais qu'une sous-classe de Selcetor Fournit que foreach DictionaryEntry
dp.
Avait un problème avec un ContentPresenter avec différents modèles de données où l'un d'entre eux avait un DependencyProperty avec un PropertyChangedCallback Lorsque vous modifiez le contenu de ContentPresenters en un autre DataTemplate, le rappel est conservé.
Dans l'événement UserControls Unloaded que j'ai appelé:
BindingOperations.ClearAllBindings(this);
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, new DispatcherOperationCallback(delegate { return null; }), null);
Cela a fonctionné pour moi