4

我有一个使用自定义 UI 委托(CustomButtonUI 扩展 BasicButtonUI)绘制的 JButton。CustomButtonUI 的paint() 方法绘制带有“抗锯齿”圆角的按钮,以使外观尽可能“平滑”。

每次我将鼠标拖到按钮上时,按钮的“抗锯齿”边缘都会以某种方式消失。这使按钮边缘看起来“像素化”。但是,一旦我添加了一行代码来重新绘制按钮的父级,即使我将鼠标拖到按钮上,抗锯齿也会启动。

现在,我的问题与这是否是个好主意有关?毕竟,我会从子组件重新绘制父组件。我想知道这是否会导致重绘循环?如果父母试图重绘它的孩子,而孩子试图重绘它的父母——那么我假设我们正在谈论一个循环。

我已附上我的代码作为参考。任何意见都非常欢迎!

public class JCustomButtonUI extends BasicButtonUI {

    @Override
    public void installUI(JComponent c) {
        super.installUI(c);

        AbstractButton b = (AbstractButton) c;
        b.setBorderPainted(false);
    }

    @Override
    public void paint(Graphics g, JComponent c) {

        //Cast the Graphics instance to a Graphics2D instance.
        Graphics2D g2d = (Graphics2D) g;
        JButton b = (JButton) c;

        //Repaint parent component to make sure that we get "antialiased"
        //edges.
        b.getParent().repaint();

        //Get the component's height and width.
        int w = (int) g.getClipBounds().getWidth();
        int h = ((int) g.getClipBounds().getHeight());

        //Make sure the button is drawn with "antialiased" edges.
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setColor(Color.GRAY);
        g2d.fillRoundRect(0, 0, w, h, w, h);       
    }
}

更新 1

只是为了说明别名和抗锯齿边框,请看下面的两张图片。当我(来自 ButtonUI 的 paint() 方法)手动调用父 JPanel 的 repaint 方法时,所有边框都始终完美抗锯齿。但是,当我不手动调用父 JPanel 的 repaint 方法时,一旦我将鼠标悬停在按钮上,边框将不再抗锯齿。

别名 抗锯齿

更新 2

我在 Snipt 上共享了由一个 JPanel、一个 JSlider 和几个 JButton 组成的整个“组件”。请从http://snipt.org/wnllg获取。

更新 3

看来我已经设法让它工作了。我没有在其paintComponent() 方法中绘制JPanel 的背景,而是创建了一个安装在JPanel 上的JCustomPanelUI。我不认为这就是解决方案本身,但我没有使用 Graphics 实例的宽度和高度,而是尝试使用 JPanel 本身的宽度和高度。当我使用 Graphics 实例的宽度和高度时,我不太确定为什么会出错。我认为 Graphics 实例的宽度和高度已经针对 JPanel 组件的尺寸进行了“准备”。您可以在这里查看最终组件:http: //snipt.org/wnlli

4

5 回答 5

4

我已将示例简化为仅抗锯齿,并且无法重现该问题。它似乎不依赖于平台。我不确定你为什么使用getClipBounds().

附录:

背景(JPanel渐变)需要透过...</p>

我已更新示例以在透明按钮后面使用渐变背景;我将抗锯齿(左)和别名(右)示例并排放置。我没有看到意外的行为。

ButtonUITest.png

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.plaf.basic.BasicButtonUI;

/** @see http://stackoverflow.com/questions/5169647 */
public class ButtonUITest extends JPanel {

    public ButtonUITest() {
        this.setLayout(new GridLayout(1, 0));
        this.setPreferredSize(new Dimension(640, 480));
        this.add(new CustomButton(true));
        this.add(new CustomButton(false));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        int w = this.getWidth();
        int h = this.getHeight();
        Graphics2D g2d = (Graphics2D) g;
        g2d.setPaint(new GradientPaint(0, 0, Color.blue, w, h, Color.red));
        g2d.fillRect(0, 0, w, h);
    }

    private void display() {
        JFrame f = new JFrame("ButtonUITest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static class CustomButton extends JButton {

        public CustomButton(boolean antialiased) {
            this.setOpaque(false);
            this.setUI(new CustomButtonUI(antialiased));
        }
    }

    private static class CustomButtonUI extends BasicButtonUI {

        private boolean antialiased;

        public CustomButtonUI(boolean antialiased) {
            this.antialiased = antialiased;
        }

        @Override
        public void paint(Graphics g, JComponent c) {
            int w = c.getWidth();
            int h = c.getHeight();
            Graphics2D g2d = (Graphics2D) g;
            if (antialiased) {
                g2d.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            }
            g2d.setColor(Color.LIGHT_GRAY);
            g2d.fillOval(0, 0, w, 2 * h);
        }
    }

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

            @Override
            public void run() {
                new ButtonUITest().display();
            }
        });
    }
}
于 2011-03-02T17:10:05.257 回答
2

为了使抗锯齿始终如一地工作,您的组件需要返回false. isOpaque否则,RepaintManager 可以自由地跳过绘制组件后面的区域。

我怀疑如果您使用屏幕放大镜查看“无锯齿”边缘,您会发现它们确实是抗锯齿的。但它是在黑色、未上漆的背景下完成的,而不是父母的背景。

于 2011-03-02T17:33:18.667 回答
1

知道他们在说什么的 Swing 专家很快就会来到这里。与此同时,让我评论一下:

现在,我的问题与这是否是个好主意有关?毕竟,我会从子组件重新绘制父组件。我想知道这是否会导致重绘循环?如果父母试图重绘它的孩子,而孩子试图重绘它的父母——那么我假设我们正在谈论一个循环。

一旦您尝试了它并发现它在您的机器上不是问题,那么很可能在您尝试的所有 JVM 上都是如此。也就是说,证明在布丁中,或者随机装模作样确实通常会导致 Swing 中的积极结果。递归循环有一种方法可以使程序在 Java 中很快停止,所以答案是……如果这是完全错误的,你已经知道了。另外,您可以将 sysouts 放在那里以查看是否发生这种情况(显然不是)。

也就是说,可能有更好的方法来处理您的问题,但如果您的答案有效,请暂时坚持下去。

于 2011-03-02T15:32:42.473 回答
0

AFAIK,repaint() 只是告诉系统在下一个绘制周期中重新绘制该组件,即如果您在 paint() 方法中调用 repaint(),则可能在下一个周期中完成重新绘制。

通常,Swing 在自己的线程中运行,因此重复重绘不应停止应用程序逻辑。但是,如果系统总是重新绘制 ui,即使没有更改,它也可能正在使用处理能力。

您可以尝试编写一条日志消息,以查看绘制按钮的时间和频率。如果这种情况持续发生,即使没有发生任何会导致 ui 更新的情况,您可能必须找到解决方案。如果没有,你应该没问题。

编辑:有一个名为RepaintManager的类,您可能会觉得它很有趣。

于 2011-03-02T15:53:15.623 回答
0

在那里重新粉刷绝对是个坏主意。尝试添加b.setOpaque(false). installUI()由于抗锯齿,您的按钮不再绘制其整个边界。您需要让背景显示出来以填补空白。

于 2011-03-02T16:07:00.027 回答