Can a specific RepaintManager be used for a specific JPanel?
I understand that the Java RepaintManager will coalesce calls to repaint(), which is fine for 99% of rendering. I have one JPanel that I'd like to update on a timer (100 ms) with images to provide smooth'ish rendering like video. In practice the RepaintManager seems to steal/ignore about every other repaint() unless the mouse is being moved. I'm wondering what options I have to workaround this issue. I also looked at paintImmediately(), but it results in the same behavior as repaint(), thus not very useful. Thanks in advance for the helpful ideas!
- Is it possible to create and use a custom RepaintManager for a specific JPanel, and use the default for everything else?
- Is there a way to make the default RepaintManger determine a certain panel is "dirty" such that it would be repainted vs. ignored?
Below is some code to illustrate the implementation, you will notice (at least on my Linux testing), that the numbers will skip almost every other sequence.
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);
}
}
2 ответа
Проблема в конечном итоге была связана с платформой Linux, возможно, с другой, но тестировалась только Linux. Проблема была исправлена или устранена с помощью следующего:
_ImagePanel.repaint();
Toolkit.getDefaultToolkit().sync();
Добавление Toolkit.getDefaultToolkit(). Sync() гарантирует, что repaint() действительно обновляет изображение на экране.
Я все еще не уверен, в чем проблема с покраской.
Я внес некоторые изменения в ваш код.
Я использовал
SwingUtilities
invokeLater
, чтобы поместить создание и выполнение компонентов Swing в поток отправки событий (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);
}
});
}
}
Изменить: на основе новой информации, представленной в комментарии вместо исходного вопроса, я переработал код.
Я не видел проблемы. Вот последние 4 строкиprintln
от Timer
метод.
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.