4

What I want to do:
Create a JPanel's subclass to draw a simple overlay on top of contained components.

Why don't I use JLayeredPane?
See JComponent#isOptimizedDrawingEnabled().

When a JMenu is present in a JFrame, adding a JPanel with an overridden paintChildren(Graphics) method, an incorrect coordinate starting point is provided in the passed Graphics object, as observed with this code sample:

import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

public final class Sscce {
    public static void main(String[] args) {
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    // a normal frame
                    JFrame f = new JFrame();

                    // set up a simple menu
                    JMenuBar mb = new JMenuBar();
                    JMenu m = new JMenu("Test");
                    JMenuItem mi = new JMenu("Whatever");
                    m.add(mi);
                    mb.add(m);
                    f.setJMenuBar(mb);

                    // a panel with a simple text overlay over components.
                    // works much faster than JLayeredPane, which doesn't have
                    // isOptimizedDrawingEnabled()
                    JPanel p = new JPanel() {
                        @Override
                        public void paint(Graphics g) {
                            // I'm not so stupid to draw stuff here
                            super.paint(g);
                            // JavaDoc: delegates to paintComponent, paintBorder, paintChildren
                            // in that order
                        }

                        @Override
                        protected void paintComponent(Graphics g) {
                            super.paintComponent(g);

                            // it is common knowledge that children are painted after parent
                            Graphics tmp = g.create();
                            try {
                                tmp.setColor(Color.MAGENTA);
                                tmp.fillRect(0, 0, getWidth(), getHeight());
                            } finally {
                                tmp.dispose();
                            }
                        }

                        @Override
                        protected void paintChildren(Graphics g) {
                            super.paintChildren(g);

                            // draw some text
                            FontMetrics fm = g.getFontMetrics();
                            // will be drawn outside panel; under menu
                            g.drawString("TEST TOP/LEFT", 0 + getX(), 0 + getY());
                            final String s = "TEST BOTTOM/RIGHT";
                            // will be drawn noticeably above the bottom
                            g.drawString(s,
                                    getWidth() - fm.charsWidth(s.toCharArray(), 0, s.length()),
                                    getHeight() - fm.getHeight());
                        }
                    };
                    // add something to the panel
                    p.add(new JTextArea(10, 15));
                    f.add(p);

                    f.pack();
                    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    f.setVisible(true);
                }
            });
        } catch (Throwable t) {
            // this is a SSCCE
        }
    }
}

The first string is drawn outside of JPanel (under the JMenu), even though both coordinates are non-negative.
The second string is NOT drawn at the bottom right corner. It is pushed up by the height of the JMenu.

Image

Even though:

When AWT invokes this method, the Graphics object parameter is pre-configured with the appropriate state for drawing on this particular component:

  • The Graphics object's color is set to the component's foreground property.
  • The Graphics object's font is set to the component's font property.
  • The Graphics object's translation is set such that the coordinate (0,0) represents the upper left corner of the component.
  • The Graphics object's clip rectangle is set to the area of the component that is in need of repainting.

Programs must use this Graphics object (or one derived from it) to render output. They are free to change the state of the Graphics object as necessary.

What am I doing wrong?

Tadas S
  • 1,955
  • 19
  • 33
  • a) I don't see the text in the upper left corner at all b) the text in the lower corner is at the same position w/out menubar c) the text above the textarea is "painted over" when typing into the area - on the whole: simply don't do custom painting in paintChildren ;-) Instead use a layeredPane, an overlay layout or JLayer (jdk7)/JXLayer(jdk6) – kleopatra Aug 13 '12 at 08:19
  • @kleopatra is right: The top-left string is drawn with the baseline at the origin; only the descenders would have been visible, but there are none in that particular string. I've updated my answer. – trashgod Aug 13 '12 at 10:58

1 Answers1

7

The first string is drawn outside of JPanel (under the JMenu), even though both coordinates are non-negative. The second string is NOT drawn at the bottom right corner. It is pushed up by the height of the JMenu.

In both cases, note that drawString() expects the coordinates to represent the baseline of the String. The font;s ascent and descent are useful in this context. It may be a coincidence that mb.getHeight() and fm.getHeight() are of comparable magnitude.

enter image description here

@Override
protected void paintChildren(Graphics g) {
    super.paintChildren(g);

    // draw some text
    FontMetrics fm = g.getFontMetrics();
    // will be drawn outside panel; under menu
    g.drawString("TEST TOP/LEFT", 0, fm.getAscent());
    final String s = "TEST BOTTOM/RIGHT";
    // will be drawn noticeably above the bottom
    g.drawString(s, getWidth() - fm.stringWidth(s),
        getHeight() - fm.getDescent());
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • See also [*Initial Threads*](http://download.oracle.com/javase/tutorial/uiswing/concurrency/initial.html). – trashgod Aug 12 '12 at 17:11
  • I am NOT painting in `contentPane`, I AM using a JPanel. I am NOT overriding `paint(Graphics)`. By the way, the same documentation states that `paintChildren(Graphics)` is done after `paintComponent(Graphics)` which makes overriding the former useless – that is the reason why I override `paintChildren`. – Tadas S Aug 13 '12 at 07:47
  • My mistake about the content pane; thank you for updating your sscce. I believe the menu bar is a distraction; more above. – trashgod Aug 13 '12 at 10:51
  • As usual, things are never overly complicated. The aforementioned coincidence led me to believe such... 'nonsense'. Thank you for clearing it up. – Tadas S Aug 13 '12 at 12:25