3

I'm working on an app where I draw everything pixel by pixel. During the process, I noticed that the paintComponent() method of a certain JPanel is called twice. So I created the following MCVE to figure out whether it has something to do with the other components being drawn to the screen, or if it's just a standalone issue:

App.java

public class App {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new MainFrame("Paint test"));
    }
}

MainFrame.java

public class MainFrame extends JFrame {
    private Board mBoard;

    public MainFrame(String title) {
        super(title);

        setMinimumSize(new Dimension(400, 400));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setLayout(new BorderLayout());

        mBoard = new Board();
        add(mBoard, BorderLayout.CENTER);

        setVisible(true);
    }
}

Board.java

public class Board extends JPanel {

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        System.out.println("CALLED");
    }
}

But the console log shows "CALLED" twice.

I'm specifically interested in paintComponent() since it's the method where all of the app's magic works, so I need to control each draw cycle.

What are the reasons behind this double call to paintComponent() ?

Is there another way I could do my drawings some other way once ? (I mean, not in paintComponent(), if it would be called twice no matter what)

Mohammed Aouf Zouag
  • 17,042
  • 4
  • 41
  • 67
  • 1
    Possible duplicate of [why is my code executing paintComponent(Graphics page) twice?](http://stackoverflow.com/questions/4814289/why-is-my-code-executing-paintcomponentgraphics-page-twice) – BackSlash Dec 29 '15 at 23:43
  • Hey @BackSlash , thanks for the link but I've already checked it out (& the link mentionned within it). In that post, it's stated that it's an OS thing, but **I need to control that behavior.** – Mohammed Aouf Zouag Dec 29 '15 at 23:45
  • 1
    You can put println statements in the MainFrame constructor to verify, but I suspect the add method and the setVisible method cause a JPanel repaint. You cause a repaint when you minimize and maximize the JFrame. You have no control over the paintComponent method. That's why you do nothing but paint in the paintComponent method. You perform calculations elsewhere in your code. – Gilbert Le Blanc Dec 29 '15 at 23:48
  • @GilbertLeBlanc I don't think painting is done on invisible components, or am I wrong? – user1803551 Dec 29 '15 at 23:49
  • For me the above code prints only once. Win 7 Java 1.8.0_25. – user1803551 Dec 29 '15 at 23:52
  • @user1803551 as stated in the *"maybe-is-an-answer"*, the behavior is **OS-specific**. (Win 10, Java 1.8 too) – Mohammed Aouf Zouag Dec 29 '15 at 23:54
  • Is [*double buffering*](http://www.oracle.com/technetwork/java/painting-140037.html#db) enabled? – Elliott Frisch Dec 29 '15 at 23:56
  • A component might be painted a number of times in quick succession when it's first displayed for a number of reasons. A lot will come down to how the native peer is initialised and prepared and how the layout is invalidated and updated when the window is finally realised and made visible. The fact is, you really shouldn't care – MadProgrammer Dec 29 '15 at 23:56
  • 1
    @ElliottFrisch Unless explicitly set otherwise, double buffering is enabled by default – MadProgrammer Dec 29 '15 at 23:57
  • Yes, I saw, but what it shows is that you can't control this (unless you do some hefty native code stuff I guess). It's going to be difficult to answer *why* is happens. Do you intent to redirect your question? Note that you shouldn't do heavy calculations inside `paintComponent`, the call should return quickly. – user1803551 Dec 29 '15 at 23:57
  • On MaxOSX, Java 8, it only gets called once when initialising the screen – MadProgrammer Dec 30 '15 at 00:00
  • In that case, I must have got it all wrong. Since I'm doing lots of calculations & drawings inside... & I really need to control that initial, *"first appearance on stage"* behavior of the panel. – Mohammed Aouf Zouag Dec 30 '15 at 00:04
  • Are the heavy calculations done only for the initial display or throughout the applications life? Is the initial state dependent on launch parameters (`String[] args`)? – user1803551 Dec 30 '15 at 00:08
  • These calculations are independent of anything else... just to explain myself more, this is a tic tac toe game. So I'm basically drawing rectangles inside rectangles inside other rectangles... but the problem is, that since `paintComponent` is called twice, some of the game's logic is executed twice. – Mohammed Aouf Zouag Dec 30 '15 at 00:11

2 Answers2

5

Why is the paintComponent method called twice?

Because it is called whenever the plug-in determines it is needed.

I need to control that behavior.

Better to use an approach that works, rather than chase an end that cannot be achieved.

The logical approach to this conundrum though, is to instead draw everything to a BufferedImage that is displayed in (an ImageIcon that is displayed in) a JLabel. Then no matter how many times the repaint() method of the label is called, the app. still only needs to generate the pixels once.

Examples

  1. Draw in an image inside panel

    enter image description here

  2. Multiple calls of paintComponent()

    enter image description here

  3. Drawing the Cannabis Curve

    enter image description here

Community
  • 1
  • 1
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
0

After reading the short discussion in the comments I have a suggestion. Prepare the Shape object and save it as a resource. This will save the runtime calculations. When the program launches, load the file in a separate thread (parallel to loading the GUI) and then just paint that object. Doing just the painting shouldn't be expensive so even if it's called several times there shouldn't be a problem.

user1803551
  • 12,965
  • 5
  • 47
  • 74
  • I'll give it a shot in 16 hours, & get back to you. (phone mode ON) – Mohammed Aouf Zouag Dec 30 '15 at 00:18
  • The `Graphics` object passed to your `paintComponent` is a shared resource, it's used by all the components within the current window to paint onto, you should NEVER maintain a reference to it (or `dispose` it), this is not how painting is done. If needed use a `BufferedImage`, also remember, Swing is NOT thread safe, so you should ALWAYS ensure that the UI (or any part of the UI) is updated only from within the context of the Event Dispatching Thread – MadProgrammer Dec 30 '15 at 01:53
  • @MadProgrammer Oops. Thanks, updated. I said `Graphics` but was thinking about the `Shape` painted by it. I remember doing that a few years ago and it worked fine, shouldn't it? – user1803551 Dec 30 '15 at 02:11
  • As long as the actually painting is done within the context of the EDT and through the appropriate paint methods, yes. Just beware that when the shape is been painted, you should not be changing the shape – MadProgrammer Dec 30 '15 at 02:30
  • @MadProgrammer I don't think that shape is going to change based on the OP's comment. If it does need to change, it can be from the EDT before painting it. – user1803551 Dec 30 '15 at 02:39
  • @MadProgrammer BTW, you shouldn't be shy of downvoting (my) bad answers. De/Serializing `Graphics` objects in this context certainly deserves one. – user1803551 Dec 30 '15 at 02:40
  • I'm just hoping to pull you (kicking and screaming) to a better one ;) – MadProgrammer Dec 30 '15 at 03:42
  • *"Prepare the `Shape` object and save it as a resource."* It can be tricky to serialize a `Shape`. See [How to serialize Java 2D Shape objects as XML?](http://stackoverflow.com/q/26579729/418556) for tips. – Andrew Thompson Dec 30 '15 at 04:03