views:

65

answers:

6

I am having two WPF (from the standard set) widgets A and B. When I change some property of A it should be set on B, when it is change in B it should be set on A.

Now I have this ugly recursion --> I change A, so code changes B, but since B is changed, it changes A, so it changes B... You have the picture.

How to avoid this recursion the most "standard" way? Naive deleting and adding event handlers does not work, and checking if the new value is the same as old value is not applicable here (because of the fluctuation of calculation -- I am not setting the same value to A and B, but transformed).

Background

I always try to put minimum info about the problem to avoid confusion. However, this might help

  • I didn't write those widgets, I just handle the events, that's all
  • despite the title "recursive triggering", the handlers are called sequentially, so you have the sequence entry-exit-entry-exit-entry-exit, not entry-entry-entry-exit-exit-exit

    and the last, probably the least important, but nevertheless

  • in this particular case I have common handler for A and B

A and B (in this case) are scrollviewers and I try to maintain proportionally the same position for both of them. The project (by Karin Huber) is here: http://www.codeproject.com/KB/WPF/ScrollSynchronization.aspx

Event triggering

The idea of blocking the events is so popular that I added the sequence of triggering the events, here we go:

  • I change the A
  • A handler is called
  • I disable handler of A
  • I change B (this is stored, but not triggered)
  • I enable handler of A
  • now the event is get from the queue
  • B handler is called
  • I disable handler of B
  • I change A
  • ...

As you see, this is futile.

+2  A: 

Rather than raising events, refactor your code so that the event handlers for A and B call another method to do the actual work.

private void EventHandlerA(object sender, EventArgs e)
{
    ChangeA();
    ChangeB();
}

private void EventHandlerB(object sender, EventArgs e)
{
    ChangeB();
    ChangeA();
}

You could then extend/change these methods if you need to do subtly different things if changing A directly or via B.

UPDATE

Given that you can't change/don't have access to the code this isn't the solution.

ChrisF
It is not doable -- this is WPF, A and B are widgets, and the widgets internally trigger events.
macias
But the events are hooked to event handlers and you surely can influence what happens inside *those*, can't you?
Christian
Chrisian, sure, but I can only call what is exposed by designer of the widget. So once I change B it triggers the event, and this triggers my handlers.... and we have "recursion" (maybe I should put this in quote to emphasis, it is not true recursion).
macias
+1  A: 

First of all, I would think about the design because those circular dependencies are often a sign of bad design.

However, there might be situations where such dependencies are the only way to go. In these case, I would suggest to use private flags indicating whether a change in B was caused by a change in A. Something like this (updated):

public class A
{
    private bool m_ignoreChangesInB = false;

    private void B_ChangeOccurred(object sender, EventArgs e)
    {
        if (!m_ignoreChangesInB)
        {
            // handle the changes...
        }
    }

    private void SomeMethodThatChangesB()
    {
        m_ignoreChangesInB = true;
        // perform changes in B...
        m_ignoreChangesInB = false;
    }
}

The same approach should be used in class B. However, this approach does not handle changes from multiple threads. If A or B might be changed from multiple threads at the same time, you will have to use appropriate techniques to avoid that property changes are lost.

gehho
I added emphasis on the fact those are WPF widgets, not mine -- i.e. they come from the standard set, and I just use them. Only event handlers are mine, the rest is not customized WPF. And it does not work, because recursive triggering is only our illusion -- the events are handled sequentially.
macias
This does not matter. In the end, the problematic situations are when *your code* changes the properties. Maybe, my code example does not match this situation completely. But the idea to have a flag indicating that the value change was initiated by your code would still work.
gehho
I changed my code example to better match your situation...
gehho
I does not work -- see the "trace" of how events are processed. It would work great, if events would be processed immediately right after the change. But they are not, your flag is restored back, and then the handler is called again.
macias
OK, now I see the point. Do you know why the handlers are not called immediately? Is the property change executed on another thread?
gehho
Yes, those properties are of WPF widgets, and this is specific thread.Btw. as workaround it would be possible something similar -- storing the info which events should be blocked (independently of WPF). So when A changes B it should add to "ignored list" B, and when B handler is executed it should remove itself from that list. So the caller always add itselfs, but remove all the others.
macias
Yes, thought about something similar. However, this does not feel like a clean solution to me. It seems like it might quickly become problematic...
gehho
A: 

ChrisFs solution is probably the way to go, but sometimes we remove the event, make the change, and re-add the event handler:

Imagine a DataContext, with the DataContextChanged event:

DataContextChanged -= OnDataContextChanged;
DataContext = new object();
DataContextChanged += OnDataContextChanged;

This way, you will know for sure your event handler will not go off so to speak. The event still fires however ;).

Arcturus
I already wrote -- this does not work. Events are fired sequentially, the recursion is just a illusion, because events are queued.
macias
Why does this not work? If you know B will invoke A, you could unhook A in B, do your stuff and rehook A.
Arcturus
Because event handlers are not re-entrant. They are processed in the sequence -- I added the example in the post now.
macias
I did not suggest you remove the handler of A in the handler A. I suggested removing the handler A in the handler of B:# I change the A# A handler is called# I disable handler of B# I change B (this is stored, but not triggered)# I enable handler of B# B handler will hopefully not be called.Otherwise, consider doing the changing in a background worker.
Arcturus
Actually, I did more -- I remove all the handlers :-) But it didn't work (and after tracing the call, I know why).
macias
A: 

