Re: [問題] Panel的paintComponent並未確實被呼叫
※ 引述《issuemylove (skill)》之銘言:
: 大家好^^
: 在下最近稍微研究了一下User interface的部份
: 想說試著讓某圖形(圓形)在視窗上位移
: 如在右方的連結情況下 http://nopaste.info/3b91c2304d.html
: 圖形正常的移動了
: 但是我使用 Button控制它的移動時機時(即按下button後才移動)
: 如右方連結 http://nopaste.info/4cb008e3bf.html
: 卻出現了中間移動過程皆不出現的情況
: 而直接時間到後,才將最後一個畫面印出
: 請問這是哪邊出了問題呢?
: 謝謝大家的回答
repaint 是個 asynchronous operation,(大致上來說)它的任務可看成是 queue
一個 PaintEvent 到 event-dispatching thread。他並不是直接造成元件的
paint routine 被執行來繪製外觀。此外過多的 PaintEvent 累積在 event-
dispatching thread 裡可能會被合併,因此並不是每 invoke 一次就一定會令元件
repaint 一次。
你的程式在 event-dispatching thread 執行把多個 paint event queue 到
event-dispatching thread 裡的動作(A),在 A 完成以前 event-dispatching
thread(EDT) 不會有機會去處理 paint event。等到 A 完成了,EDT 可以處理已累積
的 paint event,就算 EDT 有照實一個 paint event 做一次 UI 上的繪圖,你看起來
的效果也會是直接看到 UI component 最後的狀態時外觀(在 30 ms 內,螢幕上如果有
許多變化,你可能也看不出來)。
整個程式並不是如你想的 repaint method 執行一次 UI 元件外觀更新一次,停個
100 ms 再繼續。而會是程式先停了 10 秒,然後突然執行了 100 次繪圖動作,或是
一次(兩者你用肉眼觀察或許感覺上是相同的)。
你的程式要有動畫的效果,主要可以往兩個方向來改。
1. 在 event-dispatching thread 裡改變的 UI component 狀態後,invoke UI
component 的 paintImmediately method 來直接觸發 paint routine 來更新外觀。
例如把
frame.repaint();
改成執行 myPanel instance 的 paintImmediately method。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class myCircleMove {
JFrame frame;
JButton button;
myPanel panel;
int x;
int y;
public static void main(String[] args) {
myCircleMove gui = new myCircleMove();
gui.go();
}
public void go() {
x = 10;
y = 10;
panel = new myPanel();
button = new JButton("click me");
button.addActionListener(new myActionListener());
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(BorderLayout.SOUTH, button);
frame.getContentPane().add(BorderLayout.CENTER, panel);
frame.setSize(600, 600);
frame.setVisible(true);
}
public class myActionListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < 100; i++) {
x++;
y++;
// frame.repaint(); // <= 怪怪的
panel.paintImmediately(0, 0,
panel.getWidth(), panel.getHeight());
try {
Thread.sleep(100);
}
catch (Exception e1) {
}
}
}
}
class myPanel extends JPanel {
public void paintComponent(Graphics g) {
g.setColor(Color.black);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(Color.red);
g.fillOval(x, y, 40, 40);
}
}
}
2. 把變更 UI component 狀態的動作由 event-dispatching thread 以外的
thread 來執行。
public class myActionListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++) {
x++;
y++;
// frame.repaint(); // <= 怪怪的
panel.repaint();
try {
Thread.sleep(100);
}
catch (Exception e1) {
}
}
}
}).start();
}
}
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 218.173.139.219
※ 編輯: sbrhsieh 來自: 218.173.139.219 (06/28 17:06)
推
06/28 17:27, , 1F
06/28 17:27, 1F
→
06/28 17:29, , 2F
06/28 17:29, 2F
→
06/28 17:31, , 3F
06/28 17:31, 3F
沒有使用 button 觸發動畫的版本中,move100 method 不是在 EDT 裡執行,而是在
main thread 裡。move100 method 執行過程中 EDT 有機會去處理 repaint 安排的
paint event(導致 EDT 去執行 UI component 的 paint routine)。如果在 go
method 裡把 move100 invocation 丟到 EDT 去跑,就會出現相同問題。
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
move100();
}
}
※ 編輯: sbrhsieh 來自: 218.173.139.219 (06/28 17:57)
推
06/28 18:08, , 4F
06/28 18:08, 4F
討論串 (同標題文章)
本文引述了以下文章的的內容:
完整討論串 (本文為第 2 之 2 篇):