3

私はこのような単一のクラスを持っています

public class BlockSpawner implements Runnable{

public static long timeToSpawn;
private GtrisJFrame frame;

public BlockSpawner(GtrisJFrame frame)
{

    this.frame = frame;
    timeToSpawn = 2000;
}

public void run()
{
    while(true)
    {
        try
        {
            Thread.sleep(timeToSpawn);
        }
        catch(InterruptedException e)
        {
            //Unhandled exception
        }

        //After awake, instanciate 2 blocks
        //get the position of the first one
        int index = Block.getRandomStartPosition();
        new Block(frame, index);
        new Block(frame, index+1);
    }
}

}

このクラスを JFrame メイン クラスでインスタンス化し、次のようにスレッドを開始します。

private void initBlockSpawner()
{
    spawner = new BlockSpawner(this);
    new Thread(spawner).start();
}

この initBlockSpawner() 関数を JFrame コンストラクター内から呼び出します。Block Class は実際には少し大きいですが、一言で言えば、runnable を実装し、コンストラクターの最後で run() メソッドを呼び出します。run() メソッドは、ブロックを特定の速度で落下させるだけです。JFrameコンストラクターで新しいブロックを手動でインスタンス化しようとしましたが、それらは機能し、再描画して落ちます。しかし、他のスレッドからブロックをインスタンス化したいときはいつでも、それらは落ちているように見えます (つまり、そのプロパティが各ループを更新します) が、JFrame に描画されません。

追加情報として、私は NetBeans を使用しています。アプリケーションのエントリ ポイントは JFrame クラスにあるため、メイン メソッドは次のようになります。

public static void main(String args[])
{
    java.awt.EventQueue.invokeLater(new Runnable() {
        public void run() {
            new GtrisJFrame().setVisible(true);
        }
    });

}

Java スレッド、awt イベント、swing コンポーネントの経験はあまりありません。しかし、私がここで読んだことは、私の問題は、swing コンポーネントを制御できるスレッドが 1 つしかないこと、または何かだと思います...私の問題を解決する方法はありますか?

前もって感謝します。

編集:追加情報、スレッドからインスタンス化されたキューブで toString メソッドをチェックするたびに、この [,0,0,0x0] が返されますが、同じ JFrame クラス内でそれらをインスタンス化すると、この結果 [,0, 0,328x552] で、フレームに表示されます。この 328x552 の値は、getPreferredSize() によって返されるコンポーネントのディメンションと同じです...次のようにインスタンス化することで、それらをそのディメンションに強制しようとしました。

new Block(this, index).setPreferredSize(new Dimension(328, 552));

しかし、うまくいきませんでした。この [,0,0,328x552] の値が何を意味するか知っている人はいますか?

皆さん、ありがとうございます。あと少しです!

編集 2: コンポーネントのサイズが x:0 y:0 であることに気付きました。これはなぜですか? BlockSpawner の run() メソッドを次のように変更します。

public void run()
{
    while(true)
    {
        System.out.println("SPAWN");
        int index = Block.getRandomStartPosition();
        new Thread(new Block(frame, index)).start();
        new Thread(new Block(frame, index+1)).start();

        try
        {
            Thread.sleep(timeToSpawn);
        }
        catch(InterruptedException e)
        {
            //Unhandled exception
        }
    }
}

最初の実行では、すべてがうまくいきます!ブロックのペアでさえ JFrame にペイントして正しく落ちますが、Thread.sleep() の後、残りのブロックはインスタンス化されますが、getSize() メソッドは x:0 y:0; を返します。これはまだ One Dispatcher Thread の問題に何らかの形で関連していますか? それとも今は違うのですか?

4

3 に答える 3

4

イベントディスパッチ以外のスレッドからライブJFrame(つまり、画面に表示されているもの、または「実現」されているもの)にコンポーネントを追加しようとしているように思えます(上記のコードからはわかりませんが)。スレッド。これは Swing スレッド モデルに違反しており、問題が尽きることはありません。

別のスレッドから Swing オブジェクトに変更を加える場合は、変更を Runnable にパッケージ化し、EventQueue.invokeLater() または invokeAndWait() を使用してディスパッチ スレッドに送信します。

編集:詳細

いくつかの追加コメント (問題に直接関係していませんが、それでも重要です): コンストラクターでアクティビティを実行することは、おそらく良い考えではありません。JFrame をサブクラス化してコンポーネントを追加することも、おそらく良い考えではありません。さらに言えば、JPanel ではなく JFrame でこれらの操作を行うことも、おそらく最善の方法ではありません。