and checking if the new value is the same as old value is not applicable here (because of the fluctuation of calculation -- I am not setting the same value to A and B, but transformed).

So what you're saying is that something like this happens:

  1. A.Foo gets set to x.
  2. A_FooChanged sets B.Bar to f(x).
  3. B_BarChanged sets A.Foo to g(f(x)), which is not x.
  4. A_FooChanged sets B.Bar to f(g(f(x))).

and so on. Is this correct? Because if g(f(x)) is x, then the solution's simple: B_BarChanged should only set A.Foo if A.Foo != g(f(x).

Assuming that this recursion has no calculable end state, then the event handlers need some way to know the context in which the events they're handling were triggered. You can't get that information from the normal event protocol, because events are designed to decouple operations that this design is coupling.

It sounds like you need an out-of-band way for these controls to signal to each other. It could be as simple as using a HashSet<EventHandler> that's a property of the Window. I'd consider something like this:

private void A_FooChanged(object sender, EventArgs e)
{
   if (!SignalSet.Contains(B_BarChanged))
   {
      SignalSet.Add(A_FooChanged);
      B.Bar = f(A.Foo);
      SignalSet.Remove(A_FooChanged);
   }
}

This breaks down if A sets B.Bar, and B sets C.Baz, and C sets A.Foo, though I suspect that the requirements themselves break down if that happens. In that case, you probably have to resort to looking at a stack trace. That's not pretty, but then nothing about this problem is pretty.

Robert Rossney
Thank you for the idea!About the functions, yes g(f(x)) does not have to be x (it can, but I cannot assume it). I thought of the dictionary of objects and events, but I gave up this way -- the problem is when g(f(x)) **IS** x (I cannot predict it, not in clean way at least). Events are triggered when the new value is different than the old one -- and with this dictionary I will be waiting for event coming, but it will never come because the values are equal.
macias
I don't understand the last sentence. There's no "waiting for the event" in the scenario I described: `B_BarChanged` checks the `HashSet`. If `A_FooChanged` is in the `HashSet`, then `B_BarChanged` knows that it's being called in the context of `A.Foo` being changed, and it shouldn't change `A.Foo`.
Robert Rossney
Look, you trigger A, so you update B -- so you set B to be ignored on the next time. But what you don't know that B won't be updated because the old value = new value. After a while B will be triggered (because of user interaction) and now it will be ignored, because it is first time you get event from B.
macias
So check to see if `f(A.Foo) == B.Bar`, and only do the update if it doesn't.
Robert Rossney
A: 

A slightly hackish solution is to set a blackout time period during which you ignore subsequent event handler firings. If you do this, be sure to use DateTime.UtcNow rather than DateTime.Now to avoid a daylight savings boundary condition.

If you don't want a dependency on timing (the events may legitimately update quickly), you can use a similar type of blocking, though this is even more hackish:

        private int _handlerCounter;
        private void Handler()
        {
            //The logic of this handler will trigger an 'asynchronously reentrant' callback, so we ignore the next (and only the next) callback
            //Note that this breaks down if the callback is not triggered, so we need to make certain the reentrancy will occur
            //If we can't ensure that, we need to at least detect that it won't occur and manually decrement the counter
            if (Interlocked.CompareExchange(ref _handlerCounter, 0, 1) == 0)
            {
                //Call set on A/B, which triggers callback of this same handler for B/A
            }
            else
            {
                Interlocked.Decrement(ref _handlerCounter);
            }
        }
Dan Bryant
Thank you but I won't go in that direction -- it is asking for a lot of troubles. Slower/faster computer and it all breaks up.
macias
A: 

Because the positions are scaled, or otherwise changed between the ScrollViewers, you can't use a simple binding, but would a Converter work?

<Window x:Class="Application1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SurfaceApplication21"
    Title="SurfaceApplication21"
    >
    <Window.Resources>
        <local:InvertDoubleConverter x:Key="idc" />
    </Window.Resources>
  <Grid>
        <StackPanel>
            <Slider Minimum="-100" Maximum="100" Name="a" Height="23" HorizontalAlignment="Left" Margin="30,12,0,0" VerticalAlignment="Top" Width="100" />
            <Slider Minimum="-100" Maximum="100" Value="{Binding ElementName=a, Path=Value, Converter={StaticResource idc}}" Name="b" Height="23" HorizontalAlignment="Left" Margin="30,12,0,0" VerticalAlignment="Top" Width="100" />
        </StackPanel>
    </Grid>
</Window>

code where you could implement whatever math is needed to scale between the two views.

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

I have not tried with ScrollViewers, but since the Scrollbars that are part of the ScrollViewer template and Sliders both descend from RangeBase, something like this should work, but you may have to re-template and/or subclass your ScrollViewers.

John Muller
But it changes nothing really. Converters changes the output value, and the output value triggers change again -- this time back. You showed example with reversible function (mathematic negation) and that is why it works, not because of converter. To see the effect of irreversible function with each convertion add a random number (-10,+10).
macias