当JMenu存在时,JPanel#paintChildren(Graphics)的行为不正确?

我想做的事:
创建一个JPanel的子类,在包含的组件之上绘制一个简单的叠加层。

为什么我不使用JLayeredPane
请参见JComponent#isOptimizedDrawingEnabled()

JMenu存在于JFrame ,添加带有重写的paintChildren(Graphics)方法的JPanel ,在传递的Graphics对象中提供了一个不正确的坐标起点,如此代码示例所示:

 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 } } } 

第一个字符串是在JPanel外部(在JMenu下)绘制的,即使两个坐标都是非负的。
第二个字符串未在右下角绘制。 它被JMenu的高度推高了。

图片

即使:

当AWT调用此方法时,Graphics对象参数已预先配置为在此特定组件上绘制的适当状态:

  • Graphics对象的颜色设置为组件的foreground属性。
  • Graphics对象的字体设置为组件的font属性。
  • 设置Graphics对象的转换,使得坐标(0,0)表示组件的左上角。
  • Graphics对象的剪辑矩形设置为需要重新绘制的组件区域。

程序必须使用此Graphics对象(或从其派生的对象)来呈现输出。 他们可以根据需要随意更改Graphics对象的状态。

我究竟做错了什么?

第一个字符串是在JPanel外部(在JMenu )绘制的,即使两个坐标都是非负的。 第二个字符串未在右下角绘制。 它被JMenu的高度推高了。

在这两种情况下,请注意drawString()要求坐标表示String的基线 。 字体; s上升和下降在此上下文中很有用。 mb.getHeight()fm.getHeight()具有可比性,可能是巧合。

在此处输入图像描述

 @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()); }