Swing的JTable和JTree的渲染器机制难以理解。

9
通常在使用JTableJTree时,用户会定义自己的单元格渲染器。
DefaultTableCellRenderer继承用户组件是非常常见的,实现渲染方法getTableCellRendererComponent。事实上,DefaultTableCellRenderer继承自JLabel,因此在调用super(在渲染方法中)时返回自身(this),因此用户的渲染器也可以同样返回自身(this)。
这一切都很好地运作着。
我的问题是如何做到的?
每次表格调用该方法时,它都会给出不同的参数,并且输出标签会根据这些参数的函数而改变。如果确实是标签的相同实例-难道它不应该根据对此方法的最后一次调用进行更改吗? 这是否意味着所有表格的单元格都由相同的标签实例组成,该实例保存相同的值(最后一次调用渲染器方法的值)?
我在网上搜索并查看了Swing的代码,但是没有找到任何克隆或复制构造函数来复制输出标签的实际操作。我没有发现任何证据表明Swing使用反射来每次从头重新实例化渲染器。

我阅读了Swing的JTables教程,在那里我找到了下面这些内容:

你可能期望表格中的每个单元格都是一个组件。但出于性能原因,Swing表格的实现方式不同。 相反,通常使用单个单元格渲染器来绘制包含相同类型数据的所有单元格。您可以将渲染器视为可配置的墨水印章,表格使用它将格式正确的数据盖印到每个单元格上。当用户开始编辑单元格的数据时,单元格编辑器接管单元格,控制单元格的编辑行为。

他们给出了一个提示,即我的说法是正确的,但没有解释如何实际完成。

我无法理解。你们有谁能理解吗?

3个回答

13

这是享元模式的一种实现。

JTable在重新绘制时,会开始一个循环并迭代每个需要绘制的单元格。

对于每个单元格,它会使用与单元格对应的参数来调用渲染器。渲染器返回一个组件,该组件在对应于当前表格单元格的矩形中绘制。

然后为下一个单元格调用渲染器,并将返回的组件(例如具有不同文本和颜色)绘制在对应于该单元格的矩形中,以此类推。

想象一下,每次调用渲染器时,都会截取返回组件的屏幕截图并粘贴到表格单元格中。


2
+1 截图的比喻很好。我认为最常用的是“印章”,但截图可能更清晰。 - Robin
1
谢谢。这个比喻确实很有用 : )。现在已经理解了。 - aviad cohen

4

除了@JB清晰的解释,说明了JTableJTree如何使用轻量级模式,请注意这两个类都提供了公共方法getCellRenderer()getCellEditor()。检查这些方法以了解JTable如何使用类字面常量作为运行时类型标记来按类选择呈现器或编辑器,如果列没有指定。在内部,JTable使用Hashtable defaultRenderersByColumnClass进行实例存储。


4

经过一番调查,从DefaultTableCellRenderer文档中找到了下一个实现说明:

实现说明:这个类继承自JLabel,是一个标准的组件类。然而,JTable使用了一个独特的机制来渲染其单元格,因此需要一些稍微修改过的行为来适应其单元格渲染器。表类定义了一个单一的单元格渲染器,并将其用作在表中渲染所有单元格的橡皮图章;它渲染第一个单元格,改变该单元格渲染器的内容,将原点移动到新位置,重新绘制它,等等。标准的JLabel组件不是设计成以这种方式使用的,我们希望避免每次绘制单元格时触发重新验证。这将极大地降低性能,因为重新验证消息将被传递到容器的层次结构中,以确定是否会影响任何其他组件。由于渲染器仅在绘画操作的生命周期内具有父级,因此我们同样希望避免与绘画操作相关的遍历层次结构的开销。因此,这个类覆盖了validate、invalidate、revalidate、repaint和firePropertyChange方法,使它们成为无操作,并仅覆盖isOpaque方法以提高性能。如果您编写自己的渲染器,请记住这个性能问题。

这本质上就是JB上面解释的内容。

感谢(快速)回答。


1
类似的优化也可以在CellRendererPane中找到,它被TableUI委托使用,并在这里进行了说明。 - trashgod

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