3

I am a newcomer to Java, Swing, and GUI programming, so I am probably missing a number of central points about building a GUI with Swing and the thread model behind. The exercise I am trying consist in a little application for creating, moving and resizing figures on a canvas. Moreover, I am trying to keep the View as behaviourless as possible, with a Presenter object being responsible for injecting the desired behaviour. In other words, I do not want the View to know about figures or how they must be drawn, it simply offers a setUpdater() method for the Presenter to provide an object that knows what must be drawn in order to represent the state of a Model.

But I have found a problem: under some circumstances, figures are lost. For instance, if I iconify and then deiconify the application window. I thought the paintComponent() of my canvas component was not called, but a breakpoint showed me the problem was different: it was called and the figures were painted, but then dissapeared.

I have tried to simplify my code to show the problem without buttons, sliders or even a Model. However, I keep separate classes for the View and the Presenter as this separation is important for my purposes.

In the machine where I am testing the simplified example, the figure that is drawn when paintComponent is called (always the same circle) dissapears not only after deiconification, but every time it is painted.

Please, help me understand what is happening... and how to solve it.

TYIA.

PS1: The simplified code follows:

import java.awt.*;
import javax.swing.*;

interface ViewUpdater {
    void updateCanvas(Graphics2D g2d);
}

class View {
    private JFrame windowFrame;
    private JPanel canvasPanel;
    private ViewUpdater updater;
    public static final Color CANVAS_COLOR = Color.white;
    public static final int CANVAS_SIDE = 500;

    public View() {
        windowFrame = new JFrame();
        windowFrame.
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        canvasPanel = new JCanvas();
        canvasPanel.setBackground(CANVAS_COLOR);
        canvasPanel.
            setPreferredSize(new Dimension(CANVAS_SIDE,
                                           CANVAS_SIDE));
        windowFrame.getContentPane().add(canvasPanel);
        windowFrame.pack();
        windowFrame.setResizable(false);
    }

    public void setVisible() {
        windowFrame.setVisible(true);
    }

    public void setUpdater(ViewUpdater updater) {
        this.updater = updater;
    }

    public void updateView() {
        System.out.println("BEGIN updateView");
        Graphics2D g2d =(Graphics2D) canvasPanel.getGraphics();
        g2d.setColor(CANVAS_COLOR);
        g2d.fillRect(0, 0, CANVAS_SIDE, CANVAS_SIDE);
        if (updater != null) {
            System.out.println("GOING TO updateCanvas");
            updater.updateCanvas(g2d);
        }
        System.out.println("END updateView");
   }

    private class JCanvas extends JPanel {

        private static final long serialVersionUID =
                                    7953366724224116650L;

        @Override
        protected void paintComponent(Graphics g) {
            System.out.println("BEGIN paintComponent");
            super.paintComponent(g);
            updateView();
            System.out.println("END paintComponent");
        }
    }
}

class Presenter {
    private View view;
    private static final Color FIGURE_COLOR = Color.black;

    public Presenter(View view) {
        this.view = view;
        this.view.setUpdater(new ProjectViewUpdater());
        this.view.setVisible();
    }

    private class ProjectViewUpdater
        implements ViewUpdater {
        @Override
        public void updateCanvas(Graphics2D g2d) {
            g2d.setColor(FIGURE_COLOR);
            g2d.drawOval(100, 100, 300, 300);
            // The circle immediately disappears!
        }
    }
}

public class Main {
    public static void main(String[] args) {
        new Presenter(new View());
    }
}

PS2: I am reading http://www.javaworld.com/javaworld/jw-08-2007/jw-08-swingthreading.html in order to understand the thread model involved in using Swing, but I still have not done anything special for controlling threads in my code.

PS3: I have not found the answer to my problem googling, the most similar issue is maybe the one unanswered in http://www.eclipse.org/forums/index.php/t/139776/.

  • I see nothing wrong here, and indeed, I ran this program and it worked perfectly. Are you sure you see the problem with this simplified version? By the way, that linked problem is utterly dissimilar to yours -- it has to do with embedded Swing inside of SWT, a completely unrelated window toolkit, and a technical big deal with its own set of problems. – Ernest Friedman-Hill May 01 '12 at 01:43
  • @ernest-friedman-hill: Do you really see the circle after deiconificacion? I manage the code from Eclipse and run it "as Java Application", should this make any difference?. In my Windows Vista, the circle is not shown initially (at least, not for an appreciable amount of time), appeared when moving the window from partially outside towards the inside of the desktop, and was lost only after deiconification. In my Ubuntu, however, the circle is never properly shown. – federico.prat May 01 '12 at 02:35
  • 1
    I'm running this on Mac OS X, and the circle never disappears. I think @HovercraftFullOfEels is probably right, that the solution will be to have JCanvas.paintComponent() pass `g` as a parameter to `updateView()`, which should then use the parameter rather than calling getGraphics(). – Ernest Friedman-Hill May 01 '12 at 02:41
  • @ernest-friedman-hill: Thanks for the info about the behaviour under Mac OS X. – federico.prat May 03 '12 at 15:30

1 Answers1

4

Your problem is that you're getting your Graphics object by calling getGraphics() on JPanel, and any Graphics object thus obtained will not be long-lasting, so that anything drawn with it will likewise disappear on the next repaint.

The solution: don't do this. Either

  • draw with the Graphics object given to the paintComponent method by the JVM or
  • call getGraphics() or createGraphics() on a BufferedImage to get its Graphics or Graphics2D object, draw with this, dispose of it, and then draw the BufferedImage in the JComponent or JPanel's paintComponent(...) method again using the Graphics object passed in by the JVM.

For your situation, I think your best off using a BufferedImage, or point 2 above.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • Thank you very much. I have succeeded trying your first proposed approach: Graphic g is now passed from paintComponent() to updateView(), where it substitutes for canvasPanel.getGraphics(). It seems like magic to me. Time to search for info about getGraphics() and BufferedImage :-) Any pointer appreciated. BTW, why do you recommend the second option? – federico.prat May 01 '12 at 03:00
  • 1
    @federico.prat: If some of the content is static, a `BufferedImage` may be rendered once and copied repeatedly via `drawImage()`, for [example](http://stackoverflow.com/a/7298492/230513). +1 – trashgod May 01 '12 at 08:55