特定的 RepaintManager 可以用于特定的 JPanel 吗?
Can a specific RepaintManager be used for a specific JPanel?
我知道 Java RepaintManager 会合并对 repaint() 的调用,这对 99% 的渲染都没有问题。我有一个 JPanel,我想在定时器(100 毫秒)上更新图像以提供像视频一样流畅的渲染。实际上 RepaintManager 似乎 steal/ignore 每隔一个 repaint() 除非鼠标被移动。我想知道我有什么选择可以解决这个问题。我也查看了 paintImmediately(),但它导致与 repaint() 相同的行为,因此不是很有用。预先感谢有用的想法!
- 是否可以为特定的 JPanel 创建和使用自定义 RepaintManager,而对其他一切使用默认值?
- 有没有办法让默认的 RepaintManger 确定某个面板是 "dirty" 以便重新绘制而不是忽略它?
下面是一些代码来说明实现,您会注意到(至少在我的 Linux 测试中),数字几乎会跳过所有其他序列。
public class PanelRepaintIssue
{
private static final int kWIDTH = 200;
private static final int kHEIGHT = 100;
private static final int kNUM_IMAGES = 10;
private static final int kREPAINT_DELAY = 250;
private final JPanel _ImagePanel;
private final BufferedImage[] _Images;
private final Timer _Timer;
private TimerTask _TimerTask;
private int _Index;
public PanelRepaintIssue()
{
_Index = 0;
_ImagePanel = new JPanel()
{
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
if (_Index < kNUM_IMAGES)
{
g.drawImage(_Images[_Index], 0, 0, null);
}
}
};
_ImagePanel.setSize(new Dimension(kWIDTH, kHEIGHT));
_ImagePanel.setPreferredSize(new Dimension(kWIDTH, kHEIGHT));
_Images = new BufferedImage[kNUM_IMAGES];
for (int i = 0; i < _Images.length; ++i)
{
_Images[i] = new BufferedImage(kWIDTH, kHEIGHT, BufferedImage.TYPE_INT_ARGB);
Graphics2D t2d = _Images[i].createGraphics();
t2d.setColor(Color.BLACK);
t2d.fillRect(0, 0, kWIDTH, kHEIGHT);
t2d.setColor(Color.RED);
t2d.drawString(Integer.toString(i), kWIDTH/2, kHEIGHT/2);
t2d.dispose();
}
_Timer = new Timer(this.getClass().getName());
}
public JPanel getPanel()
{
return _ImagePanel;
}
public void start()
{
if (null != _TimerTask)
{
_TimerTask.cancel();
_TimerTask = null;
}
_TimerTask = new TimerTask()
{
@Override
public void run()
{
++_Index;
if (_Index >= kNUM_IMAGES)
{
_Index = 0;
}
_ImagePanel.repaint();
// Also tried _ImagePanel.paintImmediately(0, 0, kWIDTH, kHEIGHT);
}
};
_Timer.scheduleAtFixedRate(_TimerTask, 1000, kREPAINT_DELAY);
}
public static void main(String[] args)
{
PanelRepaintIssue tPanel = new PanelRepaintIssue();
tPanel.start();
JFrame tFrame = new JFrame("PanelRepaintIssue");
tFrame.add(tPanel.getPanel());
tFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
tFrame.setResizable(false);
tFrame.pack();
tFrame.setVisible(true);
}
}
我仍然不确定绘画问题是什么。
我对你的代码做了一些修改。
我使用SwingUtilities
invokeLater
方法将Swing组件的创建和执行放在Event Dispatch Thread(EDT)上。
我删除了 paintComponent
方法中除绘画代码之外的所有内容。 paintComponent
方法中除了绘画之外什么都不应该发生。
这是我的代码运行。
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class PanelRepaintIssue {
private static final int kWIDTH = 200;
private static final int kHEIGHT = 100;
private static final int kNUM_IMAGES = 10;
private static final int kREPAINT_DELAY = 250;
private final JPanel _ImagePanel;
private final BufferedImage[] _Images;
private final Timer _Timer;
private TimerTask _TimerTask;
private int _Index;
private int _TimerCnt;
private int _PaintCnt;
public PanelRepaintIssue() {
_Index = 0;
_TimerCnt = 0;
_PaintCnt = 0;
_ImagePanel = new JPanel() {
private static final long serialVersionUID = 1L;
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// if (_Index < kNUM_IMAGES) {
g.drawImage(_Images[_Index], 0, 0, null);
++_PaintCnt;
// System.out.println("Timer: " + _TimerCnt +
// " Paint: " + _PaintCnt);
// }
}
};
_ImagePanel.setSize(new Dimension(kWIDTH, kHEIGHT));
_ImagePanel.setPreferredSize(
new Dimension(kWIDTH, kHEIGHT));
_Images = new BufferedImage[kNUM_IMAGES];
for (int i = 0; i < _Images.length; ++i) {
_Images[i] = new BufferedImage(kWIDTH, kHEIGHT,
BufferedImage.TYPE_INT_ARGB);
Color color = new Color(128, 128, 20 * i);
Graphics g = _Images[i].getGraphics();
g.setColor(color);
g.fillRect(0, 0, kWIDTH, kHEIGHT);
g.dispose();
}
_Timer = new Timer(this.getClass().getName());
}
public JPanel getPanel() {
return _ImagePanel;
}
public void start() {
if (null != _TimerTask) {
_TimerTask.cancel();
_TimerTask = null;
}
_TimerTask = new TimerTask() {
@Override
public void run() {
++_TimerCnt;
++_Index;
if (_Index >= kNUM_IMAGES) {
_Index = 0;
}
_ImagePanel.repaint();
System.out.println("Timer: " + _TimerCnt +
" Paint: " + _PaintCnt);
// Also tried _ImagePanel.paintImmediately
// (0, 0, kWIDTH, kHEIGHT);
}
};
_Timer.scheduleAtFixedRate(_TimerTask, 1000,
kREPAINT_DELAY);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
PanelRepaintIssue tPanel =
new PanelRepaintIssue();
tPanel.start();
JFrame tFrame = new JFrame("PanelRepaintIssue");
tFrame.add(tPanel.getPanel());
tFrame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
tFrame.setResizable(false);
tFrame.pack();
tFrame.setVisible(true);
}
});
}
}
编辑:根据评论中提供的新信息而不是原始问题,我重新编写了代码。
我没发现问题。这是 Timer
方法中 println
的最后 4 行。
Timer: 200 Paint: 201
Timer: 201 Paint: 202
Timer: 202 Paint: 203
Timer: 203 Paint: 205
如您所见,数字是同步的,除了最后一行。差异可能是我通过Eclipse取消程序造成的。
这是修改后的代码。我仍然没有看到问题。
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class PanelRepaintIssue {
private static final int kWIDTH = 300;
private static final int kHEIGHT = 200;
private static final int kNUM_IMAGES = 10;
private static final int kREPAINT_DELAY = 250;
private final JPanel _ImagePanel;
private final BufferedImage[] _Images;
private final Timer _Timer;
private TimerTask _TimerTask;
private int _Index;
private int _TimerCnt;
private int _PaintCnt;
public PanelRepaintIssue() {
_Index = 0;
_TimerCnt = 0;
_PaintCnt = 0;
_ImagePanel = new JPanel() {
private static final long serialVersionUID = 1L;
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(_Images[_Index], 0, 0, null);
++_PaintCnt;
}
};
_ImagePanel.setSize(new Dimension(kWIDTH, kHEIGHT));
_ImagePanel.setPreferredSize(
new Dimension(kWIDTH, kHEIGHT));
_Images = new BufferedImage[kNUM_IMAGES];
int x = kWIDTH / 2;
int y = kHEIGHT / 2;
for (int i = 0; i < _Images.length; ++i) {
_Images[i] = new BufferedImage(kWIDTH, kHEIGHT,
BufferedImage.TYPE_INT_ARGB);
String text = Integer.toString(i + 1);
Graphics g = _Images[i].getGraphics();
g.setFont(g.getFont().deriveFont(80f));
g.setColor(Color.BLACK);
g.drawString(text, x, y);
g.dispose();
}
_Timer = new Timer(this.getClass().getName());
}
public JPanel getPanel() {
return _ImagePanel;
}
public void start() {
if (null != _TimerTask) {
_TimerTask.cancel();
_TimerTask = null;
}
_TimerTask = new TimerTask() {
@Override
public void run() {
++_TimerCnt;
++_Index;
if (_Index >= kNUM_IMAGES) {
_Index = 0;
}
updateImagePanel();
System.out.println("Timer: " + _TimerCnt +
" Paint: " + _PaintCnt);
}
};
_Timer.scheduleAtFixedRate(_TimerTask, 1000,
kREPAINT_DELAY);
}
private void updateImagePanel() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
_ImagePanel.repaint();
}
});
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
PanelRepaintIssue tPanel =
new PanelRepaintIssue();
tPanel.start();
JFrame tFrame = new JFrame("PanelRepaintIssue");
tFrame.add(tPanel.getPanel());
tFrame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
tFrame.setResizable(false);
tFrame.pack();
tFrame.setVisible(true);
}
});
}
}
再次编辑:我在准备午餐时 运行 你的代码。这是 println
输出的最后几行。
Timer: 4037 Paint: 4038
Timer: 4038 Paint: 4040
Timer: 4039 Paint: 4040
Timer: 4040 Paint: 4041
Timer: 4041 Paint: 4042
Timer: 4042 Paint: 4043
如您所见,没有丢掉一帧动画。以每帧 250 毫秒的速度,Swing 重绘管理器绘制图像没有问题。
为了好玩,我将 kREPAINT_DELAY
减少到每帧 10 毫秒。那是 100 帧/秒。
这是该测试的 println 行。
Timer: 1104 Paint: 1105
Timer: 1105 Paint: 1106
Timer: 1106 Paint: 1107
Timer: 1107 Paint: 1108
Timer: 1108 Paint: 1109
Timer: 1109 Paint: 1110
同样,没有丢帧。
问题可能出在您的代码中,而不是出在 Swing 重绘管理器中。
问题最终是 Linux 平台特定的,可能是其他平台,但只测试了 Linux。该问题已通过使用以下内容得到纠正或避免:
_ImagePanel.repaint();
Toolkit.getDefaultToolkit().sync();
添加 Toolkit.getDefaultToolkit().sync() 可确保 repaint() 实际更新屏幕上的图像。
我知道 Java RepaintManager 会合并对 repaint() 的调用,这对 99% 的渲染都没有问题。我有一个 JPanel,我想在定时器(100 毫秒)上更新图像以提供像视频一样流畅的渲染。实际上 RepaintManager 似乎 steal/ignore 每隔一个 repaint() 除非鼠标被移动。我想知道我有什么选择可以解决这个问题。我也查看了 paintImmediately(),但它导致与 repaint() 相同的行为,因此不是很有用。预先感谢有用的想法!
- 是否可以为特定的 JPanel 创建和使用自定义 RepaintManager,而对其他一切使用默认值?
- 有没有办法让默认的 RepaintManger 确定某个面板是 "dirty" 以便重新绘制而不是忽略它?
下面是一些代码来说明实现,您会注意到(至少在我的 Linux 测试中),数字几乎会跳过所有其他序列。
public class PanelRepaintIssue
{
private static final int kWIDTH = 200;
private static final int kHEIGHT = 100;
private static final int kNUM_IMAGES = 10;
private static final int kREPAINT_DELAY = 250;
private final JPanel _ImagePanel;
private final BufferedImage[] _Images;
private final Timer _Timer;
private TimerTask _TimerTask;
private int _Index;
public PanelRepaintIssue()
{
_Index = 0;
_ImagePanel = new JPanel()
{
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
if (_Index < kNUM_IMAGES)
{
g.drawImage(_Images[_Index], 0, 0, null);
}
}
};
_ImagePanel.setSize(new Dimension(kWIDTH, kHEIGHT));
_ImagePanel.setPreferredSize(new Dimension(kWIDTH, kHEIGHT));
_Images = new BufferedImage[kNUM_IMAGES];
for (int i = 0; i < _Images.length; ++i)
{
_Images[i] = new BufferedImage(kWIDTH, kHEIGHT, BufferedImage.TYPE_INT_ARGB);
Graphics2D t2d = _Images[i].createGraphics();
t2d.setColor(Color.BLACK);
t2d.fillRect(0, 0, kWIDTH, kHEIGHT);
t2d.setColor(Color.RED);
t2d.drawString(Integer.toString(i), kWIDTH/2, kHEIGHT/2);
t2d.dispose();
}
_Timer = new Timer(this.getClass().getName());
}
public JPanel getPanel()
{
return _ImagePanel;
}
public void start()
{
if (null != _TimerTask)
{
_TimerTask.cancel();
_TimerTask = null;
}
_TimerTask = new TimerTask()
{
@Override
public void run()
{
++_Index;
if (_Index >= kNUM_IMAGES)
{
_Index = 0;
}
_ImagePanel.repaint();
// Also tried _ImagePanel.paintImmediately(0, 0, kWIDTH, kHEIGHT);
}
};
_Timer.scheduleAtFixedRate(_TimerTask, 1000, kREPAINT_DELAY);
}
public static void main(String[] args)
{
PanelRepaintIssue tPanel = new PanelRepaintIssue();
tPanel.start();
JFrame tFrame = new JFrame("PanelRepaintIssue");
tFrame.add(tPanel.getPanel());
tFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
tFrame.setResizable(false);
tFrame.pack();
tFrame.setVisible(true);
}
}
我仍然不确定绘画问题是什么。
我对你的代码做了一些修改。
我使用
SwingUtilities
invokeLater
方法将Swing组件的创建和执行放在Event Dispatch Thread(EDT)上。我删除了
paintComponent
方法中除绘画代码之外的所有内容。paintComponent
方法中除了绘画之外什么都不应该发生。
这是我的代码运行。
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class PanelRepaintIssue {
private static final int kWIDTH = 200;
private static final int kHEIGHT = 100;
private static final int kNUM_IMAGES = 10;
private static final int kREPAINT_DELAY = 250;
private final JPanel _ImagePanel;
private final BufferedImage[] _Images;
private final Timer _Timer;
private TimerTask _TimerTask;
private int _Index;
private int _TimerCnt;
private int _PaintCnt;
public PanelRepaintIssue() {
_Index = 0;
_TimerCnt = 0;
_PaintCnt = 0;
_ImagePanel = new JPanel() {
private static final long serialVersionUID = 1L;
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// if (_Index < kNUM_IMAGES) {
g.drawImage(_Images[_Index], 0, 0, null);
++_PaintCnt;
// System.out.println("Timer: " + _TimerCnt +
// " Paint: " + _PaintCnt);
// }
}
};
_ImagePanel.setSize(new Dimension(kWIDTH, kHEIGHT));
_ImagePanel.setPreferredSize(
new Dimension(kWIDTH, kHEIGHT));
_Images = new BufferedImage[kNUM_IMAGES];
for (int i = 0; i < _Images.length; ++i) {
_Images[i] = new BufferedImage(kWIDTH, kHEIGHT,
BufferedImage.TYPE_INT_ARGB);
Color color = new Color(128, 128, 20 * i);
Graphics g = _Images[i].getGraphics();
g.setColor(color);
g.fillRect(0, 0, kWIDTH, kHEIGHT);
g.dispose();
}
_Timer = new Timer(this.getClass().getName());
}
public JPanel getPanel() {
return _ImagePanel;
}
public void start() {
if (null != _TimerTask) {
_TimerTask.cancel();
_TimerTask = null;
}
_TimerTask = new TimerTask() {
@Override
public void run() {
++_TimerCnt;
++_Index;
if (_Index >= kNUM_IMAGES) {
_Index = 0;
}
_ImagePanel.repaint();
System.out.println("Timer: " + _TimerCnt +
" Paint: " + _PaintCnt);
// Also tried _ImagePanel.paintImmediately
// (0, 0, kWIDTH, kHEIGHT);
}
};
_Timer.scheduleAtFixedRate(_TimerTask, 1000,
kREPAINT_DELAY);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
PanelRepaintIssue tPanel =
new PanelRepaintIssue();
tPanel.start();
JFrame tFrame = new JFrame("PanelRepaintIssue");
tFrame.add(tPanel.getPanel());
tFrame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
tFrame.setResizable(false);
tFrame.pack();
tFrame.setVisible(true);
}
});
}
}
编辑:根据评论中提供的新信息而不是原始问题,我重新编写了代码。
我没发现问题。这是 Timer
方法中 println
的最后 4 行。
Timer: 200 Paint: 201
Timer: 201 Paint: 202
Timer: 202 Paint: 203
Timer: 203 Paint: 205
如您所见,数字是同步的,除了最后一行。差异可能是我通过Eclipse取消程序造成的。
这是修改后的代码。我仍然没有看到问题。
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class PanelRepaintIssue {
private static final int kWIDTH = 300;
private static final int kHEIGHT = 200;
private static final int kNUM_IMAGES = 10;
private static final int kREPAINT_DELAY = 250;
private final JPanel _ImagePanel;
private final BufferedImage[] _Images;
private final Timer _Timer;
private TimerTask _TimerTask;
private int _Index;
private int _TimerCnt;
private int _PaintCnt;
public PanelRepaintIssue() {
_Index = 0;
_TimerCnt = 0;
_PaintCnt = 0;
_ImagePanel = new JPanel() {
private static final long serialVersionUID = 1L;
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(_Images[_Index], 0, 0, null);
++_PaintCnt;
}
};
_ImagePanel.setSize(new Dimension(kWIDTH, kHEIGHT));
_ImagePanel.setPreferredSize(
new Dimension(kWIDTH, kHEIGHT));
_Images = new BufferedImage[kNUM_IMAGES];
int x = kWIDTH / 2;
int y = kHEIGHT / 2;
for (int i = 0; i < _Images.length; ++i) {
_Images[i] = new BufferedImage(kWIDTH, kHEIGHT,
BufferedImage.TYPE_INT_ARGB);
String text = Integer.toString(i + 1);
Graphics g = _Images[i].getGraphics();
g.setFont(g.getFont().deriveFont(80f));
g.setColor(Color.BLACK);
g.drawString(text, x, y);
g.dispose();
}
_Timer = new Timer(this.getClass().getName());
}
public JPanel getPanel() {
return _ImagePanel;
}
public void start() {
if (null != _TimerTask) {
_TimerTask.cancel();
_TimerTask = null;
}
_TimerTask = new TimerTask() {
@Override
public void run() {
++_TimerCnt;
++_Index;
if (_Index >= kNUM_IMAGES) {
_Index = 0;
}
updateImagePanel();
System.out.println("Timer: " + _TimerCnt +
" Paint: " + _PaintCnt);
}
};
_Timer.scheduleAtFixedRate(_TimerTask, 1000,
kREPAINT_DELAY);
}
private void updateImagePanel() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
_ImagePanel.repaint();
}
});
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
PanelRepaintIssue tPanel =
new PanelRepaintIssue();
tPanel.start();
JFrame tFrame = new JFrame("PanelRepaintIssue");
tFrame.add(tPanel.getPanel());
tFrame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
tFrame.setResizable(false);
tFrame.pack();
tFrame.setVisible(true);
}
});
}
}
再次编辑:我在准备午餐时 运行 你的代码。这是 println
输出的最后几行。
Timer: 4037 Paint: 4038
Timer: 4038 Paint: 4040
Timer: 4039 Paint: 4040
Timer: 4040 Paint: 4041
Timer: 4041 Paint: 4042
Timer: 4042 Paint: 4043
如您所见,没有丢掉一帧动画。以每帧 250 毫秒的速度,Swing 重绘管理器绘制图像没有问题。
为了好玩,我将 kREPAINT_DELAY
减少到每帧 10 毫秒。那是 100 帧/秒。
这是该测试的 println 行。
Timer: 1104 Paint: 1105
Timer: 1105 Paint: 1106
Timer: 1106 Paint: 1107
Timer: 1107 Paint: 1108
Timer: 1108 Paint: 1109
Timer: 1109 Paint: 1110
同样,没有丢帧。
问题可能出在您的代码中,而不是出在 Swing 重绘管理器中。
问题最终是 Linux 平台特定的,可能是其他平台,但只测试了 Linux。该问题已通过使用以下内容得到纠正或避免:
_ImagePanel.repaint();
Toolkit.getDefaultToolkit().sync();
添加 Toolkit.getDefaultToolkit().sync() 可确保 repaint() 实际更新屏幕上的图像。