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 ответа:
Не запускайте таймер качания из метода
Этот метод должен делать вашу живопись, только вашу живопись и ничего, кроме живописи. Он должен содержать абсолютноникакой программной логики. Поймите, что у вас очень ограниченный контроль над этим методом, так как вы не можете предсказать, когда и как часто он будет вызываться. Вы даже не можете назвать его сами или гарантировать, что когда Вы предложите вызвать его через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
внутренне