在Java中处理循环事件的优雅方式?

时间:2010-03-08 03:32:00

标签: java swing events awt cyclic

我认为这对我来说不是一个具体的问题;以前每个人都可能遇到过这个问题。 为了正确说明,这是一个简单的用户界面:

alt text http://www.freeimagehosting.net/uploads/7aec06ea46.png

如您所见,这两个微调器正在控制一个变量 - “A”。唯一的区别是他们使用不同的视图来控制它。

由于这两个微调器的显示值是同步的,因此会显示循环事件。

如果我更改顶部微调器,“A”将被更改,底部微调器的值也将相应更新。 但是,更新底部微调器的调用(例如setValue)也会触发另一个事件,指示顶层微调器根据底部微调器的值进行更新。因此创建了一个糟糕的循环,最终可能导致StackOverFlow异常。

我以前的解决方案有点麻烦:我放置了一个保护布尔来指示是否应该执行第二次更新调用。

现在我想问一下“我怎样才能优雅地处理这种情况?一般情况下,不是特定于纺纱厂)”

THX


更新

由于我有2个答案建议我使用观察者结构,我不得不说些什么。

就像我说的那样,它很棒,但远非完美。不仅因为其固有的复杂性,还因为无法解决问题

为什么呢?要了解原因,您必须在Java Swing中实现View和Model-Controller的紧耦合。让我们以我的微调器UI为例。假设变量A实际上是Observer对象。然后,在从顶部微调器触发第一个状态更改事件后,观察者“A”将更新其值并触发PropertyChange事件以通知底部微调器。然后是第二次更新,更新底部微调器的视图。 然而,更改底部微调器的视图不可避免地会触发冗余事件,该事件将再次尝试设置“A”的值。然后,完全构造致命的循环并抛出堆栈溢出。

理论上,Observer模型试图通过引入2个独立的反馈路径来解决直接循环。链式更新赔率(在事件响应代码中)隐含地形成连接两个路径的桥,再次循环。

9 个答案:

答案 0 :(得分:3)

回到模型 - 视图 - 控制器,想想您的模型是什么,以及您的视图是什么。

在当前的实现中,您有两个模型(每个Spinner控件一个),并且它们将通过View层同步。

你应该做的是共享相同的支持模型。对于具有减去值的微调器,请为原始模型创建代理。即:

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() ) )

现在,您不需要添加侦听器,因为它们都在使用相同的模型,默认实现(originalModel)已经具有更改侦听器,它将触发到视图。

答案 1 :(得分:3)

解决问题


我有很多不同的建议。尤其, 我要感谢Marc W&贡佐牧师。我在这里总结一下这些想法;这可以节省您通过大量文本导航的时间。

如果仔细分离视图和模型控制器,则可以轻松绕过此问题。 死循环由依赖写入引起:write_1 -> write_2 -> write_1 ->...。直观地说,打破依赖可以优雅地解决问题。

如果我们深入研究问题,我们可以发现更新相应的视图并不一定涉及外部写调用。实际上,视图仅取决于它所代表的数据。众所周知,我们可以重新编写逻辑如下:write_1 -> read_2 & write_2 -> read_1

为了说明这个想法,让我们比较不同海报提到的3种方法: alt text http://www.freeimagehosting.net/uploads/2707f1b483.png

正如您所看到的,只有代理视图才能解决所有依赖关系,因此它是解决此问题的通用解决方案。

在实践中,它可以像这样实现(在你的事件响应代码中):

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

没有更多循环。解决。

答案 2 :(得分:2)

这有点复杂,但你可以让A实际上成为一个可观察的对象。两个微调器(或任何需要根据A的值更新自身)都会观察到A。每当A发生变化时,微调器(或同样的任何对象)都会自行更新以反映A的新值。这使得微调器的逻辑彼此分离。在你的例子中,旋转器不应该相互耦合,因为它们实际上彼此无关。相反,它们应该简单地绑定到A并且单独处理它们自己的视图更新。

每当更改第一个微调器中的值时,您只需更新A的值即可。每当第二个微调器中的值发生更改时,您当然会在将其赋值给A之前将其值加10。

<强>更新

在回答原始问题的更新时,我的回答是,旋转器不会听取彼此的更改事件。为每个微调器都有一个单独的事件处理方法。用户单击微调器中的向上或向下箭头会产生与以编程方式调用微调器上的setValue不同的事件,对吗?如果微调器完全相互独立,就不会有无限循环。

答案 3 :(得分:1)

E.g。对于第二个微调器,计算A-10然后将其与微调器的当前值进行比较。如果它是相同的,什么也不做,结束无限循环。同样适用于第一个微调器。

我认为还有一些方法可以以不会触发事件的方式更新微调器的模型,但我不知道它们是不是最重要的。

答案 4 :(得分:1)

通常,您的模型不应由GUI定义。即,支持每个JSpinner的SpinnerModel不应该是你的值A.(这将是一个非常不优雅的紧密耦合依赖于特定视图。)

相反,您的值A应该是POJO或对象的属性。在这种情况下,您可以向其添加PropertyChangeSupport。 (并且可能在任何情况下都已经这样做了,因为如果A被程序的其他部分更改,你希望你的微调器自动更新。)

我意识到这与Marc W的答案类似,你担心它“复杂”,但PropertyChangeSupport几乎为你做了所有这些。

实际上,对于简单的简单情况,您可以使用将“setProperty”方法连接到“firePropertyChange”调用的单个类(以及将值存储在HashMap中以用于任何“getProperty”调用)。

答案 5 :(得分:1)

对两个JSpinner使用单个SpinnerModel。请参阅以下代码: 请注意,只有在其中一个JSpinner定义了新值时,才会调用setValue()。

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();
        }
    }
}

答案 6 :(得分:1)

看来你真的在观察错误的事情。从给出的示例中,我假设您要检测的是用户对控件的操作,而不是值本身的更改。正如您所概述的那样,模型中的变化会反映在微调器的值中,而这就是形成无限循环事件的过程。

然而,进一步深入UI实现可能不是您想要的答案。在这种情况下,我会说你能做的最好的事情就是当前的防护解决方案,或者更好地将逻辑提取到你的模型中(类似于Marc和William所说的)。如何做到这一点取决于所提供难题的特定实施背后的“现实世界”模型。

答案 7 :(得分:0)

我真的不想解决你的问题,但我发现它很有趣。我已经面对它并且每次以不同的方式解决它。但当我想到'为什么?'而不是'怎么样?'我很困惑。
这个问题只存在,因为我正在使用一个自动化(MVC),它必须帮助我,并且正是以这种方式。如何使用这些组件使这种自动化成为美丽代码的障碍 为什么set #setEvent()必须生成与GUI操作相同的事件?

答案 8 :(得分:0)

虽然,我的意见也非常接近Observer模式,但它比那个轻一点!!!

将A作为带有setter的变量

private Integer A;

setA(int A)
{
  this.A = A;
  refreshSpinners();
}


refreshSpinners()
{
  setSpinnerA();
  setSpinnerAMinus10();
}

setSpinnerA()
{
   // show value of A
}

setSpinnerAMinus10()
{
   // show value of A-10
}