油漆组件是如何工作的?
Posted
技术标签:
【中文标题】油漆组件是如何工作的?【英文标题】:How does paintComponent work? 【发布时间】:2013-03-10 18:53:28 【问题描述】:这可能是一个非常菜鸟的问题。我刚开始学习Java
不明白paintComponent方法的操作。我知道如果我想画一些东西,我必须重写paintComponent方法。
public void paintComponent(Graphics g)
...
但是什么时候调用呢?我从来没有见过像“object.paintComponent(g)”这样的东西,但它仍然是在程序运行时绘制的。
什么是 Graphics 参数?这个从哪里来?调用方法时必须提供参数。但正如我之前所说,似乎从未显式调用此方法。那么这个参数是谁提供的呢?为什么我们必须将其转换为 Graphics2D?
public void paintComponent(Graphics g)
...
Graphics2D g2= (Graphics2D) g;
...
【问题讨论】:
以Painting in AWT and Swing开头。 @trashgod 总是很乐意发布一个具体的链接——swing tag wiki 中也引用了这个链接 :-) 或者换句话说:总是先查看 wiki ... @kleopatra:+1 在下一个(不可避免的)链接消失时也更容易修复。 【参考方案1】:GUI 系统的内部调用该方法,并将Graphics
参数作为您可以在其上绘制的图形上下文传递。
【讨论】:
@nubhihi219 没关系 :-) 你无法控制绘画何时发生,internals 是......好吧......内部,没什么好担心的关于应用程序开发,除了非常罕见的极端情况【参考方案2】:您可以在这里做两件事:
-
阅读Painting in AWT and Swing
使用调试器并在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
涉及的sn-p如下:
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
,但您可以使用其他Graphics
“不必”扩展 Graphics2D
(这种情况并不经常发生,但 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?【参考方案3】:对您的问题的(非常)简短的回答是,paintComponent
被称为“需要时”。有时更容易将 Java Swing GUI 系统视为一个“黑盒”,其中大部分内部结构都在没有太多可见性的情况下处理。
决定何时需要重新绘制组件的因素有很多,包括移动、调整大小、更改焦点、被其他框架隐藏等等。其中许多事件是自动检测到的,当确定需要该操作时,会在内部调用paintComponent
。
我已经使用 Swing 多年,我认为我曾经直接调用过 paintComponent
,甚至没有看到它直接从其他东西调用过。我最接近的是使用repaint()
方法以编程方式触发某些组件的重绘(我假设它在下游调用了正确的paintComponent
方法。
根据我的经验,paintComponent
很少被直接覆盖。我承认有些自定义渲染任务需要这种粒度,但是 Java Swing 确实提供了一组(相当)健壮的 JComponents 和 Layouts,可以用来完成大部分繁重的工作,而无需直接覆盖 paintComponent
。我想我的意思是在你尝试推出你自己的自定义渲染组件之前,确保你不能用原生 JComponents 和 Layouts 做一些事情。
【讨论】:
感谢@GuillaumePolet 捕捉到这一点。正如你所说,我的术语在技术上是不正确的。我已经编辑了帖子以澄清。 SeKa,既然你已经和swing 合作了很多年,你介意我问一下,swing/javafx 值得学生/求职者学习吗? swing/javafx 用得很多吗?非常感谢您的任何建议。【参考方案4】:调用object.paintComponent(g)
是一个错误。
而是在创建面板时自动调用此方法。 paintComponent()
方法也可以被Component
类中定义的repaint()
方法显式调用。
调用repaint()
的效果是Swing自动清除面板上的图形,并执行paintComponent
方法重绘该面板上的图形。
【讨论】:
【参考方案5】:如果您希望任何以前的绘图永久保存在组件上,您可能必须重新定义方法void paintComponent(Graphics g)
。您需要通过像super.painComponent();
那样显式调用升序类的方法来做到这一点。这样,任何时候 java 都需要使用该 paintComponent 方法,您就可以保持所做的更改。
这是因为如果你不这样做,超类将通过简单地调用它自己的方法来撤销你所做的一切,完全忽略任何更改。
【讨论】:
以上是关于油漆组件是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章