paintComponent() и actionPerformed() выходят из синхронизации для JPanel iplements ActionListener


Я пытаюсь создать приложение, которое запускает анимацию. Для этого у меня есть Jframe, который содержит мой подкласс Jpanel, в котором выполняется анимация. Вот мои два класса:

Во-первых, вот мой класс водителя:

import javax.swing.*;

public class Life {
    public static void main(String[] args){
         JFrame game = new JFrame("Life");

         game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         game.setSize(500, 500);

         MainPanel mainPanel = new MainPanel();
         game.setContentPane(mainPanel);


         game.setVisible(true);

     }
 }

Во-вторых, вот мой подкласс Jpanel:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class MainPanel extends JPanel implements ActionListener{
    int i = 0;
    int j = 0;
    public MainPanel(){
        super();
    }

    public void paintComponent(Graphics g){
        j++;
        g.drawLine(10,10, 20 + i, 20 + i);
        Timer t = new Timer(1000, this);
        t.start();
    }

    @Override
    public void actionPerformed(ActionEvent actionEvent) {
        i++;
        repaint();
    }
}
Обратите внимание, что переменная i увеличивается каждый раз, когда вызывается actionPreformed, а переменная j вызывается каждый раз, когда вызывается paintComponent. В конечном итоге происходит то, что я начинаю быть гораздо больше, чем j, и линия, проведенная paintComponent, кажется, растет все быстрее и быстрее.

Вот мои вопросы:

  • Почему это происходит?
  • Как я могу синхронизировать вещи так, чтобы линия перерисовывалась каждые 1000 мс?
  • Учитывая то, что я пытаюсь сделать, является ли мой подход неправильным? Должен ли я делать что-то по-другому?

Заранее благодарю.

3 2

3 ответа:

Не запускайте таймер качания из метода paintComponent.

Этот метод должен делать вашу живопись, только вашу живопись и ничего, кроме живописи. Он должен содержать абсолютноникакой программной логики. Поймите, что у вас очень ограниченный контроль над этим методом, так как вы не можете предсказать, когда и как часто он будет вызываться. Вы даже не можете назвать его сами или гарантировать, что когда Вы предложите вызвать его через repaint(), что он действительно будет вызван.

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

Решение состоит в том, чтобы отделить логику программы От этого метода и использовать лучшие методы, такие как ваш конструктор. Повторяющийся код должен быть в таймере качания.

Правка:
Вы заявляете:

Я просто сделал это, чтобы проверить вещи. Один еще один вопрос: Что произойдет, если paintComponent или какой-то поток, от которого зависит работа в paintComponent, займет больше 1000 мс (или что бы это ни было), чтобы сделать свою работу? Единственное, о чем я могу думать, это то, что paintComponent рисует прогресс анимации до сих пор, а не ждет, когда анимация достигнет следующего шага(если это имеет какой-либо смысл). Мысли?

Вы никогда не должны иметь код в paintComponent, который занимает столько времени или даже 10 миллисекунд. Если есть риск, что что-то подобное произойдет, затем сделайте рисунок в фоновом потоке и в BufferedImage, а затем в потоке событий Swing покажите BufferedImage в методе paintComponent, используя метод Graphics#drawImage(...).

Несколько незначительных дополнений к основным выводам @HFoE:

  • открытый метод start() - это удобный способ убедиться, что ваше представление полностью построено перед запуском.
  • поля имеют четко определенныезначения по умолчанию , и они должны быть private.
  • объекты Swing GUI должны быть построены и управлятьсятолько в потоке отправки событий .
  • переопределяют getPreferredSize() и pack() заключающие Window.

Пересмотрено код:

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

public class Life {

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            private final JTabbedPane jtp = new JTabbedPane();

            @Override
            public void run() {
                JFrame game = new JFrame("Life");
                game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                MainPanel mainPanel = new MainPanel();
                game.setContentPane(mainPanel);
                game.pack();
                game.setVisible(true);
                mainPanel.start();
            }
        });
    }

    private static class MainPanel extends JPanel implements ActionListener {

        private Timer t = new Timer(100, this);
        private int i;
        private int j;

        @Override
        public void paintComponent(Graphics g) {
            g.drawLine(10, 10, 20 + i, 20 + i);
        }

        public void start() {
            t.start();
        }

        @Override
        public void actionPerformed(ActionEvent actionEvent) {
            i++;
            repaint();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(500, 500);
        }
    }
}

A Timer по умолчанию продолжайте работать. Только когда вы вызовете setRepeats( false ), он остановится.

Итак, следующие строки

Timer t = new Timer(1000, this);
t.start();

В вашем методе paintComponent означает, что после нескольких перерисовок у вас будет запущено несколько экземпляров Timer, объясняющих, почему i увеличивается намного быстрее, чем j.

Решение, конечно, состоит в том, чтобы переместить ваш Timer за пределы метода paintComponent и придерживаться одного экземпляра Timer.

Дальнейшие замечания (которые не были сказаны другими, не собираюсь повторите их очень полезный совет):

  • никогда не переопределяйте метод paintComponent без вызова метода super
  • вы не должны выставлять интерфейс ActionListener. Просто используйте ActionListener внутренне