多次repaint()后如何重绘JComponent一次?

How to repaint JComponent once after several repaint()?

我的程序在 JButton 列表上循环。对于每个 JButton,它每秒重新绘制一次:

public class a {
    static JFrame frame = new JFrame();
    static Set<MyButton> components = new HashSet<MyButton>();

    public static void main(String[] args) {
        JPanel lPanel = new JPanel(new GridLayout(0,18));

        for (int i = 0; i < 500; i++) {
            MyButton lButton = new MyButton("Hello" + i);
            lPanel.add(lButton);
            components.add(lButton);
        }
        frame.add(lPanel);
        frame.pack();
        frame.setVisible(true);
        blinking();
    }

    public static void blinking() {
        Timer blinkTimer = new Timer(500, new ActionListener() {
            boolean on = false;

            public void actionPerformed(ActionEvent e) {
                for (MyButton lButton : components) {
                    lButton.blink(on);
                }
                on = !on;
            }
        });
        blinkTimer.start();
    }
}

public class MyButton extends JButton {

    public MyButton(String text) {
        super(text);
    }

    public void blink(boolean on) {
        setBackground(on ? Color.DARK_GRAY : Color.LIGHT_GRAY);
        setForeground(on ? Color.WHITE : Color.BLACK);
    }
}

结果:

我想确保所有按钮同时更新它们的背景和前景色。

JButton extends JComponentJComponent.setBackground 调用 repaint(),以及 JComponent.setForeground.

因此,我的程序每秒为每个按钮调用 repaint() 两次,并且颜色并不总是同时更新。

如何保证所有按钮的颜色同时更新?我认为解决方案是只调用一个 repaint(),也许在按钮容器上,但我该怎么做?

按钮是否都在面板的同一层?那为什么不使用那个层的背景而不是按钮的背景呢?

例如:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Main {
    private static JButton newButton() {
        final JButton b = new JButton("x");
        b.setContentAreaFilled(false);
        //b.setOpaque(false); //Do not make this call, because setContentAreaFilled documentation says so.
        return b;
    }

    public static void main(final String[] args) {
        final JButton tester = newButton();
        final Dimension testerDim = tester.getPreferredSize();
        final Dimension maxDim = new Dimension(1200, 700);

        final int rows = maxDim.width / testerDim.width,
                  cols = maxDim.height / testerDim.height;

        final JPanel buttonPanel = new JPanel(new GridLayout(0, cols));
        final int n = rows * cols;
        buttonPanel.add(tester);
        for (int i = 1; i < n; ++i)
            buttonPanel.add(newButton());

        final JFrame frame = new JFrame("Button grid");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(buttonPanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        final Color bg = buttonPanel.getBackground();
        final Timer timer = new Timer(500, new ActionListener() {
            private boolean on = false;

            @Override
            public void actionPerformed(final ActionEvent e) {
                buttonPanel.setBackground(on? bg: bg.darker());
                on = !on;
            }
        });
        timer.setRepeats(true);
        timer.start();
    }
}

我认为这是您可以用单个 core/thread 做的最好的事情。

此外,如果您想要按钮之间的其他组件,它们的背景与图层的背景不同,那么只需将它们设为不透明 (setOpaque(true)),如果它们还没有。

解决方案:使用 setIgnoreRepaint() 并仅重绘框架:

frame.setIgnoreRepaint(true);
for (JButton lButton : components) {
                    // blink the buttons background on and off
    lButton.blink(on);
}
frame.setIgnoreRepaint(false);
frame.repaint();
on = !on;

setIgnoreRepaint 文档有点误导,因为它说暂停重绘系统事件。所以我们正在考虑 OS 可以发送的事件。但实际上它忽略了重绘(隐式或显式),并且不忽略直接在组件上调用的方法(paint / paintImmediatly ...所有这些)。