6

I have a simple class that draws a line when mouse dragging or a dot when mouse pressing(releasing).

When I minimize the application and then restore it, the content of the window disappears except the last dot (pixel). I understand that the method super.paint(g) repaints the background every time the window changes, but the result seems to be the same whether I use it or not. The difference between the two of them is that when I don't use it there's more than a pixel painted on the window, but not all my painting. How can I fix this?

Here is the class.

package painting;

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JFrame;
import javax.swing.JPanel;

class CustomCanvas extends Canvas{   
    Point oldLocation= new Point(10, 10);
    Point location= new Point(10, 10);
    Dimension dimension = new Dimension(2, 2);     
    CustomCanvas(Dimension dimension){  
        this.dimension = dimension;   
        this.init();
        addListeners();
    }    
    private void init(){                     
        oldLocation= new Point(0, 0);
        location= new Point(0, 0);
    }
    public void paintLine(){
        if ((location.x!=oldLocation.x) || (location.y!=oldLocation.y)) {         
            repaint(location.x,location.y,1,1);                                   
        } 
    }
    private void addListeners(){
        addMouseListener(new MouseAdapter(){
            @Override
            public void mousePressed(MouseEvent me){                   
                oldLocation = location;
                location = new Point(me.getX(), me.getY());
                paintLine();
            }
            @Override
            public void mouseReleased(MouseEvent me){                
                oldLocation = location;
                location = new Point(me.getX(), me.getY());
                paintLine();
            }
        });
        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent me){                
                oldLocation = location;
                location = new Point(me.getX(), me.getY());
                paintLine();
            }
        });
    }
    @Override
    public void paint(Graphics g){  
        super.paint(g);
        g.setColor(Color.red);       
        g.drawLine(location.x, location.y, oldLocation.x, oldLocation.y);                
    }
    @Override
    public Dimension getMinimumSize() {
        return dimension; 
    }
    @Override
    public Dimension getPreferredSize() {
        return dimension;
    }

}
class CustomFrame extends JPanel {
    JPanel displayPanel = new JPanel(new BorderLayout());
    CustomCanvas canvas = new CustomCanvas(new Dimension(200, 200));        
    public CustomFrame(String titlu) {            
        canvas.setBackground(Color.white);
        displayPanel.add(canvas, BorderLayout.CENTER);            
        this.add(displayPanel);
    }   
}
public class CustomCanvasFrame {
    public static void main(String args[]) {
        CustomFrame panel = new CustomFrame("Test Paint");
        JFrame f = new JFrame();
        f.add(panel);
        f.pack();
        SwingConsole.run(f, 700, 700);
    }
}
Andrew Thompson
  • 163,965
  • 36
  • 203
  • 405
Carmen Cojocaru
  • 303
  • 1
  • 5
  • 16
  • Don't mix Swing & AWT components together! If it is not causing problem right now, it will when you add menus, tool-tips, combos or other floating elements. How many drawn elements will be typical for this use-case? Will they ever be removed? If the answer to the 1st is 'thousands' and the 2nd is 'no', a single `BufferedImage` (displayed in a label) might be a better option. A mouse listener can be added to the label. – Andrew Thompson Apr 11 '12 at 19:17
  • Re Swing/AWT. Specifically it is the `Canvas` that I am referring to. For a Swing equivalent, extend either a `JComponent` or `JPanel` then override `paintComponent()` instead of `paint()`. BTW - rendering to an image means not having to extend *anything*, & while the `java.awt.Label` does not support images, the `javax.swing.JLabel` does. ;) – Andrew Thompson Apr 11 '12 at 19:26
  • Yes, I've been looking over the example found [here](http://stackoverflow.com/questions/6132988/painting-in-a-bufferedimage-inside-swing-java) and I'm planning to use a buffered image since I need also to save my images, but for the moment I am trying to understand how paint and update work. For example, in my code(which I will post as soon as I found out how and where), I don't understand why calling the method paint in update isn't the same as writing the exactly same code(from the paint method) in the update method. – Carmen Cojocaru Apr 13 '12 at 15:10
  • *"Yes, I've been looking over the example found here"* Ah yes. That was fun code. Thanks for reminding me. :) *"..for the moment I am trying to understand how paint and update work."* Sorry I'm a bit hazy on that. When I did the tutorial on [custom painting](http://docs.oracle.com/javase/tutorial/uiswing/painting/) long ago, the thing I carried away from it was that there were a lot of methods that were not intended to be directly called, the right place to do custom painting was in the `paint()/paintComponent()` method & the right time to paint was "when requested to do so". – Andrew Thompson Apr 13 '12 at 15:40

