paintComponent是如何工作的?

82

这可能是一个非常初级的问题。我刚开始学习Java。

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

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

但是它什么时候被调用呢?我从来没有看到过类似于"object.paintComponent(g)"这样的东西,但是当程序运行时却会被绘制出来。

那么Graphics参数是什么?它从哪里来?必须在方法被调用时传递参数。但正如我之前所说,似乎从未显式调用此方法。那么是谁提供了这个参数?为什么我们需要将其转换为Graphics2D?

public void paintComponent(Graphics g)
{
    ...
    Graphics2D g2= (Graphics2D) g;
    ...
}

9
Painting in AWT and Swing开始翻译。 - trashgod
2
@trashgod总是很好心地发布一个具体的链接 - 这也在swing标签wiki中有引用 :-) 换句话说:始终首先查看Wiki... - kleopatra
@kleopatra:+1 在下一次(不可避免的)链接消失中更容易修复。 - trashgod
5个回答

56
你的问题的简短答案是,当需要时调用paintComponent。有许多因素决定了何时需要重新绘制组件,包括移动、调整大小、改变焦点、被其他框架隐藏等等。许多这些事件是自动检测到的,当确定需要该操作时,内部会调用paintComponent。在我多年的Swing工作经验中,我从未直接调用过paintComponent,甚至没有看到它被其他方法直接调用。在使用Swing时,很少直接重写paintComponent。Java Swing提供了一个(相当)强大的JComponents和Layouts集合,可以完成大部分繁重的工作,而无需直接重写paintComponent。要确保在尝试自己编写自定义渲染组件之前,不能使用原生JComponents和Layouts完成所需任务。

感谢@GuillaumePolet指出这个问题。我的术语在那里技术上是不正确的,正如你所说的那样。我已经编辑了帖子以澄清。 - SeKa
2
SeKa,由于您已经使用swing工具很多年了,我想问一下,从学生或求职者的角度来看,学习swing/javafx是否值得?swing/javafx被广泛使用吗?非常感谢您的任何建议。 - Thor

17

这里有两件事情可以做:

  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,但您可以使用其他不必扩展Graphics2D的Graphics(这种情况并不经常发生,但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);
    }
}

如果TestPaint没有扩展JPanel类,您将如何向JPanel绘制? - Doug Hauf

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

1
@nubhihi219 没关系 :-) 无论如何,您都无法控制绘画发生的时间,internals 是...嗯...内部机制,在应用程序开发中无需担心,除非在非常罕见的情况下。 - kleopatra

2

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

相反,当面板被创建时,该方法会自动调用。可以通过Component类中定义的repaint()方法显式地调用paintComponent()方法。

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


0

如果您想让组件上的任何以前的绘图保持不变,您可能需要重新定义方法void paintComponent(Graphics g){}。您需要通过显式调用父类的方法来实现这一点,例如super.painComponent();。这样,每当Java需要使用该paintComponent方法时,您都会保留所做的更改。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接