Elegant way to handle Cyclic Event in Java?

I think this is not a particular problem for me; everyone could face this problem earlier. To illustrate this correctly, here is a simple interface:

alt text

As you can see, these two counters control one variable - "A". The only difference is that they control it using different views.

Since the displayed values โ€‹โ€‹of these two counters are synchronized, a cyclic event appears.

If I change the upper counter, โ€œAโ€ will change and the value of the lower counter will also be updated accordingly. However, updating a lower counter call (such as setValue) will also trigger another event instructing the upper counter to update based on the value of the lower counter. This creates the wrong loop, which could ultimately throw a StackOverFlow exception.

My earlier decision was rather cumbersome: I placed a protective boolean value to indicate whether a second update call should be made.

Now I would like to ask: " How can I handle this situation elegantly? ( In general, not only for spinners )"

thanks


Update:

Since I have 2 answers suggesting I use the observer structure, I have to say something about this.

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

What for? To see the reason, you must implement the close relationship of View and Model-Controller in Java Swing. Let's take my counter user interface as an example. Assume that the variable A is actually an Observer object. Then, after the first state change event is triggered from the upper counter, observer "A" will update its value and fire the PropertyChange event to notify the lower counter. Then comes the 2nd update, which updates the look of the lower counter. However, a change in the appearance of the lower counter inevitably causes an excess event, in which it will again try to set the value to "A". After that, the death cycle will be fully created, and a stack overflow will be generated.

Theoretically, the Observer model attempts to solve a direct cycle by introducing 2 independent feedback paths. The chained update chances (in response event codes) implicitly form a bridge connecting the two paths, again looping.

+6
java events swing awt
source share
9 answers

Returning 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 are synchronized through the View layer.

What you have to do is a collaborative support model. For a counter with a subtracted value, create a proxy server for the source model. i.e:

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 do not need to add listeners, since they work with the same model, and the default implementation (the original model) already has listeners for the changes that it runs for presentation.

+3
source share

Solution


I have many different offers. In particular, I want to thank Mark W and Rev. Gonzo. I am here to write a summary for these ideas; this can save you time by moving through a large piece of texts.

This problem can be easily circumvented if you carefully separate View and Model-Controller. The dead loop is caused by dependent writes: write_1 -> write_2 -> write_1 ->... Intuitively, breaking addiction can solve the problem elegantly.

If we look at the problem in depth, we may find that updating the corresponding views is not necessarily related to an external record call. In fact, the presentation depends only on the data that it represents. It is known that then we can rewrite the logic as follows: write_1 -> read_2 & write_2 -> read_1 .

To illustrate this idea, let's compare 3 methods mentioned by different posters: alt text http://www.freeimagehosting.net/uploads/2707f1b483.png

As you can see, only a proxy representation can solve the whole dependency, so this is a general solution to this problem knife.

In practice, it can be implemented as something similar (in your event-response codes):

  setValue(newValue); anotherSyncUI.parse(); // not anotherSyncUI.setValue() any more anotherSyncUI.repaint(); 

More cycles. Solvable.

+3
source share

This is a bit complicated, but you could make A actually an object that can be observed. Both spinners (or something that needs to be updated based on the value of A ) will observe A Whenever A changes, the spinners (or, again, any object) are updated themselves to reflect the new value of A This separates the spinner logic from each other. In your example here, the spinners should not be related to each other, because they really have nothing to do with each other. Instead, they should simply be tied to A and take care of their own browsing on their own.

Whenever the value in the first counter changes, you simply update the value of A to match it. Whenever the value in the second counter changes, you would naturally add 10 to its value before assigning it A

Update

In response to updating your original question, my answer is that the spinners do not listen to each other. Have a separate event handling method for each counter. A user, by pressing the up or down arrows in a spinner, generates a different event than the setValue soft call to the spinner, right? If the spinners are completely independent of each other, there will be no endless cycle.

+2
source share

eg. for the second counter, calculate A-10, and then compare it with the current counter value. If this is the same, do nothing, ending an infinite loop. Similarly for the first counter.

I think there are also ways to update the spinner model so as not to fire an event, but I don't know them from the head.

+1
source share

As a rule, your model should not be defined by your graphical interface. That is, the SpinnerModel that every JSpinner supports does not have to be your value A. (This would be a terribly inelegant, closely related dependency on a particular kind.)

Instead, your value of A should be a POJO or property of an object. In this case, you can add a PropertyChangeSupport to it. (And, presumably, this has already been done anyway, since you want your spinners to automatically update if A changes to other parts of your program).

I understand that this is similar to Marc W's answer, and you were worried that it was โ€œcomplicated,โ€ but PropertyChangeSupport does almost everything for you.

In fact, for simple simple cases, you can simply use one class that pads the setProperty method before calling "firePropertyChange" (and also stores the value in the HashMap for any calls to "getProperty").

+1
source share

Use one SpinnerModel for both JSpinners. See the following code: Note that setValue () is called only once when a new value is determined by one of the JSpinners.

 import java.awt.BorderLayout; import javax.swing.*; public class Test { public static void main(String[] args) { JFrame jf = new JFrame(); SpinnerModel spinModel = new MySpinnerModel(); JSpinner jspin1 = new JSpinner(spinModel); JSpinner jspin2 = new JSpinner(spinModel); jf.setLayout(new BorderLayout()); jf.add(jspin1, BorderLayout.NORTH); jf.add(jspin2, BorderLayout.SOUTH); jf.pack(); jf.setVisible(true); jf.setDefaultCloseOperation(3); } } class MySpinnerModel extends AbstractSpinnerModel { private int _value = 0; private int _min = 0; private int _max = 10; @Override public Object getNextValue() { if (_value == _max) { return null; } return _value + 1; } @Override public Object getPreviousValue() { if (_value == _min) { return null; } return _value - 1; } @Override public Object getValue() { return _value; } @Override public void setValue(Object value) { System.out.println("setValue(" + value + ")"); if (value instanceof Integer) { _value = (Integer) value; fireStateChanged(); } } } 
+1
source share

You seem to notice the wrong thing. From the above example, I assume that you want to detect these are user actions on the controls, not changes to the values โ€‹โ€‹themselves. As you described, changes in your model are reflected in the values โ€‹โ€‹of spinners, and this is what forms an endless cycle of events.

However, further immersion in the implementation of the user interface may not be the most suitable answer. In this case, I would say that the best you can do is either your current protection solution, or it is better to extract the logic into your model (similar to what Mark and William said). How this can be done will depend on the "real world" model behind the specific implementation of the puzzle provided.

+1
source share

I really do not want to solve your problem, but I'm interested. I have already come across this and solved it each time differently. But when I think about โ€œwhy?โ€ not about how? I am perplexed. This problem exists only because I use Automation (MVC), which was supposed to help me, and that is exactly how. The art of using these components makes this automatism an obstacle to beautiful code.
Why set #setEvent() to create the same event as a GUI action?

0
source share

Although, my opinion is also very close to the Observer pattern, but it is a little lighter than that.

Have A as an Installer Variable

 private Integer A; setA(int A) { this.A = A; refreshSpinners(); } refreshSpinners() { setSpinnerA(); setSpinnerAMinus10(); } setSpinnerA() { // show value of A } setSpinnerAMinus10() { // show value of A-10 } 
0
source share

All Articles