Созданные компоненты из потока не перерисовываются в JFrame в Java - PullRequest
3 голосов
/ 20 февраля 2010

У меня есть один такой класс

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, метод main выглядит следующим образом:

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

}

У меня нет особого опыта работы с потоками Java, событиями awt и компонентами Swing. Но то, что я читаю здесь , заставляет меня думать, что моя проблема в том, что только один поток имеет контроль над компонентами колебания, или что-то ... Есть ли способ решить мою проблему?

Заранее спасибо.

РЕДАКТИРОВАТЬ: Дополнительная информация, всякий раз, когда я проверяю метод toString в кубах Insttitiated из потоков, они дают мне это [, 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, почему это? Я изменяю метод run () BlockSpawner на что-то вроде этого:

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? или сейчас что-то другое?

Ответы [ 3 ]

4 голосов
/ 20 февраля 2010

Мне кажется (хотя я не могу сказать из вашего кода выше), что вы пытаетесь добавить компоненты в живой JFrame (то есть тот, который был показан на экране, или "реализован") из потока, отличного от поток рассылки событий. Это является нарушением модели потоков Swing и не вызовет никаких проблем.

Если вы хотите внести изменения в объект Swing из другого потока, вы упаковываете это изменение в Runnable и отправляете его в поток диспетчеризации с помощью EventQueue.invokeLater () или invokeAndWait ().

РЕДАКТИРОВАТЬ: больше информации

Некоторые дополнительные комментарии (не связанные непосредственно с вашей проблемой, но тем не менее важные): Выполнение действий в конструкторе, вероятно, не очень хорошая идея. Создание подклассов JFrame для добавления компонентов к нему также, вероятно, не очень хорошая идея. В этом отношении выполнение этих операций в JFrame вместо JPanel, вероятно, также не лучший подход.

Принимая их по очереди:

  1. Конструкторы должны использоваться для выполнения начальной настройки объектов, а не для вызова поведения. Такое разделение помогает сохранить ваш дизайн в чистоте и ремонтопригодности. Хотя может показаться, что так будет проще, я рекомендую против. В какой-то момент в вашем проекте вы можете решить, что более эффективно создавать эти объекты заранее и использовать их только позже.

  2. Создание подклассов JFrame для добавления компонентов, как правило, не горячая идея. Зачем? Что если вы решите, что хотите использовать специализированный JFrame с другим поведением, которое вам нужно? Что, если вы решите использовать каркас приложения, который предоставляет you с JFrame (это типично в тех случаях, когда каркас может отслеживать события закрытия окна, чтобы сохранить размер и местоположение окна). Во всяком случае - тонны причин. Упакуйте свое поведение в класс, не связанный с графическим интерфейсом, и используйте его для внедрения поведения в JFrame (или JPanel).

  3. Попробуйте использовать JPanel вместо JFrame. Вы всегда можете добавить JPanel в JFrame, если хотите. Если вы говорите напрямую с JFrame, что произойдет, если вы решите, что хотите иметь две эти панели рядом в одном контейнере?

Итак, я бы посоветовал вам сделать что-то более похожее на:

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) для каждого обновления блока. Это действительно зависит от характера того, что вы делаете.

Если вычисление для того, чтобы переместить блок, является грубым, рассмотрите возможность отделения вычисления от фактического перемещения. Таким образом, у вас будет вызов, который получит новую точку для объекта, а затем другой вызов, который фактически сделает перемещение.

2 голосов
/ 20 февраля 2010

Swing не поддерживает многопоточность, поэтому всякий раз, когда вам нужно с ней взаимодействовать, вы должны делать это из потока событий AWT.

Это то, что происходит в методе main (), добавленном netbeans. java.awt.EventQueue.invokeLater планирует запуск в очереди событий AWT.

Обычно вы можете сделать то же самое с вашим BlockSpawner Runnable, но из-за необходимости иметь задержку sleep () заблокирует очередь событий и вызовет проблемы / задержки с пользовательским вводом.

Чтобы обойти это, я предлагаю вам использовать SwingWorker , который позволяет выполнять задачи в фоновом режиме и затем синхронизироваться с очередью событий после их завершения.

В вашем случае вы должны выполнить sleep () в методе doInBackground (), а затем создать новые компоненты в методе done ().

0 голосов
/ 21 февраля 2010

Альтернативой моему другому ответу является использование 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();

Это, пожалуй, самое простое решение, так как оно не требует дополнительных потоков. Просто убедитесь, что вы используете класс Timer в javax.swing, а не в java.util, иначе вы не сможете выполнять его в потоке отправки событий.

...