4 Answers4

5

You are not storing the state of the points you are drawing. When the panel is repainted, it only has information for the last point it drew.


Response to comment:

You would need to have a collection of Points, for instance ArrayList<Point> location = new ArrayList<Point>();

Then, in your listeners: location.add(new Point(me.getX(), me.getY()));

Finally, in paintLine():

for (Point location : locations) {
  repaint(location.x,location.y,1,1); 
}

The collection locations is usually referred to as a Display List. Most graphics programs use them.


Response to comment:

Yes, I expect so. I just tossed off an idea based on your code to give you a starting point. It is almost certainly a bad idea to do exactly as I have described.

Dave
  • 4,388
  • 2
  • 35
  • 57
  • Doesn't that mean I will draw all the points(instead of one) everytime I press or drag the mouse? – Carmen Cojocaru Apr 11 '12 at 17:44
  • Yes, thanks for the idea. It's been very helpful. I managed to make some improvements to my code and now I can draw points and lines and my image doesn't disappear anymore. However, there's another issue I don't get. If I replace the code from my update method with "paint(g)", my image flashes everytime I try to paint on it. Can you explain me why? (Uhm, I'm not sure how to post my code in here. ) – Carmen Cojocaru Apr 13 '12 at 14:28
  • I think you might already know more about this than I do. GUI work is not usually my thing. – Dave Apr 14 '12 at 16:52
5

Doesn't that mean I will draw all the points(instead of one) everytime I press or drag the mouse?

Yes, but @Dave's approach is perfectly satisfactory for thousands of nodes, as may be seen in GraphPanel. Beyond that, consider the flyweight pattern, as used by JTable renderers and illustrated here.

Addendum: Focusing on your AWTPainting questions, the variation below may illustrate the difference between System- and App-triggered Painting. As the mouse is dragged, repaint() invokes update(), which calls paint(); this is app-triggered. As you resize the window, only paint() is called (no red numbers are drawn); this is system-triggered. Note that there is a flicker when the mouse is released after resizing.

Flickering typically occurs when the entire component's background is cleared and redrawn:

 4. If the component did not override update(), the default implementation of update() clears the component's background (if it's not a lightweight component) and simply calls paint().

AWTPainting

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Panel;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;

public class AWTPainting {

    public static void main(String args[]) {
        CustomPanel panel = new CustomPanel();
        Frame f = new Frame();
        f.addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        f.add(panel);
        f.pack();
        f.setVisible(true);
    }
}

class CustomPanel extends Panel {

    public CustomPanel() {
        this.add(new CustomCanvas(new Dimension(320, 240)));
    }
}

class CustomCanvas extends Canvas {

    private MouseAdapter handler = new MouseHandler();
    private List<Point> locations = new ArrayList<Point>();
    private Point sentinel = new Point();
    private Dimension dimension;

    CustomCanvas(Dimension dimension) {
        this.dimension = dimension;
        this.setBackground(Color.white);
        this.addMouseListener(handler);
        this.addMouseMotionListener(handler);
        this.locations.add(sentinel);
    }

    @Override
    public void paint(Graphics g) {
        g.setColor(Color.blue);
        Point p1 = locations.get(0);
        for (Point p2 : locations.subList(1, locations.size())) {
            g.drawLine(p1.x, p1.y, p2.x, p2.y);
            p1 = p2;
        }
    }

    @Override
    public void update(Graphics g) {
        paint(g);
        g.clearRect(0, getHeight() - 24, 50, 20); // to background
        g.setColor(Color.red);
        g.drawString(String.valueOf(locations.size()), 8, getHeight() - 8);
    }

    private class MouseHandler extends MouseAdapter {

        @Override
        public void mousePressed(MouseEvent e) {
            if (locations.get(0) == sentinel) { // reference identity
                locations.set(0, new Point(e.getX(), e.getY()));
            }
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            locations.add(new Point(e.getX(), e.getY()));
            repaint();
        }
    }

