6

これは私にとって特別な問題ではないと思います。誰もがこの問題に遭遇したことがあるかもしれません。適切に説明するために、単純な UI を次に示します。

代替テキスト

ご覧のとおり、これら 2 つのスピナーは単一の変数「A」を制御しています。唯一の違いは、異なるビューを使用して制御することです。

この 2 つのスピナーの表示値は同期しているため、周期的なイベントが表示されます。

上のスピナーを変更すると、「A」が変更され、それに応じて下のスピナーの値も更新されます。ただし、下部スピナーの呼び出し (setValue など) を更新すると、下部スピナーの値に基づいて更新するように上部スピナーに指示する別のイベントもトリガーされます。したがって、最終的に StackOverFlow 例外を引き起こす可能性のある悪いサイクルが作成されます。

私の以前の解決策はちょっと面倒です.2番目の更新呼び出しを実行する必要があるかどうかを示すために、保護ブール値を配置しました。

ここで、「どうすればそのような状況をエレガントに処理できますか? (一般的に、スピナーに固有ではありません)」と尋ねたいと思います。

どうも


アップデート:

オブザーバー構造を利用することを示唆する2つの回答があるので、それについて何か言わなければなりません。

私が言ったように、それは素晴らしいですが、完璧には程遠いです. その固有の複雑さのためだけでなく、問題を解決できないこともあります。

なんで?その理由を理解するには、Java Swing における View と Model-Controllerの密結合を理解する必要があります。例として、私のスピナー UI を見てみましょう。変数 A が実際には Observer オブジェクトであるとします。次に、一番上のスピナーから最初の状態変更イベントを発生させた後、オブザーバー "A" はその値を更新し、PropertyChange イベントを発生させて一番下のスピナーに通知します。次に、下部スピナーのビューを更新する 2 番目の更新が行われます。ただし、ボトム スピナーのビューを変更すると、「A」の値を再度設定しようとする冗長なイベントが必然的にトリガーされます。その後、致命的なループが完全に構築され、スタック オーバーフローがスローされます。

理論的には、オブザーバー モデルは、2 つの独立したフィードバック パスを導入することで直接的なサイクルを解決しようとします。チェーン化された更新オッズ (イベント応答コード内) は、暗黙的に両方のパスを接続するブリッジを形成し、再びサイクルを作ります。

4

9 に答える 9

3

問題が解決しました


いろいろな提案があります。特に、マーク・Wとゴンゾ牧師に感謝します。私はこれらのアイデアの要約を作成するためにここにいます。これにより、大量のテキストをナビゲートする時間を節約できます。

ViewとModel-Controllerを注意深く切り離すと、この問題を簡単に回避できます。デッドサイクルは、依存する書き込みによって発生しますwrite_1 -> write_2 -> write_1 ->...。直感的には、依存関係を壊すことで問題を上品に解決できます。

問題を詳細に調べると、対応するビューの更新が必ずしも外部の書き込み呼び出しを伴うとは限らないことがわかります。実際、ビューはそれが表すデータにのみ依存します。これを知っていると、次のようにロジックを書き直すことができますwrite_1 -> read_2 & write_2 -> read_1

このアイデアを説明するために、さまざまなポスターで言及されている3つの方法を比較してみましょう。 代替テキストhttp://www.freeimagehosting.net/uploads/2707f1b483.png

ご覧のとおり、プロキシされたビューのみがすべての依存関係を解決できるため、この問題の種類の一般的な解決策になります。

実際には、次のように実装できます(イベント応答コードで)。

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

これ以上のループはありません。解決しました。

于 2010-03-09T03:14:01.543 に答える
3

Model-View-Controller に戻り、Model とは何か、View とは何かを考えてみましょう。

現在の実装では、2 つのモデル (Spinner コントロールごとに 1 つ) があり、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) には、ビューに対して起動する変更リスナーが既にあるためです。

于 2010-03-09T02:33:33.207 に答える
1

原則として、モデルはGUIで定義しないでください。つまり、各JSpinnerをサポートするSpinnerModelは、値Aであってはなりません(これは、特定のビューに対するひどくエレガントでない緊密に結合された依存関係になります)。

代わりに、値AはPOJOまたはオブジェクトのプロパティのいずれかである必要があります。その場合、PropertyChangeSupportを追加できます。(そして、Aがプログラムの他の部分によって変更された場合にスピナーを自動的に更新したいので、おそらくすでにそうしているでしょう)。

これはMarcWの答えに似ていると思います。あなたはそれが「複雑」であると心配していましたが、PropertyChangeSupportがほとんどすべてを代行してくれます。

実際、ごく単純なケースでは、「setProperty」メソッドを「firePropertyChange」呼び出しに接続する単一のクラスを使用できます(「getProperty」呼び出しの値をHashMapに格納することもできます)。

于 2010-03-08T08:24:56.020 に答える
1

両方の JSpinner に単一の SpinnerModel を使用します。次のコードを参照してください。 setValue() の呼び出しは、JSpinner の 1 つによって新しい値が定義されるたびに 1 回だけ行われることに注意してください。

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();
        }
    }
}
于 2010-03-08T12:37:49.207 に答える
1

あなたは本当に間違ったことを観察しているようです。与えられた例から、検出したいのは、値自体の変更ではなく、コントロールに対するユーザーのアクションであると推測します。概説したように、モデルの変更はスピナーの値に反映され、これがイベントの無限ループを形成します。

ただし、UI 実装をさらに掘り下げることは、あなたが望む答えではないかもしれません。その場合、あなたができる最善の方法は、現在のガードソリューション、またはモデルにロジックをより適切に抽出することです (Marc と William が言ったことと同様)。それがどのように行われるかは、提供されたパズルの特定の実装の背後にある「現実世界」のモデルによって異なります。

于 2010-03-08T12:44:28.070 に答える
0

ただし、私の意見も Observer パターンにかなり近いですが、それよりも少し軽いです!!!

A をセッター付きの変数として持つ

private Integer A;

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


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

setSpinnerA()
{
   // show value of A
}

setSpinnerAMinus10()
{
   // show value of A-10
}
于 2010-03-08T11:59:54.003 に答える
0

あなたの問題を解決したいわけではありませんが、興味深いと思います。私はすでにそれに直面しており、毎回異なる方法で解決しています。しかし、「なぜ?」と考えると、「どのように?」についてではありません。困惑したままです。
この問題は、私を助けなければならなかった自動化 (MVC) を使用しているためにのみ存在します。コンポーネントがどのように使用されるかが、この自動化を美しいコードの障壁にしています。setが GUI アクションと同じイベントを生成する必要があるのは
なぜですか?#setEvent()

于 2010-03-08T08:12:37.220 に答える