How does paintComponent work?

2019-01-01 11:32发布

This might be a very noob question. I'm just starting to learn Java

I don't understand the operation of paintComponent method. I know if I want to draw something, I must override the paintComponent method.

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

But when is it called? I never see anything like "object.paintComponent(g)" but still it is drawn when the program is running.

And what is the Graphics parameter? Where is it from? Parameter must be supplied when the method is called. But as I said before, it seems like this method is never be explicitly called. So who provides this parameter? And why do we have to cast it to Graphics2D?

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

4条回答
有味是清欢
2楼-- · 2019-01-01 11:43

The (very) short answer to your question is that paintComponent is called "when it needs to be." Sometimes it's easier to think of the Java Swing GUI system as a "black-box," where much of the internals are handled without too much visibility.

There are a number of factors that determine when a component needs to be re-painted, ranging from moving, re-sizing, changing focus, being hidden by other frames, and so on and so forth. Many of these events are detected auto-magically, and paintComponent is called internally when it is determined that that operation is necessary.

I've worked with Swing for many years, and I don't think I've ever called paintComponent directly, or even seen it called directly from something else. The closest I've come is using the repaint() methods to programmatically trigger a repaint of certain components (which I assume calls the correct paintComponent methods downstream.

In my experience, paintComponent is rarely directly overridden. I admit that there are custom rendering tasks that require such granularity, but Java Swing does offer a (fairly) robust set of JComponents and Layouts that can be used to do much of the heavy lifting without having to directly override paintComponent. I guess my point here is to make sure that you can't do something with native JComponents and Layouts before you go off trying to roll your own custom-rendered components.

查看更多
浮光初槿花落
3楼-- · 2019-01-01 11:51

The internals of the GUI system call that method, and they pass in the Graphics parameter as a graphics context onto which you can draw.

查看更多
还给你的自由
4楼-- · 2019-01-01 11:53

Calling object.paintComponent(g) is an error.

Instead this method is called automatically when the panel is created. The paintComponent() method can also be called explicitly by the repaint() method defined in Component class.

The effect of calling repaint() is that Swing automatically clears the graphic on the panel and executes the paintComponent method to redraw the graphics on this panel.

查看更多
看淡一切
5楼-- · 2019-01-01 12:04

Two things you can do here:

  1. Read Painting in AWT and Swing
  2. Use a debugger and put a breakpoint in the paintComponent method. Then travel up the stacktrace and see how provides the Graphics parameter.

Just for info, here is the stacktrace that I got from the example of code I posted at the end:

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  

The Graphics parameter comes from here:

RepaintManager.paintDirtyRegions(Map) line: 781 

The snippet involved is the following:

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();
                    }
                }

If you take a look at it, you will see that it retrieve the graphics from the JComponent itself (indirectly with javax.swing.JComponent.safelyGetGraphics(Component, Component)) which itself takes it eventually from its first "Heavyweight parent" (clipped to the component bounds) which it self takes it from its corresponding native resource.

Regarding the fact that you have to cast the Graphics to a Graphics2D, it just happens that when working with the Window Toolkit, the Graphics actually extends Graphics2D, yet you could use other Graphics which do "not have to" extends Graphics2D (it does not happen very often but AWT/Swing allows you to do that).

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);
    }
}
查看更多
登录 后发表回答