    @Override
    public Dimension getPreferredSize() {
        return dimension;
    }
}
Community
  • 1
  • 1
trashgod
  • 196,350
  • 25
  • 213
  • 918
  • Ok, I have to know: what the hell is this `public class ![AWTPainting][7] {...`? – Dave Apr 14 '12 at 16:52
  • 1
    Um, careless editing? I _wondered_ where that first image [went](http://stackoverflow.com/posts/10112189/revisions). :-) I have little AWT experience, so I'd welcome you critical review. – trashgod Apr 14 '12 at 17:25
  • I'm in the same boat with regards to AWT/Swing; I can get by, but I'm not really an experienced user. I asked about your typo because, years ago, I had experience in Java 1.2, but then ended up living face-down in a moon crater for quite some time. When I got back, Java had generics and other stuff that took me by surprise. I was concerned that maybe this had happened again and Oracle had done something strange to Java while I was looking the other way. – Dave Apr 14 '12 at 18:14
  • Ah, Swing enables double buffering by default, which avoids the problem. You can clear and draw anything within your frame budget, for [example](http://stackoverflow.com/a/3256941/230513). – trashgod Apr 14 '12 at 18:22
2

@Andrew, @Dave, @trashgod Hi, I did some research on this and, finally, this is what I've got. Please correct me if I'm wrong. You cannot override paint() so you call repaint() everytime you need to do app-triggered painting. Repaint() calls update() which its default behavior is to call paint(). update() is used for incremental painting; that explains the flickering screen when paint() was doing all the work, which practically means it was painting the whole image at every step. However, my question is if I add "locationsAdded = 0" in the update method that means everytime I drag the mouse I paint the whole image(like in paint), so why doesn't it blink like before? I've also read something about painting in swing and I didn't understand why update() is never invoked for swing. Can you explain me why?

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;

class CustomCanvas extends Canvas{ 
    ArrayList<Point> locations;        
    int locationsAdded;    
    Point oldLocation;
    Point location;
    Dimension dimension;
    CustomCanvas(Dimension dimension){  
        locations = new ArrayList<>();        
        this.dimension = dimension;   
        this.init();
        addListeners();
    }    
    private void init(){                          
        oldLocation= new Point(0, 0);
        location= new Point(0, 0);
    }
    public void paintLine(Graphics g, int x){
        Point p1 = (Point)locations.get(x);
        Point p2 = (Point)locations.get(x+1);
        g.drawLine(p1.x, p1.y, p2.x, p2.y);
        locationsAdded++;
    }
    @Override
    public void paint(Graphics g){          
        locationsAdded = 0;        
        g.setColor(Color.red);                  
        for(int i = locationsAdded; i < locations.size()-1; i++){
            paintLine(g, i);
        }             
    }
    public void update(Graphics g) {        
    //locationsAdded = 0;
        for (int i = locationsAdded; i < locations.size()-1; i++) {            
            paintLine(g, i);
        }
    }
    private void addListeners(){
        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent me){                                   
                oldLocation = location;
                location = new Point(me.getX(), me.getY());
                locations.add(location);
                repaint();
            }
        }); 
    }

    @Override
    public Dimension getMinimumSize() {
        return dimension; 
    }
    @Override
    public Dimension getPreferredSize() {
        return dimension;
    }
}
class CustomFrame extends Panel {
    Panel displayPanel = new Panel(new BorderLayout());
    CustomCanvas canvas = new CustomCanvas(new Dimension(700, 700));        
    public CustomFrame(String titlu) {            
        canvas.setBackground(Color.white);
        displayPanel.add(canvas, BorderLayout.CENTER);            
        this.add(displayPanel);
    }  
}
public class AWTPainting {
    public static void main(String args[]) {
        CustomFrame panel = new CustomFrame("Test Paint");
        Frame f = new Frame();
        f.add(panel);
        f.pack();        
        f.setSize(700,700);                    
        f.show();                
    }
}
Carmen Cojocaru
  • 303
  • 1
  • 5
  • 16
  • +1 for an [sscce](http://sscce.org/); I've elaborated [here](http://stackoverflow.com/a/10112189/230513). – trashgod Apr 14 '12 at 01:47
0

set your layout to Null layout

Andrey Stukalin
  • 3,554
  • 1
  • 25
  • 42