¿Cómo funciona PaintComponent?

Esta podría ser una pregunta muy novato. Estoy empezando a aprender Java

No entiendo el funcionamiento del método paintComponent. Sé que si quiero dibujar algo, debo anular el método paintComponent.

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

Pero cuando se llama? Nunca veo algo como “object.paintComponent (g)” pero aún se dibuja cuando el progtwig se está ejecutando.

¿Y cuál es el parámetro Graphics? ¿De dónde es? El parámetro debe ser proporcionado cuando se llama al método. Pero como dije antes, parece que este método nunca se llama explícitamente. Entonces, ¿quién proporciona este parámetro? ¿Y por qué tenemos que lanzarlo a Graphics2D?

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

La (muy) corta respuesta a su pregunta es que paintComponent se llama “cuando debe ser”. A veces es más fácil pensar en el sistema Java Swing GUI como una “caja negra”, donde la mayoría de los internos se manejan sin demasiada visibilidad.

Hay una serie de factores que determinan cuándo se debe volver a pintar un componente, desde moverse, cambiar el tamaño, cambiar el enfoque, estar oculto por otros marcos, y así sucesivamente. Muchos de estos eventos se detectan de forma auto-mágica, y se llama internamente a paintComponent cuando se determina que esa operación es necesaria.

He trabajado con Swing durante muchos años, y no creo haber llamado alguna paintComponent directamente a paintComponent , o incluso haberlo visto directamente desde otra cosa. Lo más cerca que he llegado es usando los métodos de repaint() para activar mediante progtwigción un repintado de ciertos componentes (que supongo que llama a los métodos paintComponent correctos en paintComponent descendente).

En mi experiencia, paintComponent rara vez se reemplaza directamente. Admito que hay tareas de renderizado personalizadas que requieren dicha granularidad, pero Java Swing ofrece un conjunto (bastante) robusto de JComponents y Layouts que se pueden usar para hacer gran parte del trabajo pesado sin tener que anular directamente paintComponent . Supongo que mi punto aquí es asegurarme de que no se puede hacer algo con JComponents y Layouts nativos antes de que intentes rodar tus propios componentes renderizados a medida.

Dos cosas que puedes hacer aquí:

  1. Leer pintura en AWT y Swing
  2. Use un depurador y ponga un punto de interrupción en el método paintComponent. Luego, recorra la stacktrace y vea cómo proporciona el parámetro Graphics.

Solo por información, esta es la stack de fichas que obtuve del ejemplo del código que publiqué al final:

 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) 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, AccessControlContext) line: not available [native method] ProtectionDomain$1.doIntersectionPrivilege(PrivilegedAction, 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 

El parámetro Graphics viene de aquí:

 RepaintManager.paintDirtyRegions(Map) line: 781 

El fragmento involucrado es el siguiente:

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

Si le echas un vistazo, verás que recupera los gráficos del JComponent mismo (indirectamente con javax.swing.JComponent.safelyGetGraphics(Component, Component) ) que a su vez lo toma de su primer “padre de peso pesado” (recortado a los límites del componente) que él mismo toma de su recurso nativo correspondiente.

En cuanto al hecho de que tiene que transmitir los Graphics a un Graphics2D , sucede que al trabajar con el Window Toolkit, Graphics realmente amplía Graphics2D , sin embargo, podría usar otros Graphics que no “tienen que” se extienden Graphics2D (no ocurren muy a menudo pero AWT / Swing te permite hacer eso).

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

Las partes internas del sistema GUI llaman a ese método, y pasan el parámetro Graphics como un contexto gráfico sobre el que puede dibujar.

Llamar a object.paintComponent(g) es un error.

En cambio, este método se llama automáticamente cuando se crea el panel. El método paintComponent() también se puede llamar explícitamente mediante el método paintComponent() definido en la clase Component .

El efecto de llamar a repaint() es que Swing borra automáticamente el gráfico en el panel y ejecuta el método paintComponent para volver a dibujar los gráficos en este panel.