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/.
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
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.