原创 NetBeans管理教程三:NetBeans Nodes API 收藏

新一篇: NetBeans网络开发教程I—构建GUI(一) | 旧一篇: NetBeans选择管理教程II—使用节点

本教程演示了如何使用NetBeans Nodes API的一些特性。包括以下内容

l         使用图标装饰Nodes

l         使用HTML标记增强Node的显示效果

l         在属性表中创建显示属性

l         提供NodeAction

入门

本教程是NetBeans选择管理教程的延续该教程讲述了如何使用Lookup来管理NetBeans窗口系统中的选择后续教程演示了在管理选择过程中如何使用Nodes API

要下载完整的示例,单击此处

本教程使用在第一部教程中创建并在第二部教程中改进过的代码作为基础。如果您没有学习过这些教程,建议首先学习这些教程。

创建Node子类

正如在前一部教程中提到的一样Node属于表示对象。这意味着,它们本身不是一个数据模型 —— 而是底层数据模型 的一个表示层。在NetBeans IDEProjectsFiles窗口中可以看到在使用Node时底层数据模型为磁盘上的文件。在IDEServices窗口中,可以看到,当底层数据是NetBeans运行时环境的可配置内容时使用Node,比如可用的应用服务器和数据库。

作为表示层Node向它们模拟的对象添加人类友好的属性。基本属性包括

l         显示名称 —— 一个人类可读的、用户友好的显示名称

l         描述 —— 人类可读的、用户友好的描述通常显示为一个工具提示

l         图标 —— 一些图形化表示显示的对象及其可能状态的符号

l         动作 —— 右键单击节点时出现在上下文菜单中的动作可以由用户调用

在上一部教程中使用MyChildren类创建Node方法是首先调用

new AbstractNode (new MyChildren(), Lookups.singleton(obj))

然后调用setDisplayName(obj.toString())从而提供一个基本的显示名称。还可以使用很多方法使Node更具用户友好性。首先需要创建一个Node 子类:

  1. My Editor项目右键单击org.myorg.myeditor包并选择New > Java Class
  2. 打开向导后将类命名为MyNode并按下Enter或单击Finish
  3. 将类的签名和构造函数更改为
public class MyNode extends AbstractNode {
 
public MyNode(APIObject obj) {
super (new MyChildren(), Lookups.singleton(obj));
setDisplayName ( "APIObject " + obj.getIndex());
    }
    
public MyNode() {
super (new MyChildren());
setDisplayName ("Root");
    }
  1. 在代码编辑器中打开同一个包的MyEditor
  2. 将构造函数中的以下行:
mgr.setRootContext(new AbstractNode(new MyChildren()));
setDisplayName ("My Editor");

替换为以下代码:

mgr.setRootContext(new MyNode());
  1. 现在对Children对象进行类似更改。在编辑器中打开MyChildren类并将其createNodes方法更改为
protected Node[] createNodes(Object o) {
APIObject obj = (APIObject) o;
return new Node[] { new MyNode(obj) };
}
 

使用HTML增强显示名称

现在代码可以运行了但是目前为止您做的所有工作都只与逻辑有关。 一切和原来一样。惟一的区别(用户看不到)是,您现在使用了Node子类,而不只是AbstractNode

要做的第一件事是提供一个增强的显示名称。NodeExplorer API支持HTML的一个有限子集,可以使用这个子集增强Node标签在Explorer UI组件中的显示效果。支持以下标记

l         字体颜色 —— 使用标准html语法不支持字体大小和设置但支持字体颜色

l         字体样式标记 —— bius标记分别表示粗体、斜体、下划线和删除线

l         SGML实体的一个有限子集"<&‘’“”–—≠≤≥©®™ 

因为APIObject不包含特殊数据它只包含一个整数和一个创建日期所以您需要扩展这个示例并决定将奇数编号的APIObject显示为蓝色文本。

  1. MyNode添加以下方法
public String getHtmlDisplayName() {
APIObject obj = getLookup().lookup (APIObject.class);
if (obj!=null && obj.getIndex() % 2 != 0) {
return "<font color='0000FF'>APIObject " + obj.getIndex() + "</font>";
} else {
return null;
    }
}
  1. 上面的代码完成的任务如下当绘图时显示节点的Explorer组件首先调用getHtmlDisplayName()如果组件获得非空的返回值,那么它将用收到的HTML字符串和一个快速的、轻量型HTML呈现器来呈现节点。如果返回值为空,那么它将回滚到由getDisplayName()返回的值。通过这种方法任何MyNode只要其APIObject的索引不能被2整除,那么这个MyNode将具有一个非空的HTML显示名称。

再次运行套件您会看到如下内容

getDisplayName() getHtmlDisplayName()是两个独立的方法这有两个原因首先,这是最优化设置;其次您将会看到它可以将HTML字符串连接起来而不需要移除<html>标记。

可以进一步增强这一点 —— 在上一部教程中日期包含在HTML字符串中此处已经将日期移除了。现在让HTML字符串稍微复杂一点,为所有节点提供HTML显示名称。

