为什么在 JComponent 上定义 .paintComponent()?

Why .paintComponent() is defined on JComponent?

很抱歉,如果我的问题不适合 Whosebug 要求,因为它是理论上的,但我不知道还有什么地方可以问。

在过去的几周里,我一直在努力更好地理解 Swing API 的工作原理及其组件,以便创建我自己的自定义组件。我已经阅读了大量的教程,在这里进行了搜索,我对 Java 的 swing 源代码很感兴趣,坦率地说......我的脑子一团糟。

据我了解,秋千组件由三部分组成:

在本教程中 https://docs.oracle.com/javase/tutorial/uiswing/painting/step2.html 有一段说:

The paintComponent method is where all of your custom painting takes place. >This method is defined by javax.swing.JComponent and then overridden by your >subclasses to provide their custom behavior

为什么 JComponent 上有一个 paintComponent 方法?它不应该是 UI 委托的独占方法吗?

Swing 基于 AWT。所以最初的限制是基于 AWT 如何绘制它的组件。因为 Swing 是轻量级的,所以当绘制组件根组件时,需要通知子 Swing 组件它们也需要更新。这是通过调用受更新影响的所有子组件的 paint 方法(依次调用 paintComponent 方法)来完成的。

关于 Swing API 绘画链的许多决定都是基于定制的概念。它降低了所涉及的复杂性(通过阻止覆盖 paint 并将功能集中在 paintComponent 方法上)。

外观 API 基于 "delegate" 模型。这意味着用于执行所述操作的功能是 "delegated" 到其他一些对象。这意味着 UI 委托 UI 实际上不知道组件 "needs" 何时被绘制,而是被组件告知它需要被绘制。这使得它更加灵活,并且在许多情况下更易于定制。

根据您的 previous question,自定义 ButtonUI 可能是更好的选择之一,这样您就可以更好地控制按钮的绘制方式。

尝试让按钮遵循当前的外观和感觉配色方案将非常困难,但您可以尝试查看随 JDK 安装的 src.jar,它包括许多外观和感觉的实现(如果你在 Windows 你应该得到 Windows 外观和感觉,如果你在 Mac,那么你也不会得到 Mac 或 Windows )

我首先查看了 BasicButtonUIButtonUI 以更好地了解它们的属性。我将一些更有趣的方法引入了自定义 ShapeButtonUI...

public class ShapeButtonUI extends BasicButtonUI {

    private Shape shape;

    public ShapeButtonUI(Shape shape) {
        this.shape = shape;
    }

    protected Color getSelectColor() {
        return UIManager.getColor(getPropertyPrefix() + "select");
    }

    protected Color getDisabledTextColor() {
        return UIManager.getColor(getPropertyPrefix()
                        + "disabledText");
    }

    protected Color getFocusColor() {
        return UIManager.getColor(getPropertyPrefix() + "focus");
    }

    @Override
    protected void installDefaults(AbstractButton b) {
        super.installDefaults(b);
    }

    @Override
    protected void uninstallDefaults(AbstractButton b) {
        super.uninstallDefaults(b);
    }

    @Override
    public void paint(Graphics g, JComponent c) {
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setClip(shape);
        Rectangle bounds = shape.getBounds();
        LinearGradientPaint lgp = new LinearGradientPaint(
                        new Point(bounds.x, bounds.y),
                        new Point(bounds.x, bounds.y + bounds.height),
                        new float[]{0, 1},
                        new Color[]{c.getBackground().brighter(), c.getBackground().darker()});
        g2d.setPaint(lgp);
        g2d.fill(shape);
        g2d.dispose();
        g2d = (Graphics2D) g.create();
        g2d.setColor(c.getForeground());
        g2d.draw(shape);
        g2d.dispose();
        super.paint(g, c);
    }

    @Override
    protected void paintButtonPressed(Graphics g, AbstractButton b) {
        super.paintButtonPressed(g, b);
    }

    @Override
    protected void paintFocus(Graphics g, AbstractButton b,
                    Rectangle viewRect, Rectangle textRect, Rectangle iconRect) {
        super.paintFocus(g, b, viewRect, textRect, iconRect);
    }

    @Override
    protected void paintText(Graphics g, AbstractButton b, Rectangle textRect, String text) {
        super.paintText(g, b, textRect, text);

//          ButtonModel model = b.getModel();
//          FontMetrics fm = SwingUtilities2.getFontMetrics(c, g);
//          int mnemIndex = b.getDisplayedMnemonicIndex();
//
//          /* Draw the Text */
//          if (model.isEnabled()) {
//              /**
//               * * paint the text normally
//               */
//              g.setColor(b.getForeground());
//          } else {
//              /**
//               * * paint the text disabled **
//               */
//              g.setColor(getDisabledTextColor());
//          }
//          SwingUtilities2.drawStringUnderlineCharAt(c, g, text, mnemIndex,
//                          textRect.x, textRect.y + fm.getAscent());

    }

    @Override
    public Dimension getMinimumSize(JComponent c) {
        Rectangle bounds = shape.getBounds();
        return new Dimension(bounds.x + bounds.width + 1, bounds.y + bounds.height + 1);
    }

    @Override
    public Dimension getPreferredSize(JComponent c) {
        Rectangle bounds = shape.getBounds();
        return new Dimension(bounds.x + bounds.width + 1, bounds.y + bounds.height + 1);
    }

    @Override
    public Dimension getMaximumSize(JComponent c) {
        Rectangle bounds = shape.getBounds();
        return new Dimension(bounds.x + bounds.width + 1, bounds.y + bounds.height + 1);
    }

}

您可能不需要担心其中的大多数,但您至少应该知道它们的存在,因为您可能希望稍后自定义其他一些 properties/functionality。

此委托旨在根据需要安装在按钮上,而不是将其安装为按钮的默认 UI 委托。这样做的原因是需要形状对象。如果需要,这允许每个按钮具有自己的形状。

您也可以将单个形状植入 UIManager 的属性中并使用它来代替,但我没有为这个例子费心。

然后我创建了自己的 shape/path...

public class PointerPath extends Path2D.Double {

    public PointerPath() {
        moveTo(1, 1);
        lineTo(150, 1);
        lineTo(198, 100);
        lineTo(150, 198);
        lineTo(1, 198);
        lineTo(50, 100);
        closePath();
    }

}

并将其应用到按钮...

ShapeButtonUI shapeUI = new ShapeButtonUI(new PointerPath());
JButton btn = new JButton("That way");
btn.setUI(shapeUI);

最终生成了类似...

现在,这是一个非常基本的示例,实际上,按钮应该围绕文本调整自身大小(带有一些额外的 padding/margin 信息),但这需要一个多部分形状,所以我们知道哪些部分可以调整大小以及调整方向,所以,很复杂。