油漆组件是如何工作的?

这可能是个很菜的问题,我刚开始学 Java

我不理解 PaintComponent 方法的操作。我知道如果我想要绘制一些东西,我必须重写 PaintComponent 方法。

public void paintComponent(Graphics g)
{
...
}

但是什么时候命名呢?我从来没有看到过类似于“ object.PaintComponent (g)”的东西,但是当程序运行时,它仍然会被绘制出来。

图形参数是什么?从哪来的?调用方法时必须提供。但是正如我之前所说,这个方法似乎从来没有被显式调用过。那么谁提供这个参数呢?为什么我们要把它转换成 Graphics2D 呢?

public void paintComponent(Graphics g)
{
...
Graphics2D g2= (Graphics2D) g;
...
}
162146 次浏览

GUI 系统的内部调用该方法,并将 Graphics参数作为图形上下文传递给您,您可以在上下文中绘制图形。

在这里你可以做两件事:

  1. 阅读 AWT 和 Swing 中的绘画
  2. 使用一个调试器并在 PaintComponent 方法中放置一个断点。然后沿着堆栈跟踪上行,查看如何提供 Graphics 参数。

仅供参考,下面是我从最后发布的代码示例中获得的堆栈跟踪:

Thread [AWT-EventQueue-0] (Suspended (breakpoint at line 15 in TestPaint))
TestPaint.paintComponent(Graphics) line: 15
TestPaint(JComponent).paint(Graphics) line: 1054
JPanel(JComponent).paintChildren(Graphics) line: 887
JPanel(JComponent).paint(Graphics) line: 1063
JLayeredPane(JComponent).paintChildren(Graphics) line: 887
JLayeredPane(JComponent).paint(Graphics) line: 1063
JLayeredPane.paint(Graphics) line: 585
JRootPane(JComponent).paintChildren(Graphics) line: 887
JRootPane(JComponent).paintToOffscreen(Graphics, int, int, int, int, int, int) line: 5228
RepaintManager$PaintManager.paintDoubleBuffered(JComponent, Image, Graphics, int, int, int, int) line: 1482
RepaintManager$PaintManager.paint(JComponent, JComponent, Graphics, int, int, int, int) line: 1413
RepaintManager.paint(JComponent, JComponent, Graphics, int, int, int, int) line: 1206
JRootPane(JComponent).paint(Graphics) line: 1040
GraphicsCallback$PaintCallback.run(Component, Graphics) line: 39
GraphicsCallback$PaintCallback(SunGraphicsCallback).runOneComponent(Component, Rectangle, Graphics, Shape, int) line: 78
GraphicsCallback$PaintCallback(SunGraphicsCallback).runComponents(Component[], Graphics, int) line: 115
JFrame(Container).paint(Graphics) line: 1967
JFrame(Window).paint(Graphics) line: 3867
RepaintManager.paintDirtyRegions(Map<Component,Rectangle>) line: 781
RepaintManager.paintDirtyRegions() line: 728
RepaintManager.prePaintDirtyRegions() line: 677
RepaintManager.access$700(RepaintManager) line: 59
RepaintManager$ProcessingRunnable.run() line: 1621
InvocationEvent.dispatch() line: 251
EventQueue.dispatchEventImpl(AWTEvent, Object) line: 705
EventQueue.access$000(EventQueue, AWTEvent, Object) line: 101
EventQueue$3.run() line: 666
EventQueue$3.run() line: 664
AccessController.doPrivileged(PrivilegedAction<T>, AccessControlContext) line: not available [native method]
ProtectionDomain$1.doIntersectionPrivilege(PrivilegedAction<T>, AccessControlContext, AccessControlContext) line: 76
EventQueue.dispatchEvent(AWTEvent) line: 675
EventDispatchThread.pumpOneEventForFilters(int) line: 211
EventDispatchThread.pumpEventsForFilter(int, Conditional, EventFilter) line: 128
EventDispatchThread.pumpEventsForHierarchy(int, Conditional, Component) line: 117
EventDispatchThread.pumpEvents(int, Conditional) line: 113
EventDispatchThread.pumpEvents(Conditional) line: 105
EventDispatchThread.run() line: 90

Graphics 参数来自这里:

RepaintManager.paintDirtyRegions(Map) line: 781

所涉及的代码片段如下:

Graphics g = JComponent.safelyGetGraphics(
dirtyComponent, dirtyComponent);
// If the Graphics goes away, it means someone disposed of
// the window, don't do anything.
if (g != null) {
g.setClip(rect.x, rect.y, rect.width, rect.height);
try {
dirtyComponent.paint(g); // This will eventually call paintComponent()
} finally {
g.dispose();
}
}

如果你看一下它,你会看到它从 JComponent 本身获取图形(通过 javax.swing.JComponent.safelyGetGraphics(Component, Component)间接获取) ,它自己最终从它的第一个“重量级父元素”(剪切到组件边界)获取图形,它自己从相应的本机资源获取图形。

关于你必须将 Graphics转换为 Graphics2D的事实,只是碰巧在使用 Window Toolkit 时,Graphics实际上扩展了 Graphics2D,但是你可以使用其他“不必”扩展 Graphics2DGraphics(这种情况并不经常发生,但是 AWT/Swing 允许你这样做)。

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


import javax.swing.JFrame;
import javax.swing.JPanel;


class TestPaint extends JPanel {


public TestPaint() {
setBackground(Color.WHITE);
}


@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval(0, 0, getWidth(), getHeight());
}


public static void main(String[] args) {
JFrame jFrame = new JFrame();
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setSize(300, 300);
jFrame.add(new TestPaint());
jFrame.setVisible(true);
}
}

对你的问题的(非常)简短的回答是,paintComponent被称为“当它需要的时候。”有时更容易将 JavaSwing GUI 系统想象成一个“黑盒子”,其中大部分内部机制都是在不太可见的情况下处理的。

决定何时需要重新绘制组件的因素有很多,从移动、调整大小、更改焦点、被其他框架隐藏等等。许多这样的事件是自动检测到的,并且当确定操作是必需的时候,就在内部调用 paintComponent

我在 Swing 工作了很多年,我不认为我直接调用了 永远不会,甚至没有看到它直接从其他东西调用。最接近的方法是使用 repaint()方法以编程方式触发对某些组件的重新绘制(我假设它在下游调用正确的 paintComponent方法)。

根据我的经验,paintComponent很少被直接覆盖。我承认有一些自定义的呈现任务需要这样的粒度,但是 Java Swing 确实提供了一组(相当)健壮的 JComponent 和 Layouts,它们可以用来完成大部分繁重的工作,而不必直接覆盖 paintComponent。我想我在这里的观点是,在您尝试滚动自己的定制呈现组件之前,确保不能使用本机 JComponent 和 Layouts 进行某些操作。

调用 object.paintComponent(g)是一个错误。

而是在面板创建时自动调用此方法。paintComponent()方法也可以由 Component类中定义的 repaint()方法显式调用。

调用 repaint()的效果是 Swing 自动清除面板上的图形,并执行 paintComponent方法重新绘制该面板上的图形。

如果希望组件上的任何以前的绘图都是永久的,则可能必须重新定义方法 void paintComponent(Graphics g){}。您需要通过像 super.painComponent();那样显式调用升级类的方法来实现这一点。这样,Java 在任何时候都需要使用您正在保持更改的 PaintComponent 方法。

这是由于如果您不这样做,超类将通过简单地调用它自己的方法来撤消您所做的一切,完全忽略任何更改。