  1. 修改getHtmlDisplayName()方法如下
public String getHtmlDisplayName() {
APIObject obj = getLookup().lookup (APIObject.class);
if (obj != null) {
return "<font color='#0000FF'>APIObject " + obj.getIndex() + "</font>" +
"<font color='AAAAAA'><i>" + obj.getDate() + "</i></font>";
} else {
return null;
    }
}
  1. 再次运行套件现在您将看到以下内容

可以稍作改动来改善显示效果当前在HTML中使用的是硬编码的颜色。 虽然NetBeans可以有不同的外观,但是不能保证硬编码的颜色与树或显示Node的其他UI组件的背景色不同或差别较大。

NetBeans HTML呈现器对HTML规范进行了扩展它可以通过传递UIManager键来查找颜色。Swing使用的外观提供了一个UIManager它管理给定外观使用的颜色和字体的名称-值映射。大多数但不是全部外观通过调用UIManager.getColor(String)来查找用于不同GUI元素的颜色其中字符串键是某个一致的值。因此,使用来自UIManager的值,可以保证总是生成可读的文本。将使用的两个键是“textText”和“controlShadow”,前者返回文本的默认颜色(通常为黑色,除非使用带有黑色背景主题的外观), 后者提供一个与默认控制背景颜色相对的颜色,但差别不是太大。

