J'ai deux widgets WPF (à partir de la standard) Widgets A et B. Lorsque je modifie certaines biens d'une propriété, il doit être défini sur B, lorsqu'il est modifié dans B, il doit être défini sur un. P>
Maintenant, j'ai cette nouvelle récursion laid -> Je change A, alors changements de code B, mais depuis B est modifié, cela change A, il change donc de ... vous avez la photo. P>
Comment éviter cette récursive la méthode la plus "standard"? La suppression naïve et l'ajout de gestionnaires d'événements ne fonctionne pas et ne vérifie pas si la nouvelle valeur est identique à celle de la valeur ancienne n'est pas applicable ici (en raison de la fluctuation du calcul - je ne définit pas la même valeur à A et B, mais transformée) . P>
J'essaie toujours de mettre des informations minimales sur le problème pour éviter toute confusion. Cependant, cela pourrait aider p>
malgré le titre "déclenchement récursif", les gestionnaires sont appelés séquentiellement, de sorte que vous disposez de la séquence d'entrée-sortie-sortie-sortie-sortie, pas d'entrée-entrée-entrée-entrée-EXIT-EXIT-EXIT-EXIT-EXIT-EXIT-EXIT / p>
et le dernier, probablement le moins important, mais néanmoins p> li>
A et B (dans ce cas) sont des valeurs de défilement et j'essaie de maintenir proportionnellement la même position pour les deux. Le projet (de Karin Huber) est ici: http://www.codeproject.com/kb/wpf/scrollsynchronization.aspx p>
L'idée de bloquer les événements est si populaire que j'ai ajouté la séquence de déclencher les événements, nous allons ici: P>
Comme vous le voyez, c'est inutile. P>
7 Réponses :
plutôt que d'augmenter les événements, refacteur votre code de sorte que les gestionnaires d'événements pour A et B appellent une autre méthode pour effectuer le travail réel.
private void EventHandlerA(object sender, EventArgs e) { ChangeA(); ChangeB(); } private void EventHandlerB(object sender, EventArgs e) { ChangeB(); ChangeA(); }
Il n'est pas faisable - il s'agit de WPF, A et B sont des widgets et les widgets déclenchent en interne.
Mais les événements sont accrochés aux manipulateurs d'événements et vous pouvez sûrement influencer ce qui se passe à l'intérieur de ces i>, ne pouvez-vous pas?
Chrisien, bien sûr, mais je ne peux que appeler ce qui est exposé par designer du widget. Donc, une fois que je change, il déclenche l'événement, et cela déclenche mes manutentionnaires ... et nous avons "récursivité" (peut-être que je devrais mettre cela en devise à l'accent, ce n'est pas une vraie récursive).
Tout d'abord, je penserais à la conception parce que ces dépendances circulaires sont souvent un signe de mauvaise conception.
Cependant, il pourrait y avoir des situations dans lesquelles de telles dépendances sont la seule façon d'aller. Dans ces cas, je suggère d'utiliser des drapeaux privés indiquant si un changement de B a été causé par un changement de A. Quelque chose comme ceci ( mis à jour fort>): p> La même approche doit être utilisée dans la classe B. Toutefois, cette approche ne traite pas les variations de plusieurs threads. Si A ou B pourrait être changé de multiples threads en même temps, vous devrez utiliser des techniques appropriées pour éviter que les modifications de propriété soient perdues. P> P>
J'ai ajouté l'accent sur le fait que ce sont les widgets WPF, pas la mienne - c'est-à-dire de l'ensemble standard et je viens de les utiliser. Seuls les gestionnaires d'événements sont à moi, le reste n'est pas personnalisé WPF. Et cela ne fonctionne pas, car le déclenchement récursif n'est que notre illusion - les événements sont traités de manière séquentielle.
Cela n'a pas d'importance. En fin de compte, les situations problématiques sont lorsque votre code i> change les propriétés. Peut-être que mon exemple de code ne correspond pas à cette situation complètement. Mais l'idée d'avoir un drapeau indiquant que le changement de valeur a été initié par votre code fonctionnerait toujours.
Je ne travaille pas - voir la "trace" de la manière dont les événements sont traités. Cela fonctionnerait bien, si les événements seraient traités immédiatement juste après le changement. Mais ils ne le sont pas, votre drapeau est restauré, puis le gestionnaire est encore appelé.
Ok, maintenant je vois le point. Savez-vous pourquoi les gestionnaires ne sont pas appelés immédiatement? Le changement de propriété est-il exécuté sur un autre fil?
Oui, ces propriétés sont de widgets WPF, ce qui est un fil spécifique. D'ailleurs. Autant de contournement, il serait possible de stocker l'info que les événements doivent être bloqués (indépendamment du WPF). Ainsi, lorsqu'une modifie B, elle doit ajouter à "Liste ignorée" B, et lorsque le gestionnaire B est exécuté, il devrait se retirer de cette liste. Donc, l'appelant s'ajoute toujours, mais enlevez tous les autres.
Oui, pensé à quelque chose de similaire. Cependant, cela ne se sent pas comme une solution propre pour moi. Il semble que cela puisse devenir rapidement problématique ...
La solution Chrisfs est probablement la voie à suivre, mais parfois, nous supprimons l'événement, apportons le changement et ajoutons le gestionnaire d'événements:
Imaginez un DataContext, avec l'événement DataContextChanged: P>
DataContextChanged -= OnDataContextChanged; DataContext = new object(); DataContextChanged += OnDataContextChanged;
J'ai déjà écrit - cela ne fonctionne pas. Les événements sont tirés de manière séquentielle, la récursion n'est qu'une illusion, car les événements sont mis en file d'attente.
Pourquoi cela ne fonctionne-t-il pas? Si vous savez B, vous invoquerez A, vous pourriez décomposer un dans B, faites vos affaires et ReHook A.
Parce que les gestionnaires d'événements ne sont pas ré-entrants. Ils sont traités dans la séquence - j'ai ajouté l'exemple dans le poste maintenant.
Je ne vous ai pas suggéré de supprimer le gestionnaire d'un dans le gestionnaire A. J'ai suggéré de retirer le gestionnaire A dans le gestionnaire de B: # Je change le # un gestionnaire est appelé # je désactive le gestionnaire de b # je change B (c'est Stocké, mais non déclenché) # Je active le gestionnaire du gestionnaire B # B, espérons-le, ne sera pas appelé. Sinon, envisagez de faire l'évolution d'un ouvrier d'arrière-plan.
En fait, j'ai fait plus - je supprime tous les gestionnaires :-) Mais cela n'a pas fonctionné (et après avoir traçable l'appel, je sais pourquoi).
et vérifier si la nouvelle valeur est identique à celle de l'ancienne valeur n'est pas applicable ici (en raison de la fluctuation du calcul - je ne définit pas la même valeur à A et B, mais transformée). P>
Alors, ce que vous dites, c'est que quelque chose comme ça arrive: p>
a.foo code> est défini sur x fort>. li>
a_foochanged code> définit
b.bar code> à f ( x fort>). LI>
B_BRARCHANGED CODE> SETS
A.FOO CODE> TO G (F (
X STROND>)), qui n'est pas x strong>. li> a_foochanged code> définit
b.bar code> à f (g (f (
x fort>))). Li> ol> et ainsi de suite. Est-ce correct? Parce que si g (f ( x fort>) est em> x fort>, alors la solution est simple:
b_barchanged code> ne doit que définir < code> a.foo code> si
a.foo code>! = g (f (
x fort>). p> supposant que cette récursive n'a pas de calculable État final, puis les gestionnaires d'événements ont besoin d'une manière de connaître le contexte dans lequel les événements qu'ils manipulent ont été déclenchés. Vous ne pouvez pas obtenir ces informations du protocole d'événement normal, car les événements sont conçus pour découpler des opérations que cette conception couplait . p>
On dirait que vous avez besoin d'une manière hors bande pour que ces commandes se signent mutuellement. Cela pourrait être aussi simple que d'utiliser un
hashset
code> c'est un Propriété de la fenêtre code>. Je considérerais quelque chose comme ceci: p>
xxx pré> ceci rompre si un ensemble
b.bar code >, et b ensembles
c.baz code> et c ensembles
a.foo code>, bien que je soupçonne que les exigences elles-mêmes se décomposent si cela se produit. Dans ce cas, vous Il suffit de recourir à une trace de pile. Ce n'est pas jolie, mais alors rien de ce problème est joli. P> blockquote>
Merci pour l'idée! A propos des fonctions, oui g (f (x)) ne doit pas être x (cela peut, mais je ne peux pas l'assumer). J'ai pensé au dictionnaire des objets et des événements, mais j'ai abandonné cette façon - le problème est quand g (f (x)) est b> x (je ne peux pas le prédire, pas de manière propre au moins ). Les événements sont déclenchés lorsque la nouvelle valeur est différente de celle de l'ancien - et avec ce dictionnaire, j'attendrai un événement à venir, mais cela ne viendra jamais parce que les valeurs sont égales.
Je ne comprends pas la dernière phrase. Il n'y a pas de "attente de l'événement" dans le scénario que j'ai décrit: b_barchanged code> vérifie le
hashset code>. Si
a_foochanged code> est dans le code> code>, puis
b_barchanged code> sait qu'il est appelé dans le contexte de
a.foo code> étant Modifié, et il ne devrait pas changer
a.foo code>.
Regardez, vous déclenchez un, alors vous mettez à jour B - de sorte que vous définissez B pour être ignoré la prochaine fois. Mais ce que vous ne savez pas que b ne sera pas mis à jour car l'ancienne valeur = nouvelle valeur. Après un moment, B sera déclenché (à cause de l'interaction de l'utilisateur) et maintenant, il sera ignoré, car c'est la première fois que vous recevez un événement de B.
Alors vérifiez si f (a.foo) == b.bar code> et seulement la mise à jour si elle ne le fait pas.
Une solution légèrement piraquine consiste à définir une période de temps d'annulation au cours desquelles vous ignorez les tireurs d'événements ultérieurs. Si vous faites cela, assurez-vous d'utiliser Si vous ne voulez pas de dépendance Sur le calendrier (les événements peuvent légitimement mettre à jour rapidement), vous pouvez utiliser un type de blocage similaire, bien que cela soit encore plus hacable: p> denttime.utcnow code> plutôt que
datetime.now code> pour éviter une condition de limite d'économie de lumière du jour.
Merci mais je n'irai pas dans cette direction - il demande beaucoup de problèmes. Ordinateur plus lent / plus rapide et tout se sépare.
Parce que les positions sont à l'échelle ou modifiées autrement entre les valeurs de défilement, vous ne pouvez pas utiliser une simple liaison, mais un convertisseur fonctionne-t-il? Code où vous pouviez mettre en œuvre tout ce que vous pouvez mettre en œuvre les mathématiques nécessaire pour échelle entre les deux vues. P> [ValueConversion(typeof(double), typeof(double))]
public class InvertDoubleConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo ci)
{
return -(double)value;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo ci)
{
return -(double)value;
}
}
Mais cela ne change rien vraiment. Les convertisseurs changent la valeur de sortie et la valeur de sortie déclenche à nouveau - cette fois. Vous avez montré un exemple avec une fonction réversible (négation mathématique) et c'est pourquoi cela fonctionne, pas à cause du convertisseur. Pour voir l'effet de la fonction irréversible avec chaque conversion, ajoutez un nombre aléatoire (-10, + 10).
Je ne suis pas familier avec les commandes WPF, de sorte que les solutions suivantes pourraient ne pas être applicable: p>