简体   繁体   中英

How to repaint JComponent once after several repaint()?

My program loops on a list of JButton. For each JButton, it repaints it each second:

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);
    }
}

Result: 在此处输入图片说明

I want to ensure that all the buttons update their background and foreground color in the same time.

JButton extends JComponent , and JComponent.setBackground calls repaint() , as well as JComponent.setForeground .

Therefore, each second my program calls repaint() two times for each button, and the colors are not always updated at the same time.

How can I ensure that the colors of all the buttons are updated at the same time? I think the solution is to call only one repaint() , maybe on the buttons container, but how can I do that?

Are the buttons all in the same layer of the panel? Then why not use the background of that layer instead of the buttons' background?

For example:

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();
    }
}

I think this is the best you can do with a single core/thread.

Also, if you want other components between the buttons, where their background is not the same as the layer's one, then just make them opaque ( setOpaque(true) ), if they are not already.

Solution: use setIgnoreRepaint() and repaint only the frame:

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

The setIgnoreRepaint doc is a bit misleading since it says to suspend repaint for system events. So we are thinking of events that the OS could send. But in fact it ignores the repaints (implicit or explicit), and does not ignore the method calls directly on the component (paint / paintImmediatly ... all that).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM