javaswingeventsawtcyclic

The Elegant way to handle Cyclic Event in Java?


i think this not a specific problem to me; everybody might have encountered this issue before. To properly illustrate it, here's a simple UI:

alt text

As you can see, those two spinners are controlling a single variable -- "A". The only difference is that they control it using different views.

Since these two spinners' displaying values are synchronized, cyclic event shows up.

If i change the top spinner, "A" will be changed and the bottom spinner's value will also be updated accordingly. However, updating the bottom spinner's call (such as setValue) will also trigger another event instructing the top spinner to update based on the bottom spinner's value. Thus creates a bad cycle which can eventually cause a StackOverFlow exception.

My previously solution is kinda cumbersome: i placed a guarding boolean to indicate whether the 2nd updating call should be performed.

Now i'd like to ask "how can i handle such situation elegantly? ( in general, not specific to spinners )"

thx


Update:

Since i've got 2 answers suggesting me to utilize the observer structure, i have to say something about it.

Like what i've said, it's great but far from being perfect. Not only because of its inherent complexity, but also Its inability to solve the problem.

Why? To see the reason, you must realize the tight coupling of the View and Model-Controller in Java Swing. Lets take my spinner UI for an example. Suppose the variable A is actually an Observer object. Then, after firing the first state change event from the top spinner, the Observer "A" will update its value and fire a PropertyChange event to notify the bottom spinner. Then comes the 2nd updating which updates the bottom spinner's View. However, changing bottom spinner's view inevitably triggers a redundant event that will try to set "A"'s value again. Afterwards, the deadly loop is fully constructed and the stack overflow will be thrown.

In theory, the Observer model tries to solve the direct cycle by introducing 2 independent feedback paths. The chained updating odds(in event-response codes) implicitly form a bridge connecting both paths, making a cycle again.


Solution

  • Going back to Model-View-Controller, think about what your Model is, and what your View is.

    In your current implementation, you have two models (one for each Spinner control), and they're being synced through the View layer.

    What you should be doing though is share the same backing model. For the spinner with a subtracted value, create a proxy to the original model. ie:

    class ProxySpinnerModel implements SpinnerModel {
        getValue() { return originalSpinner.getValue() - 10 }
        setValue(v) { originalSpinner.setValue(v+10) }
    }
    
    spinnerA = new JSpinner()
    spinnerB = new JSpinner( new ProxySpinnerModel( spinnerA.getModel() ) )
    

    Now, you don't need to add listeners, since they're both working off the same model and the default implementation (the originalModel) already has change listeners which it fires to the view.