关于 JTree 的一些碎碎念

本博号称关注 Web2.0, Ruby/Rails,Java。但是据本人所知,到目前为止还没有任何一篇关于 Java 方面的文章和只言片语。作为本博的博主,确实有点大言不惭的感觉,一想到这个心中就颇不宁静啊。

其实作为一名博客,写东西的欲望一定要强烈,很多的时候我们并没有很多的素材可以写,因为生活几乎每天都是 Just so so,我们谁都不想书写平淡,总想语不惊人死不休。作为博客的人们可能这种感觉更为强烈,我是一名独立博客耶,我不能人云亦云啊,我得从我的文章里抒发我的思想啊。艹,你娘的思想也就大街上小摊上的胡萝卜包子一般廉价,还装。所以呢,我们还是需要不时的刺激一下自己的神经,同时呢,也给我们伟大的祖国发育不良的国联网添加一些有趣或无趣的素材资料吧,也算是为我国早日实现四个现代化添砖加瓦了。

===========我是分割线=========

树中特定的节点可以由 TreePath(封装节点及其所有祖先的对象)标识,或由其显示行(其中显示区域中的每一行都显示一个节点)标识。展开 节点是一个非叶节点(由返回 false 的 TreeModel.isLeaf(node) 标识),当 展开 其所有祖先时,该节点将显示其子节点。 折叠 节点是隐藏它们的节点。 隐藏 节点是位于折叠祖先下面的节点。所有 可查看 节点的父节点都是可以展开的,但是可以显示它们,也可以不显示它们。 显示 节点是可查看的并且位于可以看到它的显示区域。

这是 JDK 5.0 非官方中文文档中的描述,我想这个描述给我们的第一个直观印象就是这个控件应该和 Windows 资源管理器中的树状结构的表现是一致的。确实如此,JTree 确实是用来作为树形展示使用的,因为我们在很多的数据管理和业务处理上,秉承了我们自古就袭承的分类方式,所以我们有很多的业务可能用到或者说可以用到 JTree。

那么 JTree 怎么用的呢?这个问题我曾经问过自己很多次,还做过很多相关的工作来学习 JTree 的使用,记得来到北京实习做的第一个学习阶段作业就是使用公司已有的组件产品做一个叠加分析的 Demo。作为一个比较友好的叠加分析工具,就肯定需要一些 GIS 数据处理的功能,比如打开工作空间,数据源,数据集,地图,同时还应该能简单地做一些图层删除或者地图关闭之类的基础功能。这样一来,主体的工作就是基础功能模块的实现了,只是在基础功能模块的基础上,添加一个叠加分析功能模块,而在基础功能模块中的可视化显示 JTree 必然是首选控件。

在实现树状加载和显示工作空间,参考公司已有的桌面产品 UI 设计,需要再 JTree 的每个节点之前添加一些图标,用于标识当前节点的数据类型,例如线数据集和面数据集的图标是有明显差异的,这样对用户的友好度会提高很多。默认的 JTree 使用 Java 默认的 meta 风格显示文件夹 (根节点) 和文件 (叶节点) 的图标样式,那么如何添加自定义的图标显示呢?当时我就在 JTree 的文档中苦苦找寻,希望自己能找到一个 setIcon() 的方法,最终无耻的失败鸟。后来才知道之需要自己实现一个类,实现 TreeCellRenderer 就可以了,重写下面这个方法 (Sun 就是这么排版的,我觉得这样挺好的,不要怪我占太多行了)

Component getTreeCellRendererComponent(JTree tree,

在这个方法中,它的返回值为一个 Component,那么我们完全可以自由发挥了,比如返回一个 JLabel。当时欣喜若狂,赶紧将代码巴拉巴拉地敲好,一看效果显著啊,根据类型不同生成不同的 Icon 设置给将要返回的 JLabel 对象就 OK 了。

实习的工作相对简单,当时对 JTree 的理解也就点到为止了。那么此次项目组需要退出 Objects Java 的控件,其中就有一些控件需要使用到 JTree 来实现,功能要求相对就复杂了不少。

需要在一个节点文本前,显示多个图标,除了用于区别节点数据类型的图标外,还需要添加几个操作图标,用于可视化节点数据的操作,那么返回一个什么呢?JPanel 无疑是最好的选择,轻量化容器中的万金油。在 JPanel 中添加几个可视化的 Icon 并不是难事,只是 JLabel 多少的问题了,通过对节点数据类型的判断分别设置便好了。问题是,如何判断用户当前点击的位置落在 JPanel 中的那个操作图标上呢?又如何将这些操作直接反映到实际的数据上来呢?通过 JTree.getUI() 方法获取当前的 JTree 的 UI,然后调用 TreeUI.getPathBounds(JTree tree, TreePath path) 方法获取当前节点的绘制区域,之后通过鼠标事件 MouseEvent 的 getX() 和 getY() 方法获取当前鼠标的位置信息,由于控件的编写图标的大小由自己定义,所以可以确定图标的大小和 JPanel 的布局 (我使用的是 FlowLayout,并且使用 setHgap() 方法将水平控件间距设置为 0),通过像素坐标的计算便可以得出当前用户鼠标单击事件应该响应哪个对应索引处的动作。

在实现的过程中,还需要使用到编辑节点的功能,那么如何来做呢?首选实现 TreeCellEditor 接口,完全自己定制,可以避免像继承自 DefaultTreeCellEditor 的烦恼 (因为它能满足一般需求,但是会限制你拳脚)。TreeCellEditor 也有一个很嚣张的方法,号称自己要接管 TreeCellRenderer 的方法,就是这个啦

Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row)

重写这个方法,用来自定义编辑时的节点渲染,例如我的树形控件在编辑的时候只能编辑节点名称,那么前面的图标还是要保持与未编辑时一致的,编辑时只需要将后面的文本置换为一个 JTextField 就可以了。然后为该 JTextField 注册一个键盘事件监听器,当输入 Enter 的时候,调用 TreeCellEditor 的 stopEditing() 方法,触发停止编辑事件。如果我们需要将编辑保存至数据模型中的话,就在 stopEditing() 方法中编写业务代码即可。

综上 JTree 这个控件实际上是一个完全遵循 MVC 架构的设计,甚至比 MVC 还抽象一些。JTree 中只处理数据和显示的一些控制,例如判断当前节点是否能够被编辑的方法 isPathEditable(TreePath path) 等。数据模型完全可以委托为 TreeModel 来管理,之需要使用 setModel 便可以轻松地将 JTree 和 TreeModel 关联起来。而在其显示渲染上便是完全由 TreeCellRenderer 来接管的,只需实现一个类,该类实现 TreeCellRenderer 接口,重写 getTreeCellRendererComponent(…) 方法,便可实现完全自定义面板显示节点。抽象最高层的是,当节点为可编辑状态时,其渲染又交由 TreeCellEditor 接管了,在编辑节点时,TreeCellEditor 的 getTreeCellRendererComponent(…) 方法便将完全接管节点的渲染工作,并且控制编辑时的动作,例如取消编辑和停止编辑之类的种种。这么看来,其实 JTree 只是提供一个壳子,通过其内部的 TreeMod 存储数据,外部的 TreeCellRenderer 和 TreeCellEditor 来控制其显示渲染和编辑渲染,给开发人员提供了一个可高度定制化的控件接口。

==========我还是分割线=========

本文的形成是如行云流水账一般啊,我也有点不知所云了,众位看官,能看则看,不能看则拉倒吧。JDK API 文档在你家里叫你回家翻翻呢。

  • java 初学者

    垃圾博文

  • thinks

    行好