これらを順番に取り上げます。

  1. コンストラクターは、オブジェクトの初期構成を実行するために使用する必要があります。呼び出し動作ではありません。この分離は、設計をクリーンで保守しやすい状態に保つのに役立ちます。この方法の方が簡単に思えるかもしれませんが、私はお勧めしません。設計のある時点で、これらのオブジェクトを事前に作成し、後でのみ使用する方が効率的であると判断する場合があります。

  2. コンポーネントを追加するために JFrame をサブクラス化することは、通常はお勧めできません。なんで?必要な他の動作を備えた特殊な JFrame を使用することにした場合はどうなるでしょうか。JFrame を提供するアプリケーション フレームワークを使用することにした場合はどうなるでしょうか(これは、フレームワークがウィンドウのサイズと位置を節約できるように、ウィンドウを閉じるイベントを追跡する必要がある場合によく見られます)。とにかく - たくさんの理由。動作を非 GUI 関連クラスにパッケージ化し、それを使用して JFrame (または JPanel) に動作を注入します。

  3. JFrame の代わりに JPanel を使用することを検討してください。必要に応じて、いつでも JPanel を JFrame に追加できます。JFrame を直接使用する場合、これらのパネルを 2 つ並べて 1 つのコンテナーに入れたいと判断した場合はどうなるでしょうか。

したがって、次の行に沿って何かを行うことをお勧めします。

BlockAnimator animator = new BlockAnimator();
DispatchThread.invokeLater( 
  new Runnable(){
    public void run(){
      JPanel blockAnimationPanel = new JPanel();
      Block block = new Block(...);
      blockAnimationPanel.add(block);
      JFrame mainFrame = new JFrame();
      mainFrame.add(blockAnimationPanel);
      animator.start(); // note that we probably should start the thread *after* the panel is realized - but we don't really have to.
    }
  }

public class BlockAnimator extends Thread{
  private final List<Block> blocks = new CopyOnWriteArray<Block>(); // either this, or synchronize adds to the list
  public void addBlock(Block block){
    blocks.add(block);
  }
  public void run(){
    while(true){ // either put in a cancel check boolean, or mark the thread as daemon!
      DispatchThread.invokeAndWait(
        new Runnable(){
          public void run(){
            for(Block block: blocks){
              block.moveTo(....); // do whatever you have to do to move the block
            }
          }
        }
      ); // I may have missed the brace/paren count on this, but you get the idea
      spawnNewBlockObjects();
      Thread.sleep(50);
    }
  }
}

上記のコードは、正確性などについてチェックされていません...

理論的には、新しいブロックを生成するための別のスレッドを持つこともできますが、上記は非常に簡単です。上記のように単一のバックグラウンド スレッドで実装する場合は、ブロック リストに単純な ArrayList を使用できます。これは、そのオブジェクトで競合状態が発生しないためです。

これに関する他のいくつかの考え:

  1. 上記では、ブロックアニメーターはブロック自体から独立して管理できます。たとえば、すべてのブロックを一時停止する pause() メソッドを追加できます。

  2. 同じディスパッチ スレッド呼び出しで発生するすべてのブロックのアニメーションを更新しました。アニメーションのコストによっては、バックグラウンド スレッドで新しい座標を計算し、実際の位置の更新のみを EDT にポストする方がよい場合があります。また、ブロックの更新ごとに個別の invokeAndWait を発行する (または場合によっては invokeLater を使用する) こともできます。それは本当にあなたがしていることの性格に依存します.

ブロックをどこに移動するかの計算が危険な場合は、計算を実際の移動から分離することを検討してください。したがって、オブジェクトの新しいポイントを取得する呼び出しがあり、次に実際に移動する別の呼び出しがあります。

于 2010-02-20T05:46:46.877 に答える
2

Swing はマルチスレッドをサポートしていないため、Swing と対話する必要があるときはいつでも、AWT イベント スレッドから実行する必要があります。

これは、netbeans によって追加された main() メソッドで起こっていることです。 java.awt.EventQueue.invokeLaterAWT イベント キューで実行されるようにランナブルをスケジュールします。

通常、BlockSpawner Runnable で同じことを行うことができますが、遅延が必要なため、sleep() はイベント キューをブロックし、ユーザー入力で問題/遅延を引き起こします。

これを回避するには、タスクをバックグラウンドで実行し、終了時にイベント キューと再同期できるように するSwingWorkerを使用することをお勧めします。

あなたの場合、doInBackground() メソッドで sleep() を実行してから、done() メソッドで新しいコンポーネントを作成する必要があります。

于 2010-02-20T05:53:02.047 に答える
0

私の他の答えに代わるものは、javax.swing.Timerを使用することです

これにより、指定されたレートでイベント ディスパッチ スレッドで発生するアクションをスケジュールする機能が提供され、Java 6 は必要ありません。

次のコードを使用して BlockSpawner をスケジュールできます。

  int timeToSpawn = 2000;

  ActionListener blockSpawner = new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
          int index = Block.getRandomStartPosition();
          new Block(frame, index);
          new Block(frame, index+1);
      }
  };
  new Timer(timeToSpawn, blockSpawner).start();

これは、追加のスレッドを必要としないため、おそらく最も単純なソリューションです。java.util ではなく、javax.swing で Timer クラスを使用していることを確認してください。そうしないと、イベント ディスパッチ スレッドで実行されない可能性があります。

于 2010-02-20T22:20:09.030 に答える