1

Let's say I have a JPanel called content with its paintComponent(g) overridden. This panel usually paints very quickly, but sometimes (Clarification: the user generates a graphics object that can have thousands of points, which slows down the rendering. It needs to be this way and it's not part of my question) it may take a whole second to repaint due to all of the graphics stuff going on in it.

I want to paint a dot near the location of the mouse, but I want this to update quickly and not have to repaint content every time the mouse is moved.

What I tried to do was paint it on a glass pane, like this:

class LabelGlassPane extends JComponent {
    public LabelGlassPane(JApplet frame) {
        this.frame = frame;
    }
    public JApplet frame;
    public void paintComponent(Graphics g) {
        if(ell != null){
            g2.setColor(Vars.overDot);
            g2.fill(ell); //this is an ellipse field that was created in the mouesmoved() listener
        }
    }
}

My issue is that even though I now only update this glass pane when the mouse is moved, it's repainting all of the other components in the applet when I call repaint() on it.

How can I paint to this glass pane (or have another way of painting) without it painting the components below?

Thanks.

EDIT: Here is a simple example. jp should not update when the mouse is moved, but it is.

public class Glasspanetest extends JApplet implements Runnable{

public void init() {
    setLayout(new BorderLayout());
    GraphicsPanel jp = new GraphicsPanel();
    add(jp, BorderLayout.CENTER);
    glass = new LabelGlassPane(this);
    this.setGlassPane(glass);
    glass.setVisible(true);
}


class GraphicsPanel extends JPanel{
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        g.setColor(Color.red);
        g.fillOval(50, 50, 50, 50);
        System.err.println("Painting bottom panel");
    }
}

public LabelGlassPane glass = null;
public Ellipse2D.Double ell = null;

class LabelGlassPane extends JComponent {
    public LabelGlassPane(JApplet frame) {
        this.frame = frame;
        this.addMouseMotionListener(new MoveInfoListener());
    }
    public JApplet frame;
    public void paintComponent(Graphics g) {
        //g.setColor(Color.red);
        //Container root = frame.getRootPane();
        //          g.setColor(new Color(255,100,100,100));
        //          g.fillRect(100, 100, 500, 500);
        if(ell != null){
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g.setColor(Color.black);
            g2.fill(ell);
            System.out.println("Painted glass pane");
        }

        //rPaint(root,g);
    }
}

public class MoveInfoListener implements MouseMotionListener{
    public void mouseMoved(MouseEvent e) {
        ell = new Ellipse2D.Double(e.getX()-3, e.getY()-3, 6, 6);
        glass.repaint();
    }

    public void mouseDragged(MouseEvent arg0) {}
}

public void run() {}

}

CorlinP
  • 169
  • 1
  • 2
  • 15
  • 1
    _Sometimes_? [Profile](http://stackoverflow.com/q/2064427/230513) and handle the problem code in the background. – trashgod Aug 14 '14 at 00:34
  • 2
    Make sure you're calling `super.paintComponent`. Consider providing a [runnable example](https://stackoverflow.com/help/mcve) which demonstrates your problem. This will result in less confusion and better responses – MadProgrammer Aug 14 '14 at 00:34
  • `Sometimes it will take a whole second to repaint` - If you make a simple app which prints the mouse coordinates on mouse-move, you'll find it will print coordinates several times per second. If you only need to repaint once per second, don't do that. `I want to paint a dot near the location of the mouse, but I want this to update quickly` - Separate this code into it's own thread, so it doesn't have to wait for the other stuff to paint before it gets drawn. – Ozzy Aug 14 '14 at 00:42
  • 1
    @Ozzy Don't forget that Swing is not thread safe and that all painting is done via the Event Dispatching Thread as well... – MadProgrammer Aug 14 '14 at 00:52
  • @MadProgrammer thanks for the reminder, I don't use swing anymore :). You're that guy from java-forums :D – Ozzy Aug 14 '14 at 00:56
  • 2
    @Ozzy I think you're thinking of camickr, I'm not famous, I'm infamous ;) – MadProgrammer Aug 14 '14 at 00:58
  • Thanks for the replies! I added a clarification edit about what 'sometimes' means. Essentially I'm only asking "How can I paint only to a glass pane without repainting other components?" – CorlinP Aug 14 '14 at 03:02
  • Hey guys, I just added a simple case example that shows the problem I'm talking about – CorlinP Aug 14 '14 at 03:40

1 Answers1

1

The problem is that Swing has no way to know that it should only repaint a small part of your LabelGlassPane and possibly a small part or no part of your GraphicsPanel.

This is due to the fact that you perform custom painting in your components and that, although you only paint a small area of your panel's, you could also very well paint over the entire panel.

Now, as to why your GraphicsPanel gets repainted every time your LabelGlassPane is repainted, this is due to the fact that LabelGlassPane is not opaque, meaning that, in order to properly display not painted areas, it needs to paint whatever is "under" the glasspane.

The combination of those 2 aspects forces Swing to paint entirely both panels everytime you call repaint.

But there are ways to avoid this bad combination. For this to work, you need to make your painting methods more clever by:

  1. Only ask to repaint the rectangle that has been made dirty (ie, the union of the old ellipse and the new one)
  2. In your painting components, only do the repaint if the clip of the Graphics intersects the area where you plan to paint. If there are no intersection, then you can just skip the paint operation.

Small demo code showing that:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Ellipse2D.Double;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Glasspanetest extends JFrame {

    public JFrame init() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout());
        GraphicsPanel jp = new GraphicsPanel();
        add(jp, BorderLayout.CENTER);
        LabelGlassPane glass = new LabelGlassPane();
        this.setGlassPane(glass);
        glass.setVisible(true);
        setSize(400, 400);
        return this;
    }

    class GraphicsPanel extends JPanel {

        private Shape oval = new Ellipse2D.Double(50, 50, 50, 50);

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (g.getClip().intersects(oval.getBounds2D())) {
                g.setColor(Color.red);
                ((Graphics2D) g).fill(oval);
                System.out.println("Painting bottom panel");
            }
        }
    }

    public Ellipse2D.Double ell = null;

    class LabelGlassPane extends JComponent {
        public LabelGlassPane() {
            this.addMouseMotionListener(new MoveInfoListener());
        }

        public class MoveInfoListener implements MouseMotionListener {
            @Override
            public void mouseMoved(MouseEvent e) {
                Ellipse2D.Double oldEll = ell;
                ell = new Ellipse2D.Double(e.getX() - 3, e.getY() - 3, 6, 6);
                LabelGlassPane.this.repaint(oldEll, ell);
            }

            @Override
            public void mouseDragged(MouseEvent arg0) {
            }
        }

        public void repaint(Double oldEll, Double ell) {
            Rectangle rect = ell.getBounds();
            if (oldEll != null) {
                rect = rect.union(oldEll.getBounds());
            }
            // repaint(rect);
            repaint();
        }

        @Override
        public void paintComponent(Graphics g) {
            // g.setColor(Color.red);
            // Container root = frame.getRootPane();
            // g.setColor(new Color(255,100,100,100));
            // g.fillRect(100, 100, 500, 500);
            if (ell != null) {
                super.paintComponent(g);
                Graphics2D g2 = (Graphics2D) g;
                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g.setColor(Color.black);
                g2.fill(ell);
                System.out.println("Painted glass pane");
            }

            // rPaint(root,g);
        }

    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new Glasspanetest().init().setVisible(true);
            }
        });
    }

}

Note: this wouldn't be my favourite approach to your application, but it will work. Unfortunately, I don't have the full picture of your application to provide an alternate approach to this.

Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117