上一部教程介绍了在NetBeans中处理组件级选择的基本知识—如何从TopComponent
的Lookup
中提供对象,以及如何编写对获得焦点的组件的Lookup
敏感的其他组件。
本教程侧重于Nodes API,它可以比组件级选择执行更加粒度化的视图和选择。当然,您可以编写一个组件,该组件可以根据需要读取和写入其自身的Lookup,并且这样可以提供更加粒度化的选择逻辑。但是Nodes API可以轻松执行该操作,并且与自己执行该操作相比,它提供了很多优势。
第一个优点是Nodes API提供一个表示层—采用某种方式编辑的数据模型和向用户公开该数据模型的UI组件之间的层。该层非常有用并且功能强大,因为可以采用多种方式或使用多个UI演示同一种模型。
第二个优点是Explorer API—模块org.openide.explorer
提供一组丰富的组件—树、列表、三个表以及更多—这可以呈现Node及其子节点。
Node
是一个普通的层次结构对象—一个Node
具有:
l Children—其下面的层次结构中的节点,可以显示在树中
l Actions—一个操作数组,可以显示在弹出菜单中
l Display Name—一个人类可读的局部的显示名称,可以显示在UI组件中
l Icon—可以显示在UI组件中的图标
Node
可以激活以上任何一項内容的更改,并且资源管理器UI组件将自动更新自身。这并不意味着上一部教程没有用—相反,它解释了Nodes API可以工作的原因。org.openide.nodes.Node
具有一个getLookup()
方法
。实际上当您更改IDE中Projects选项卡的选择时发生了一些事情,例如Projects选项卡是TopComponent。它代理树中当前选择的对象的Lookup
—就像Utilities.actionsGlobalContext()
Lookup
代理获得焦点的组件一样,并且当焦点改变时激活更改。
使用Explorer API中的组件,很容易创建您自己的Node
树视图,并且使用非常少的代码在您自己的组件中拥有此类型的代理。查看器类型组件(如上一部教程中的MyViewer
组件)不用执行其他任何特殊的操作便能够响应Explorer组件中的选择更改—当选择更改时将自动通知它们。
入门
本教程中的代码将延续上一部教程中的代码—假设您熟悉该代码以及其执行的操作。若要下载完整的示例,请访问http://plugins.netbeans.org/PluginPortal/faces/PluginDetailPage.jsp?pluginid=3146。
创建资源管理器视图
您将做的第一件事是对您的MyEditor
编辑器组件进行一些实际修改。 首先从编辑器中打开它。
- 首先,显示My Editor项目的属性对话框。在Libraries选项卡上,单击add按钮,然后在对话框中键入“BeanTreeView”。当您看到列出了Explorer & Property Sheet API之后,单击OK,如下所示。这将在Explorer API模块上添加一个依赖性,以便您可以使用其中的类。
- 下一步,删除操作处理程序方法的主体部分:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
}
并从构造函数中删除对它的调用。这样,当您删除与其关联的按钮时,处理程序方法也将被删除。
- 切换到表单设计器,选择所有组件并删除它们。
- 在Component Inspector中,右键单击
TopComponent
节点,然后选择Set Layout > BorderLayout,如下所示:
- 单击Component Palette窗口中的
JScrollPane
按钮并将滚动窗格拖动到表单上—它将占据整个表单。此处的关键是所有Explorer UI组件都是JScrollPane
的子类—因此您可以只更改实例化代码即可创建一个资源管理器视图。 - 选择您的
JscrollPane
,右键单击它,然后选择Customize Code。通过添加new BeanTreeView()
来自定义Code Customizer中的初始行,如下所示:
BeanTreeView
是Explorer API中的一个组件—在Node
及其子节点之上的一个基本的基于
JTree
的视图,其中具有内置的弹出菜单处理、搜索以及更多操作。
- 切换到代码编辑器并按下Ctrl-Shift-I以导入BeanTreeView,因为需要添加导入语句,如下所示:
- 下一步是为您的树提供显示的内容。Explorer UI组件的工作类似于以下内容:当添加到某个容器时,它们搜索该容器及其祖先,直到它们找到实现
ExplorerManager.Provider
的容器为止。因此不要将该节点设置为直接在组件上查看—应该将其设置为在组件的管理器上查看。这样便可以拥有多个视图,主/详细信息视图以及由单个管理器管理的所有此类视图。按照如下方式添加MyEditor的签名:
public class MyEditor extends TopComponent implements ExplorerManager.Provider {
然后按下Ctrl-Shift-I修复导入。保留签名行中的插入符号,一个灯泡状的图案将出现在边缘。按下Alt-Enter并接受“Implement all abstract methods”提示。这将添加一个方法getExplorerManager()
。按照如下方式实现该方法:
private final ExplorerManager mgr = new ExplorerManager();
public ExplorerManager getExplorerManager() {
return mgr;
}
- 现在,由于目标是一个可以显示多个
APIObject
s的组件,因此您需要一个或两个Node
以显示在您的组件中。每个组件都将拥有自己的APIObject
实例。因此,您现在将添加代码来为您的树视图创建一个根节点。向构造函数中添加以下行:
mgr.setRootContext(new AbstractNode(new MyChildren()));
上面的代码为MyEditor
的子组件的所有资源管理器视图设置根节点。
- 如果您尝试Fix Imports,则可能会看到错误对话框,它告诉您
AbstractNode
或MyChildren
都无法解析。若要解析AbstractNode
,您需要在Nodes API模块上添加一个依赖性。右键单击My Editor项目,然后转到Libraries页面并单击Add Dependency。在Add对话框中键入“AbstractNode”,并且选中列表中的“Nodes API”项目时单击OK或按Enter键。 - 现在回到源编辑器中,按Ctrl-Shift-I键以执行Fix Imports。将通知您
MyChildren
无法解析。好了—您可以编写它了。
实现Node和Node子节点
您将注意到上面使用的类名为AbstractNode
。尽管名称有所暗示,但它并不是抽象类!它是org.openide.nodes.Node
的一种有用实现,可以节省很多时间和精力—而不是您自己实现Node,您可以只创建AbstractNode并将一个可为其提供子节点的Children
对象传递给它,然后根据需要设置它的图标和显示名称。因此这是使Node
对象表示某些内容的简单方法,无需为Node
创建任何子类。
下一步是实现MyChildren
,以便初始节点下面具有子节点。
- 右键单击My Editor项目中的
org.myorg.myeditor
包并从弹出菜单中选择New > Java Class。 - 在New Java Class向导中,将该类命名为“MyChildren”,然后单击Finish或按Enter键来创建该类。
- 修改该类的签名以便展开
Children.Keys
:
class MyChildren extends Children.Keys {
- 按Ctrl-Shift-I键执行Fix Imports
- 将插入符号放置到类签名行中。当空白处出现灯泡状图案时,按Alt-Enter键,然后再次按Enter键以接受“Implement all Abstract Methods”提示。这将添加一个
createNodes (Object key)
方法—这是您将创建节点的位置,这些节点将是您的根节点的子节点。 - 但是首先,您需要覆盖一个方法—
addNotify
。由于使用的是Swing组件中的addNotify()
模式,因此Children.Keys.addNotify()
将在
第一次关注这个Children对象时得到调用—即第一次询问它的子节点。因此您可以延迟创建子Node,直到用户实际上已经在视图中展开了父节点并且需要查看子节点为止。将插入符号放置在源文件中的某个位置并按Alt-Insert键。然后选择“Override Method...”。在出现的对话框中,展开“Children”,选择addNotify()
方法,然后单击OK或按Enter键。 - 实现
addNotify()
方法如下所示:
protected void addNotify() {
APIObject[] objs = new APIObject[5];
for (int i = 0; i < objs.length; i++) {
objs[i] = new APIObject();
}
setKeys (objs);
}
正如您从名称Children.Keys
中所猜测的一样,您的父类所执行的操作就是获取一个数组或关键对象的Collection
,并充当它们的Node
s的工厂。因此,您在addNotify()
中调用setKeys()
,因为addNotify()
告诉您某些操作将询问子节点。对于数组中的每个元素或您传递给setKeys()
的集合,都将调用一次createNodes()
(请注意,这意味着如果必要的话可以让多个节点代表一个对象)。
- 现在您需要实现为所有这些节点实际创建Node对象的代码。按照如下代码实现
createNodes()
:
protected Node[] createNodes(Object o) {