AWTUtilities.setWindowOpaque引起的字体质量问题的一种解决方案

2011-01-10写于iteye的一篇文章,今迁至此,以后这里便是三哥的大本营!
AWTUtilities.setWindowOpaque的功能在Java7中可以通过Window.setBackground来实现,但问题依旧!以下为原文:

       上个周末,将三号管家更新到了V1.0.4,其实主要是修改了SwingC,管家本身只改了版本号。更新说明里我写的是“优化菜单字体显示”。请大家仔细观察下面的截图:
效果对照

       这张图片是在三号管家V1.0.3中抓取的,左边是菜单有一部分超出了主界面的范围(那部分是全透明的,所以不能说人家不存在),右边是菜单全部显示在主界面区域内。很明显,左边菜单中字体的质量要差很多,这还是我在绘制的时候修改了某些参数优化过的,未经参数优化的比现在看到的效果还要差很多。为什么会这样呢?TWaver的那位刀客在他的“Swing是一把刀”系列中曾经提到过,我在这里再说一下。可以认为这是JDK的一个bug,具体问题是:java.awt.Window及其子类如果使用com.sun.awt.AWTUtilities.setWindowOpaque(window, false)设为全透明,那么这个窗体及其下的所有子组件所显示的字体都会变得很粗糙。既然是窗体的问题,为什么会影响到菜单呢?原因就在于JPopupMenu显示的区域如果未超出其父窗体的范围,那么它是一个轻量级的JComponent,但是一旦超出了,那这个时候显示出来的实际上是一个重量级的Window。为了实现弹出菜单边框半透明的模糊效果,我重写了setVisible,如果显示的是重量级的Window,直接使用AWTUtilities.setWindowOpaque(window, false)将其置为全透明,所以字体质量就严重下降了。

       这是个很棘手的问题,我使劲的谷歌、百度一番过后,仍一无所获。TWaver的刀客采用的解决方案是在JFrame之上放一个JDialog,然后采取一些手段使它们保持同步,所以看起来仍然是一个窗体,但我这个仅仅只是弹出菜单,如果使用这个方案感觉成本太高。经过几番研究和尝试之后,发现在绘制字体的时候改改某些参数的默认值可以稍稍优化一下,不至于和正常字体的效果差太多,但终究还是有差,这也就是上面图片中大家看到的效果。心头之痛哪,但是没有更好的解决方案只能如此了。

       再然后就到了2011年的某一天,灵感突然告诉我,有个办法或许可行,于是试之,果然没让老三失望。我先把代码帖上来吧,后面再慢慢说闲话。

    public void paint(Graphics g)
    {
        if(!UIUtil.isTranslucencySupported() || !buffered)
        {
            super.paint(g); 
        }
        else
        {
            Insets insets = this.getInsets();
            int x = insets.left;
            int y = insets.top;
            int width = this.getWidth();
            int height = this.getHeight();
            int contentWidth = width - insets.left - insets.right;
            int contentHeight = height - insets.top - insets.bottom;
            BufferedImage image = UIUtil.getGraphicsConfiguration(this).createCompatibleImage(width, height, Transparency.TRANSLUCENT);
            BufferedImage contentImage = UIUtil.getGraphicsConfiguration(this).createCompatibleImage(contentWidth,
                            contentHeight, Transparency.OPAQUE);
            Graphics2D g2d = image.createGraphics();
            Graphics2D contentG2d = contentImage.createGraphics();
            contentG2d.translate(-x, -y);
            super.paint(g2d);
            super.paint(contentG2d);
            g2d.dispose();
            contentG2d.dispose();
            g.drawImage(image, 0, 0, this);
            g.drawImage(contentImage, x, y, this);
        }
    }

       代码很简单,创建了两个BufferedImage。注意createCompatibleImage中最后一个参数,第一张图片使用Transparency.TRANSLUCENT,这种类型的图片可以设置透明度,用它来绘制边框的半透明模糊效果,为了避免复杂的计算,直接将菜单的所有内容都绘制上去,但这种类型的图片上面显示的字体效果依然很差,所以在中间内容部分用另外一张图片来填充,前提是这张图片不能覆盖掉边框的效果,于是它的长和宽都比第一张图片小,起始位置也跳过了边框,这就是第二个BufferedImage,类型为Transparency.OPAQUE(即完全不透明,默认背景为黑色)。在这种类型的图片上绘制字体,效果与JComponent组件正常的字体完全一致,最后再将这两张图片绘制到组件上。问题就这么解决了,这也就是三号管家V1.0.4中的菜单效果。上面这段代码呢,只做讲解使用,大家如果要用的话可能还需要实际情况实际绘制,但是方法就是这么个方法,万变不离其踪。

你可能感兴趣的:(swing,JavaSE,Java2D)