  1. 修改getHtmlDisplayName()方法如下
public String getHtmlDisplayName() {
APIObject obj = getLookup().lookup (APIObject.class);
if (obj != null) {
return "<font color='!textText'>APIObject " + obj.getIndex() + "</font>" +
"<font color='!controlShadow'><i>" + obj.getDate() + "</i></font>";
} else {
return null;
    }
}
  1. 再次运行套件您应该看到如下结果

您将会注意到原来的蓝色现在变成了黑色。使用UIManager.getColor("textText")的值可以保证文本在任何外观下都是可读的这非常有用另外在用户界面中应该谨慎使用颜色以避免搞得像水果色拉一样。如果想在UI中使用更加个性的颜色,最好找到一个始终满足要求的UIManager/值对,或者创建一个ModuleInstall类并从UIManager导出需要的颜色,如果知道外观的颜色主题,那么可以根据每个外观对其进行硬编码(if ("aqua".equals(UIManager.getLookAndFeel().getID())……)

提供图标

使用图标是一个明智的选择,它可以增强用户界面的效果。因此,提供16x16像素的图标是另一种改进UI外观的方法。使用图标的一个缺陷是,不能通过图标传递较多的信息 —— 没有太多的像素。另一个缺陷是(显示名称也有同样的缺陷)不能只使用颜色来区分节点 —— 世界上有许多色盲。

提供一个图标非常简单 —— 只需载入一个图像并进行设置。需要使用GIFPNG文件。如果没有这种格式的文件,可以使用下面的文件:

  1. 将上面的图像或另一个16x16 PNGGIF文件复制到MyEditor类所在的包中。
  2. MyNode类添加以下方法:
public Image getIcon (int type) {
return Utilities.loadImage ("org/myorg/myeditor/icon.png");
}

注意可能有不同的图标大小和样式 —— 传递给getIcon()的可能的英寸值是java.beans.BeanInfo上的一个常量比如 BeanInfo.ICON_COLOR_16x16。另外尽管可以使用标准JDK ImageIO.read()装载图像但是Utilities.loadImage() 更理想,因为它具有更好的缓存行为而且支持商标图像。

  1. 如果现在运行代码您将会注意到图标只被应用到部分节点而没有应用到其他节点上。这是因为默认情况下为展开的Node和未展开的Node使用不同的图标。要避免这一点,只需覆盖另一个方法。

将以下方法添加到MyNode

public Image getOpenedIcon(int i) {
return getIcon (i);
}
  1. 现在如果运行代码所有节点将拥有同样的图标如下图所示。

操作和节点

将要处理的Node的另一个方面是操作。一个Node拥有一个弹出菜单,其中包含用户可以调用的针对此Node的操作。javax.swing.Action的任何子类都可由一个Node提供而且都会显示在弹出菜单中。此外,还涉及到一个呈现器 的概念,稍后将会讨论。

首先,创建一个可以用于节点的简单操作:

  1. MyNode getActions()方法重写为:
public Action[] getActions (boolean popup) {
return new Action[] { new MyAction() };
}
  1. 现在创建MyAction类作为MyNode的内部类
private class MyAction extends AbstractAction {
public MyAction () {
putValue (NAME, "Do Something");
    }
 
public void actionPerformed(ActionEvent e) {
APIObject obj = getLookup().lookup (APIObject.class);
JOptionPane.showMessageDialog(null, "Hello from " + obj);
    }
} 
  1. 再次运行套件并注意当右键单击一个节点时会显示一个菜单项

当选择菜单项时将调用该操作

呈现器

当然有时候想在弹出菜单中显示一个子菜单、复选框菜单项或者其他某个组件而不是一个JMenuItem。这很容易实现:

  1. 添加MyAction的签名以实现Presenter.Popup
private class MyAction extends AbstractAction implements Presenter.Popup {
  1. Ctrl-Shift-I修正导入的内容。
  2. 当在空白处出现灯泡状图案时将插入符号放在MyAction的类签名行中并按下Alt-Enter键,然后接受提示Implement All Abstract Methods
  3. 实现新创建的方法getPopupPresenter(),如下所示:
public JMenuItem getPopupPresenter() {
JMenu result = new JMenu("Submenu");  //remember JMenu is a subclass of JMenuItem
result.add (new JMenuItem(this));
result.add (new JMenuItem(this));
return result;
}
  1. 再次运行套件显示结果如下

结果太令人兴奋了 —— 现在有了一个叫做Submenu的子菜单它包含两个相同的菜单项。但是,您应该举一反三 —— 如果想返回一个JCheckBoxMenuItem或其他类型的菜单项,也可以使用此方法。

警告也可以使用Presenter.Menu来提供一个不同的组件以显示主菜单的其他任何操作但是 某些Macintosh Mac OS-X版本不能很好地处理嵌入到菜单项中的随机Swing组件。为安全起见,不要在主菜单中使用除JMenuJMenuItem子类以外的其他组件。

属性和属性表

本教程将要讨论的最后一个主题是属性。您可能知道NetBeans IDE包含一个“属性表”,这个表可以显示Node的“属性”。“属性”的确切含义依赖于实现Node的方式。属性其实就是拥有一个Java类型的名称-值对,这些名称-值对经过分组并在属性表中显示——其中可写的属性可以通过其属性编辑器 来编辑(参见java.beans.PropertyEditor,获得关于属性编辑器的大致信息)。

因此Node表达的其实是这样一种思想一个节点可以有多个属性可以在属性表上查看这些属性以及可选编辑这些属性。很容易实现这一点。在Nodes API中有一个很方便的类Sheet,它表示一个NodeNode的完整属性集。可以向其添加Sheet.Set实例该实例表示属性集”,属性集作为一组属性出现在属性表中。

  1. MyNode.createSheet()重写为:
protected Sheet createSheet() {
 
Sheet sheet = Sheet.createDefault();
Sheet.Set set = Sheet.createPropertiesSet();
APIObject obj = getLookup().lookup(APIObject.class);
 
try {
 
Property indexProp = new PropertySupport.Reflection(obj, Integer.class, "getIndex", null);
Property dateProp = new PropertySupport.Reflection(obj, Date.class, "getDate", null);
 
indexProp.setName("index");
dateProp.setName("date");
 
set.put(indexProp);
set.put(dateProp);
 
} catch (NoSuchMethodException ex) {
ErrorManager.getDefault();
    }
 
sheet.put(set);
return sheet;
 
}
  1. Ctrl-Shift-I修正导入的内容
  2. 右键单击模块套件并选择Run利用安装的套件模块启动NetBean的一个副本。
  3. 使用File > Open Editor显示编辑器。
  4. 选择Window > Properties显示NetBeans属性表。
  5. 单击编辑器窗口并在不同节点间移动选择注意属性表正在更新就像MyViewer组件所做的一样如下图所示

上面的代码利用了一个非常方便的类PropertySupport.Reflection可以通过此类传递一个对象、类型、gettersetter方法名称它将创建一个Property对象该对象可以读取(也可以写入当前对象的属性。因此我们使用PropertySupport.Reflection将一个Property对象连接到APIObjectgetIndex()方法。

如果希望将Property对象用于一个底层模型对象上几乎所有的getters/setters getter/setter方法