使用线程 repaint() 组件
Posted
技术标签:
【中文标题】使用线程 repaint() 组件【英文标题】:Using a thread to repaint() components 【发布时间】:2018-05-03 04:02:41 【问题描述】:我的线程似乎从未启动过。无论是那个还是 run
方法实际上都没有做任何事情,为此我无法解释。
我的按钮StartRace
上有一个侦听器,它应该启动线程,该线程将增加每个矩形的长度,直到其中一个长到足以被宣布为获胜者(通过超过窗口的宽度,250 像素)。
我将所有组件最初绘制到屏幕上,但从未重新绘制它们。我调用该方法的方式有问题吗?我是否有嵌套在其他不应该嵌套的类?
//--------------------------------------------------------------
// Start a race between blue and red, track the winner
// Use threads to manage each rectangle's movement
// Allow for user interaction, like stopping and starting
//--------------------------------------------------------------
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Random;
public class ConcurrencyRace extends JFrame
private ConcurrencyPanel panel = new ConcurrencyPanel();
private JButton startRace = new JButton("Start The Race!");
private JButton stopRace = new JButton("Stop The Race!");
private JLabel winnerText = new JLabel("Winner: ");
private int blueDraw = 5, redDraw = 5;
private Random rn = new Random();
//-----------------------------------------------------------------
// Creates and displays the main program frame.
//-----------------------------------------------------------------
public ConcurrencyRace()
super("Concurrency");
setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
Container cp = getContentPane();
cp.add(panel, BorderLayout.CENTER);
JPanel p = new JPanel();
p.add(startRace);
p.add(stopRace);
cp.add(p, BorderLayout.NORTH);
cp.add(winnerText, BorderLayout.SOUTH);
pack();
setVisible(true);
public static void main (String[] args)
ConcurrencyRace tRun = new ConcurrencyRace();
tRun.setVisible(true);
private class ConcurrencyPanel extends JPanel
public class runnerThread extends Thread
@Override
public void run()
while (blueDraw < 250 && redDraw < 250)
panel.validate();
panel.repaint();
try
Thread.sleep(200);
catch (InterruptedException e)
public ConcurrencyPanel ()
setPreferredSize(new Dimension(600,250));
private class ButtonListener implements ActionListener
runnerThread rectDraw = new runnerThread();
//--------------------------------------------------------------
// Starts the thread to draw each rectangle ("racer")
//--------------------------------------------------------------
public void actionPerformed (ActionEvent event)
if (event.getSource() == startRace)
rectDraw.start();
@Override
public void paintComponent (Graphics page)
super.paintComponent(page);
page.setColor(Color.blue);
page.fillRect(0,80,blueDraw,20);
page.setColor(Color.red);
page.fillRect(0,120,redDraw,20);
blueDraw += rn.nextInt(10) + 1;
redDraw += rn.nextInt(10) + 1;
page.dispose();
【问题讨论】:
Swing 操作只能发生在 AWT 事件派发线程中;见docs.oracle.com/javase/tutorial/uiswing/concurrency。不要创建新线程,而是使用javax.swing.Timer。此外,不要在不是您创建的 Graphics 对象上调用dispose()
— 传递给绘制方法的 Graphics 实例由 AWT/Swing 系统管理。
停止再发同样的问题——这是对这个系统的滥用,对在这里提供帮助的志愿者不公平。
【参考方案1】:
主要
您永远不会在任何一个按钮上添加ActionListener
,因此当它们被激活时没有任何响应
另外
-
状态管理无处不在。
blueDraw
和 redDraw
应该是 ConcurrencyPanel
的实例字段。
不要在任何绘制方法中更新 UI 的状态(或 UI 依赖的变量)。绘制方法应该绘制状态,而不是改变它。更新blueDraw
和redDraw
应该在特定的方法中完成,需要更新时可以调用。
所有这一切让我相信你最好使用 Swing Timer
概念上...
你可以做这样的事情......
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ConcurrencyRace
//-----------------------------------------------------------------
// Creates and displays the main program frame.
//-----------------------------------------------------------------
public ConcurrencyRace()
SwingUtilities.invokeLater(new Runnable()
@Override
public void run()
Timer timer = new Timer(200, null);
JFrame frame = new JFrame();
frame.add(new ButtonPane(timer), BorderLayout.NORTH);
frame.add(new RacePane(timer));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
);
public static void main(String[] args)
new ConcurrencyRace();
public class ButtonPane extends JPanel
private JButton startRace = new JButton("Start The Race!");
private JButton stopRace = new JButton("Stop The Race!");
public ButtonPane(Timer timer)
startRace.addActionListener(new ActionListener()
@Override
public void actionPerformed(ActionEvent e)
timer.start();
);
stopRace.addActionListener(new ActionListener()
@Override
public void actionPerformed(ActionEvent e)
timer.stop();
);
setLayout(new GridBagLayout());
add(startRace);
add(stopRace);
private class RacePane extends JPanel
private int blueDraw = 5, redDraw = 5;
private Random rn = new Random();
public RacePane(Timer timer)
timer.addActionListener(new ActionListener()
@Override
public void actionPerformed(ActionEvent e)
if (updateState())
((Timer)e.getSource()).stop();
);
protected boolean updateState()
blueDraw += rn.nextInt(10) + 1;
redDraw += rn.nextInt(10) + 1;
repaint();
return blueDraw >= getWidth() || redDraw >= getWidth();
@Override
public Dimension getPreferredSize()
return new Dimension(600, 250);
@Override
public void paintComponent(Graphics page)
System.out.println(">>");
super.paintComponent(page);
page.setColor(Color.blue);
page.fillRect(0, 80, blueDraw, 20);
page.setColor(Color.red);
page.fillRect(0, 120, redDraw, 20);
这将Timer
保持为中心概念,在按钮和比赛面板之间共享。
我没有添加对生成获胜者通知的支持,这将通过传递给RacePane
的简单观察者模式来完成
【讨论】:
我无法确定在哪里添加ButtonListener
。如果我尝试在 ConcurrencyPanel()
构造函数中这样做,它会引发一堆错误。如果我尝试在 ConcurrencyRace
构造函数中执行此操作,它将无法识别 ButtonListener
类。至于blueDraw
和redDraw
你是说在ConcurrencyPanel
中声明它们并使用一种方法来更新它们的值?我会在Timer
的每个勾号之后这样做吗?
@mad:可能会帮助吸血鬼,但 1+ 是您通常的好答案。
谢谢。你的编辑绝对对我有帮助,而且非常有意义。我会复习一下,以便更清楚地理解这个概念。
在屏幕上打印获胜者通知非常容易,但我怎么能在停止按钮的右侧添加标签呢?我想更新它而不是打印通知,但我不知道如何从 RacePane
设置该标签的文本
@Steven 正如我所说,您需要某种观察者模式,RacePane
然后会向相关方生成状态已更改的通知。如果您有兴趣,ActionListener
和 Timer
是工作中观察者模式的示例以上是关于使用线程 repaint() 组件的主要内容,如果未能解决你的问题,请参考以下文章
QWidget::repaint:更新进度条时检测到递归重绘
使用带有 actionPerformed 的 repaint() 方法