Repaint() 请求合并成一个是一个神话?
Repaint() requests coalescing into one is a myth?
我看到的每个地方都说无需担心多个 repaint() 请求,因为它们正在合并并推迟在一个优化的批次中同时发生。但是这个简单的 SSCE 表明事实并非如此。每个按钮都以看似随机的顺序分别重新绘制。
/*****************************************************************************
SSCE1.java
*****************************************************************************/
package com.example;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
* <p>No paint coalescing!</p>
*/
final class SSCE1
{
private static class MainWin
{
private static final int NUMBTN = 400;
private final JButton[] buttons;
private final JButton reset_button;
private final JButton quit_button;
private final JPanel tools;
private final JPanel main;
private final JPanel root;
private final JFrame frame;
private final ActionListener reset_handler;
private final ActionListener quit_handler;
private final ActionListener btn_handler;
MainWin()
{
this.buttons = new JButton[NUMBTN];
this.reset_button = new JButton("Reset");
this.quit_button = new JButton("Quit");
this.tools = new JPanel(new BorderLayout(5,5));
this.main = new JPanel(new FlowLayout(FlowLayout.LEFT,5,5));
this.root = new JPanel(new BorderLayout(5,5));
this.frame = new JFrame("Paint coalescing is a myth!");
this.reset_handler = (ev)->{ for( int i=0; i<NUMBTN; ++i ) buttons[i].setEnabled(true); };
this.quit_handler = (ev)->{ Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(new WindowEvent(frame,WindowEvent.WINDOW_CLOSING)); };
this.btn_handler = (ev)->{ for( int i=0; i<NUMBTN; ++i ) buttons[i].setEnabled(false); };
root.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
reset_button.addActionListener(reset_handler);
quit_button.addActionListener(quit_handler);
for( int i=0; i<NUMBTN; ++i ) {
buttons[i] = new JButton(Integer.toString(i+1));
buttons[i].addActionListener(btn_handler);
main.add(buttons[i]);
}
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setUndecorated(true);
frame.setBounds(GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds());
tools.add(reset_button,BorderLayout.NORTH);
tools.add(quit_button,BorderLayout.SOUTH);
root.add(tools,BorderLayout.WEST);
root.add(main,BorderLayout.CENTER);
frame.getContentPane().add(root,BorderLayout.CENTER);
}
private void open()
{
frame.setVisible(true);
}
}
public static void main( String[] args )
{
SwingUtilities.invokeLater(()->{
MainWin win = new MainWin();
win.open();
});
}
}
所以问题是:如何打开重绘合并?
当然,我可以做类似 SSCE #2 的事情——创建我自己的按钮,然后一切正常。但是标准控件呢?
/*****************************************************************************
SSCE2.java
*****************************************************************************/
package com.example;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowEvent;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
* <p>We must do something about it.</p>
*/
final class SSCE2
{
private static interface Handler
{
void handleClicked( FakeBtn src );
}
private static class FakeBtn extends JLabel
{
private static final long serialVersionUID = -1372036387522045748L;
private static final Color back = new Color(0x66,0x77,0x88);
private static final Color backdown = back.darker();
private static final Color backhover = back.brighter();
private static final Color backoff = Color.DARK_GRAY;
private static class MouseHandler implements MouseMotionListener, MouseListener
{
@Override public void mouseClicked( MouseEvent ev )
{
FakeBtn btn = (FakeBtn)ev.getSource();
btn.setDownState(false);
if( btn.m_click_handler!=null )
btn.m_click_handler.handleClicked(btn);
}
@Override public void mousePressed( MouseEvent ev )
{
FakeBtn btn = (FakeBtn)ev.getSource();
if( btn.m_state_active )
btn.setDownState(true);
}
@Override public void mouseReleased( MouseEvent ev )
{
FakeBtn btn = (FakeBtn)ev.getSource();
btn.setDownState(false);
}
@Override public void mouseEntered( MouseEvent ev )
{
FakeBtn btn = (FakeBtn)ev.getSource();
btn.setHoverState(true);
}
@Override public void mouseExited( MouseEvent ev )
{
FakeBtn btn = (FakeBtn)ev.getSource();
btn.setHoverState(false);
}
@Override public void mouseDragged( MouseEvent ev )
{
}
@Override public void mouseMoved( MouseEvent ev )
{
}
}
private static MouseHandler mhandler = new MouseHandler();
private boolean m_state_active;
private boolean m_state_down;
private boolean m_state_hover;
private Handler m_click_handler;
FakeBtn( String label )
{
super(label);
this.m_state_active = true;
this.m_state_down = false;
this.m_state_hover = false;
this.m_click_handler = null;
addMouseListener(mhandler);
addMouseMotionListener(mhandler);
}
private void setActiveState( boolean flag )
{
m_state_active = flag;
getParent().repaint(getX(),getY(),getWidth(),getHeight());
}
private void setDownState( boolean flag )
{
m_state_down = flag;
getParent().repaint(getX(),getY(),getWidth(),getHeight());
}
private void setHoverState( boolean flag )
{
m_state_hover = flag;
setCursor( m_state_active && m_state_hover ? Cursor.getPredefinedCursor(Cursor.HAND_CURSOR) : Cursor.getDefaultCursor() );
getParent().repaint(getX(),getY(),getWidth(),getHeight());
}
@Override protected void paintBorder( Graphics g )
{
g.setColor(m_state_active?m_state_hover?Color.RED:Color.WHITE:Color.BLACK);
g.drawRect(0,0,getWidth()-1,getHeight()-1);
}
@Override public Dimension getPreferredSize()
{
Dimension ret = super.getPreferredSize();
ret.width = 80;
ret.height = 40;
return ret;
}
@Override protected void paintComponent( Graphics g )
{
g.setColor(m_state_active?m_state_down?backdown:m_state_hover?backhover:back:backoff);
g.fillRect(0,0,getWidth(),getHeight());
int w = getWidth();
int h = getHeight();
String text = getText();
FontMetrics fm = g.getFontMetrics();
int tw = fm.stringWidth(text);
int th = fm.getHeight();
int base = fm.getMaxAscent();
g.setColor(m_state_active?Color.WHITE:Color.GRAY);
g.drawString(text,(w-tw)/2,(h-th)/2+base);
}
void setActive( boolean state )
{
setActiveState(state);
}
void setHandler( Handler h )
{
m_click_handler = h;
}
}
private static class MainWin
{
private static final int NUMBTN = 400;
private final FakeBtn[] buttons;
private final FakeBtn reset_button;
private final FakeBtn quit_button;
private final JPanel tools;
private final JPanel main;
private final JPanel root;
private final JFrame frame;
private final Handler reset_handler;
private final Handler quit_handler;
private final Handler btn_handler;
MainWin()
{
this.buttons = new FakeBtn[NUMBTN];
this.reset_button = new FakeBtn("Reset");
this.quit_button = new FakeBtn("Quit");
this.tools = new JPanel(new BorderLayout());
this.main = new JPanel(new FlowLayout(FlowLayout.LEFT));
this.root = new JPanel(new BorderLayout());
this.frame = new JFrame("Paint coalescing is a myth!");
this.reset_handler = (src)->{ for( int i=0; i<NUMBTN; ++i ) buttons[i].setActive(true); };
this.quit_handler = (src)->{ Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(new WindowEvent(frame,WindowEvent.WINDOW_CLOSING)); };
this.btn_handler = (src)->{ for( int i=0; i<NUMBTN; ++i ) buttons[i].setActive(false); };
root.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
reset_button.setHandler(reset_handler);
quit_button.setHandler(quit_handler);
for( int i=0; i<NUMBTN; ++i ) {
buttons[i] = new FakeBtn(Integer.toString(i+1));
buttons[i].setHandler(btn_handler);
main.add(buttons[i]);
}
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setUndecorated(true);
frame.setBounds(GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds());
tools.add(reset_button,BorderLayout.NORTH);
tools.add(quit_button,BorderLayout.SOUTH);
root.add(tools,BorderLayout.WEST);
root.add(main,BorderLayout.CENTER);
frame.getContentPane().add(root,BorderLayout.CENTER);
}
private void open()
{
frame.setVisible(true);
}
}
public static void main( String[] args )
{
SwingUtilities.invokeLater(()->{
MainWin win = new MainWin();
win.open();
});
}
}
合并绘画请求有两种类型:
如果您重新绘制多个组件,那么将计算由多个组件定义的区域,并为 Graphics 对象设置裁剪区域以最小化正在绘制的区域。您的 SSCE1 代码就是一个例子。
当您多次重新绘制同一个组件时。通常,当您同时设置组件的多个属性时会发生这种情况,例如前景、背景、边框、字体等。或者如果您在循环中多次重置相同的 属性,例如,组件的文本。
这是在不断重置相同 属性 时合并重绘请求的示例:
即使标签的文本更改了 10 次,也只生成了一个 paintComponent()
:
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class SSCCE extends JPanel
{
public SSCCE()
{
JLabel label = new JLabel("label 0")
{
@Override
public void setText(String text)
{
super.setText(text);
System.out.println("new text: " + text);
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
System.out.println("paintComponent");
}
};
add(label);
JButton button = new JButton("Click Me");
button.addActionListener((e) -> { for( int i=0; i < 10; ++i ) label.setText("label " + i); });
add(button);
}
private static void createAndShowGUI()
{
System.setProperty("sun.awt.noerasebackground", "false");
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SSCCE());
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(300, 200);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args) throws Exception
{
java.awt.EventQueue.invokeLater( () -> createAndShowGUI() );
}
}
编辑:
我在您的原始代码中做了以下更改:
buttons[i] = new JButton(Integer.toString(i+1))
{
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
System.out.println("painting: " + getText());
}
};
执行此操作时,您会注意到:
- 首次显示屏幕时,按钮以相反的顺序绘制。这是正常的,也是 Swing 支持基于 ZOrder 的绘画的方式。
- 当您单击一个按钮时,按钮将被禁用并以随机顺序绘制(如您所见)。注意我查看了 RepaintManager 源代码。在高层次上,我可以看到它使用 Map 来保存需要绘制的组件。这可以解释绘画的随机顺序,但不能解释为什么它们仍然不是同时绘制的。它们都应该被绘制到缓冲区(因此顺序无关紧要)然后缓冲区被绘制。
然后我做了以下更改:
//this.btn_handler = (ev)->{ for( int i=0; i<NUMBTN; ++i ) buttons[i].setEnabled(false); };
this.btn_handler = (ev)->{ for( int i=0; i<NUMBTN; ++i ) buttons[i].setEnabled(false); ((JButton)ev.getSource()).getParent().repaint();};
父面板上的 repaint() 似乎按预期一次绘制了所有组件。这是有道理的,因为现在只有一个组件要绘制。它的子组件将按照正常的绘制逻辑进行绘制。
请注意,如果您在代码中保留 System.out.println(...) 语句,那么当您单击“重置”按钮时,您真的可以看到随机绘制按钮的效果。
how do I turn on repaint coalescing?
似乎简单的答案是在更改每个单独按钮的状态后重新绘制()父面板。
我看到的每个地方都说无需担心多个 repaint() 请求,因为它们正在合并并推迟在一个优化的批次中同时发生。但是这个简单的 SSCE 表明事实并非如此。每个按钮都以看似随机的顺序分别重新绘制。
/*****************************************************************************
SSCE1.java
*****************************************************************************/
package com.example;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
* <p>No paint coalescing!</p>
*/
final class SSCE1
{
private static class MainWin
{
private static final int NUMBTN = 400;
private final JButton[] buttons;
private final JButton reset_button;
private final JButton quit_button;
private final JPanel tools;
private final JPanel main;
private final JPanel root;
private final JFrame frame;
private final ActionListener reset_handler;
private final ActionListener quit_handler;
private final ActionListener btn_handler;
MainWin()
{
this.buttons = new JButton[NUMBTN];
this.reset_button = new JButton("Reset");
this.quit_button = new JButton("Quit");
this.tools = new JPanel(new BorderLayout(5,5));
this.main = new JPanel(new FlowLayout(FlowLayout.LEFT,5,5));
this.root = new JPanel(new BorderLayout(5,5));
this.frame = new JFrame("Paint coalescing is a myth!");
this.reset_handler = (ev)->{ for( int i=0; i<NUMBTN; ++i ) buttons[i].setEnabled(true); };
this.quit_handler = (ev)->{ Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(new WindowEvent(frame,WindowEvent.WINDOW_CLOSING)); };
this.btn_handler = (ev)->{ for( int i=0; i<NUMBTN; ++i ) buttons[i].setEnabled(false); };
root.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
reset_button.addActionListener(reset_handler);
quit_button.addActionListener(quit_handler);
for( int i=0; i<NUMBTN; ++i ) {
buttons[i] = new JButton(Integer.toString(i+1));
buttons[i].addActionListener(btn_handler);
main.add(buttons[i]);
}
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setUndecorated(true);
frame.setBounds(GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds());
tools.add(reset_button,BorderLayout.NORTH);
tools.add(quit_button,BorderLayout.SOUTH);
root.add(tools,BorderLayout.WEST);
root.add(main,BorderLayout.CENTER);
frame.getContentPane().add(root,BorderLayout.CENTER);
}
private void open()
{
frame.setVisible(true);
}
}
public static void main( String[] args )
{
SwingUtilities.invokeLater(()->{
MainWin win = new MainWin();
win.open();
});
}
}
所以问题是:如何打开重绘合并? 当然,我可以做类似 SSCE #2 的事情——创建我自己的按钮,然后一切正常。但是标准控件呢?
/*****************************************************************************
SSCE2.java
*****************************************************************************/
package com.example;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowEvent;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
* <p>We must do something about it.</p>
*/
final class SSCE2
{
private static interface Handler
{
void handleClicked( FakeBtn src );
}
private static class FakeBtn extends JLabel
{
private static final long serialVersionUID = -1372036387522045748L;
private static final Color back = new Color(0x66,0x77,0x88);
private static final Color backdown = back.darker();
private static final Color backhover = back.brighter();
private static final Color backoff = Color.DARK_GRAY;
private static class MouseHandler implements MouseMotionListener, MouseListener
{
@Override public void mouseClicked( MouseEvent ev )
{
FakeBtn btn = (FakeBtn)ev.getSource();
btn.setDownState(false);
if( btn.m_click_handler!=null )
btn.m_click_handler.handleClicked(btn);
}
@Override public void mousePressed( MouseEvent ev )
{
FakeBtn btn = (FakeBtn)ev.getSource();
if( btn.m_state_active )
btn.setDownState(true);
}
@Override public void mouseReleased( MouseEvent ev )
{
FakeBtn btn = (FakeBtn)ev.getSource();
btn.setDownState(false);
}
@Override public void mouseEntered( MouseEvent ev )
{
FakeBtn btn = (FakeBtn)ev.getSource();
btn.setHoverState(true);
}
@Override public void mouseExited( MouseEvent ev )
{
FakeBtn btn = (FakeBtn)ev.getSource();
btn.setHoverState(false);
}
@Override public void mouseDragged( MouseEvent ev )
{
}
@Override public void mouseMoved( MouseEvent ev )
{
}
}
private static MouseHandler mhandler = new MouseHandler();
private boolean m_state_active;
private boolean m_state_down;
private boolean m_state_hover;
private Handler m_click_handler;
FakeBtn( String label )
{
super(label);
this.m_state_active = true;
this.m_state_down = false;
this.m_state_hover = false;
this.m_click_handler = null;
addMouseListener(mhandler);
addMouseMotionListener(mhandler);
}
private void setActiveState( boolean flag )
{
m_state_active = flag;
getParent().repaint(getX(),getY(),getWidth(),getHeight());
}
private void setDownState( boolean flag )
{
m_state_down = flag;
getParent().repaint(getX(),getY(),getWidth(),getHeight());
}
private void setHoverState( boolean flag )
{
m_state_hover = flag;
setCursor( m_state_active && m_state_hover ? Cursor.getPredefinedCursor(Cursor.HAND_CURSOR) : Cursor.getDefaultCursor() );
getParent().repaint(getX(),getY(),getWidth(),getHeight());
}
@Override protected void paintBorder( Graphics g )
{
g.setColor(m_state_active?m_state_hover?Color.RED:Color.WHITE:Color.BLACK);
g.drawRect(0,0,getWidth()-1,getHeight()-1);
}
@Override public Dimension getPreferredSize()
{
Dimension ret = super.getPreferredSize();
ret.width = 80;
ret.height = 40;
return ret;
}
@Override protected void paintComponent( Graphics g )
{
g.setColor(m_state_active?m_state_down?backdown:m_state_hover?backhover:back:backoff);
g.fillRect(0,0,getWidth(),getHeight());
int w = getWidth();
int h = getHeight();
String text = getText();
FontMetrics fm = g.getFontMetrics();
int tw = fm.stringWidth(text);
int th = fm.getHeight();
int base = fm.getMaxAscent();
g.setColor(m_state_active?Color.WHITE:Color.GRAY);
g.drawString(text,(w-tw)/2,(h-th)/2+base);
}
void setActive( boolean state )
{
setActiveState(state);
}
void setHandler( Handler h )
{
m_click_handler = h;
}
}
private static class MainWin
{
private static final int NUMBTN = 400;
private final FakeBtn[] buttons;
private final FakeBtn reset_button;
private final FakeBtn quit_button;
private final JPanel tools;
private final JPanel main;
private final JPanel root;
private final JFrame frame;
private final Handler reset_handler;
private final Handler quit_handler;
private final Handler btn_handler;
MainWin()
{
this.buttons = new FakeBtn[NUMBTN];
this.reset_button = new FakeBtn("Reset");
this.quit_button = new FakeBtn("Quit");
this.tools = new JPanel(new BorderLayout());
this.main = new JPanel(new FlowLayout(FlowLayout.LEFT));
this.root = new JPanel(new BorderLayout());
this.frame = new JFrame("Paint coalescing is a myth!");
this.reset_handler = (src)->{ for( int i=0; i<NUMBTN; ++i ) buttons[i].setActive(true); };
this.quit_handler = (src)->{ Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(new WindowEvent(frame,WindowEvent.WINDOW_CLOSING)); };
this.btn_handler = (src)->{ for( int i=0; i<NUMBTN; ++i ) buttons[i].setActive(false); };
root.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
reset_button.setHandler(reset_handler);
quit_button.setHandler(quit_handler);
for( int i=0; i<NUMBTN; ++i ) {
buttons[i] = new FakeBtn(Integer.toString(i+1));
buttons[i].setHandler(btn_handler);
main.add(buttons[i]);
}
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setUndecorated(true);
frame.setBounds(GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds());
tools.add(reset_button,BorderLayout.NORTH);
tools.add(quit_button,BorderLayout.SOUTH);
root.add(tools,BorderLayout.WEST);
root.add(main,BorderLayout.CENTER);
frame.getContentPane().add(root,BorderLayout.CENTER);
}
private void open()
{
frame.setVisible(true);
}
}
public static void main( String[] args )
{
SwingUtilities.invokeLater(()->{
MainWin win = new MainWin();
win.open();
});
}
}
合并绘画请求有两种类型:
如果您重新绘制多个组件,那么将计算由多个组件定义的区域,并为 Graphics 对象设置裁剪区域以最小化正在绘制的区域。您的 SSCE1 代码就是一个例子。
当您多次重新绘制同一个组件时。通常,当您同时设置组件的多个属性时会发生这种情况,例如前景、背景、边框、字体等。或者如果您在循环中多次重置相同的 属性,例如,组件的文本。
这是在不断重置相同 属性 时合并重绘请求的示例:
即使标签的文本更改了 10 次,也只生成了一个 paintComponent()
:
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class SSCCE extends JPanel
{
public SSCCE()
{
JLabel label = new JLabel("label 0")
{
@Override
public void setText(String text)
{
super.setText(text);
System.out.println("new text: " + text);
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
System.out.println("paintComponent");
}
};
add(label);
JButton button = new JButton("Click Me");
button.addActionListener((e) -> { for( int i=0; i < 10; ++i ) label.setText("label " + i); });
add(button);
}
private static void createAndShowGUI()
{
System.setProperty("sun.awt.noerasebackground", "false");
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SSCCE());
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(300, 200);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args) throws Exception
{
java.awt.EventQueue.invokeLater( () -> createAndShowGUI() );
}
}
编辑:
我在您的原始代码中做了以下更改:
buttons[i] = new JButton(Integer.toString(i+1))
{
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
System.out.println("painting: " + getText());
}
};
执行此操作时,您会注意到:
- 首次显示屏幕时,按钮以相反的顺序绘制。这是正常的,也是 Swing 支持基于 ZOrder 的绘画的方式。
- 当您单击一个按钮时,按钮将被禁用并以随机顺序绘制(如您所见)。注意我查看了 RepaintManager 源代码。在高层次上,我可以看到它使用 Map 来保存需要绘制的组件。这可以解释绘画的随机顺序,但不能解释为什么它们仍然不是同时绘制的。它们都应该被绘制到缓冲区(因此顺序无关紧要)然后缓冲区被绘制。
然后我做了以下更改:
//this.btn_handler = (ev)->{ for( int i=0; i<NUMBTN; ++i ) buttons[i].setEnabled(false); };
this.btn_handler = (ev)->{ for( int i=0; i<NUMBTN; ++i ) buttons[i].setEnabled(false); ((JButton)ev.getSource()).getParent().repaint();};
父面板上的 repaint() 似乎按预期一次绘制了所有组件。这是有道理的,因为现在只有一个组件要绘制。它的子组件将按照正常的绘制逻辑进行绘制。
请注意,如果您在代码中保留 System.out.println(...) 语句,那么当您单击“重置”按钮时,您真的可以看到随机绘制按钮的效果。
how do I turn on repaint coalescing?
似乎简单的答案是在更改每个单独按钮的状态后重新绘制()父面板。