javaswinggraphics2drepaintjava-2d

Is it truly never okay to call Component.getGraphics?


MadProgrammer made a comment here stating that you should NEVER EVER use Component.getGraphics, which I generally agree with: In virtually all situations, it is wrong.

But take the case of this class I created years ago. I'm posting what I think is the relevant code. You can find the full code here

public class MouseDragOutliner extends MouseAdapter implements MouseMotionListener {
private class MyRunnable implements Runnable {
    public void run() {
        Graphics g = mComponent.getGraphics();
        if (g == null) {
            return;
        }
        Graphics2D g2 = (Graphics2D) g;
        Stroke s = g2.getStroke();
        g2.setStroke(DASH_STROKE);
        int x = Math.min(mStart.x, mEnd.x);
        int y = Math.min(mStart.y, mEnd.y);
        int w = Math.abs(mEnd.x - mStart.x);
        int h = Math.abs(mEnd.y - mStart.y);
        if (w == 0 || h == 0) return;
        g2.setXORMode(Color.WHITE);
        if (mCustomShape != null) {
            Rectangle r = mCustomShape.getBounds();
            AffineTransform scale = AffineTransform.getScaleInstance(w / (double)r.width, h / (double)r.height);
            AffineTransform trans = AffineTransform.getTranslateInstance(x - r.x, y-r.y);
            g2.transform(trans);
            g2.transform(scale);
            g2.draw(mCustomShape);
        } else {
            if (mShape == RECTANGLE) g2.drawRect(x, y, w, h);
            else if (mShape == OVAL) g2.drawOval(x, y, w, h);
            else if (mShape == LINE) g2.drawLine(mStart.x, mStart.y, mEnd.x, mEnd.y);
        }
        g2.setStroke(s);    
    }
}

public void doMouseDragged(MouseEvent me) {
    mEnd = me.getPoint();
    if (mStart != null) {
        mComponent = me.getComponent();
        mComponent.repaint();
        SwingUtilities.invokeLater(mRunner);
    }
}
}

doMouseDragged is called from both mouseDragged and mouseMoved (when configured to). In case it's not obvious, what it does is call repaint on the component (so the component is ready to paint on), then run the mRunner after. I've used this in a number of situations quite successfully, with the benefits being that the component that the MouseDragOutliner doesn't need any customization (in paintComponent, specifically) to accomodate.

If it is truly never okay to call Component.getGraphics, can someone suggest a better solution for this?

Thanks!


http://sourceforge.net/p/tus/code/HEAD/tree/tjacobs/ui/drag/MouseDragOutliner.java#l66


Solution

  • I never like to say never. I think you just need to be aware of the drawbacks when using an approach that is not recommended.

    The main point about getGraphics() is that the painting is only temporary. Whenever Swing determines the component needs to be repainted you will lose the custom painting. For example, try creating your outline and then minimize or maximize the frame and the outline will be lost. If you use Alt+Tab to switch application in Windows you will also lose the painting.

    I agree in this case it is not a high probability the user will do something like that, but it may cause problems and result in a bug report. So you need to decide if you care about this problem or not. Who knows, maybe this is a requirement so it is not a big deal?

    As suggested by @kiheru you can probably use the JLayer class. I don't think JLayer was part of the API when you created your code. I am not that familiar with it but the Swing tutorial has a section on Responding to Events when using the JLayer class, which seems to indicate you can do what you want.

    When using a JLayer you can't dynamically add the functionality to any component (the way your code does). You need to add the functionality at design time, but I don't think that should be a problem.