软件开发

当前位置:首页 > 软件开发

《Java核心技术高级特性》(第六章)(续)

6.3.5定制树模型

在最后一.个示例中,我们实现了一个能够查看变量内容的程序,正如调试器所做的那样(参见图6-34 )。

在继续深入之前,请先编译运行这个示例程序。其中每个节点对应于-一个实例域。如果该域是-一个对象,那么可以展开该节点以便查看它自已的实例域。该程序可以观察框架窗口的内容。如果你浏览了好几个实例域,那么你将会发现--些熟悉的类,还会对复杂的Swing用户界面构件有所了解。

该程序的不同之处在于它的树并没有使用DefaultTreeMode1。如果你已经拥有按照层次结构组织的数据,那么你可能并不想花精力去再创建-.棵副本树,而且创建副本树还要担心怎样保持两棵树的一致性。 这正是我们要讨论的情形:通过对象的引用,要观察的对象已经彼此连接起来了,因此在这里就不需要复制这种连接结构了。

新梦想IT职业教育

TreeMode1接口只有几个方法。第一组方法使得JTree能够按照先是根节点,后是子节点的顺序找到树中的节点。JTree类只在用户真正展开-一个节点的时候才会调用这些方法。

0bject getRoot(0
int getChildCount(Object parent)
0bject getChild(Object parent, int index)

这个示例显示了为什么TreeMode1接口像JTree类那样,不需要用于描述节点的显式概念。根节点和子节点可以是任何对象,TreeMode1 负责告知JTree它们是怎样联系起来的。

TreeMode1接口的下-个方法与getChild相反:

int getIndex0fChild(Object parent, 0bject child)

实际上,这个方法可以用前面的三个方法实现,参见程序清单6-16中的代码。树模型会告诉JTree哪些节点应该显示成叶节点:

boolean isleaf(0bject node)

如果你的代码更改了树模型,那么必须告知这棵树以便它能够对自己进行重新绘制。树是将它自己作为- -个TreeModelListener添加到模型中的,因此,模型必须支持通常的监听器管理方法:

void adTreedelLitener(reeModelListener 1)
void reoveTeeodelListener(TeeMdelListener 1)

可以在程序清单6-17中看到这些方法的具体实现。

当模型修改了树的内容时,它会调用TreModelListener接口中下面4个方法中的某一个:

void treelodesChangedr(reeModelEvent e)
void treeNodesInserted(TreeMode1Event e)
void treeodesenovedr(reeModelEvent e)
void treeStructureChanged(TreeModelEvent e)

TreeModelEvent对象用于描述修改的位置。对描述插入或移除事件的树模型事件进行组装的细节是相当技术性的。如果树中确实有要添加或移除的节点,只需要考虑如何触发这些事件。在程序清单6-16中,我们展示了怎么触发一-个事件: 将根节点替换为一-个新的对象。

T提示:为了简化事件触发的代码,我们使用了javax. swing. EventListenerList这个使用方便、能够收集监听器的类。程序清单6-17中最后3个方法展示了如何使用这个类。

最后,如果用户要编辑树节点,那么模型会随着这种修改而被调用:

void valueForPathangedT(reePath path, 0bject newValue)

如果不允许编辑,则永远不会调用到该方法。

如果不支持编辑功能,那么构建-一个树模型就变得相当容易了。我们要实现下面3个方法: 

0bject getRoot(O
int getChildCount (0bject parent)
object getChild(Object parent, int index)

这3个方法用于描述树的结构。还要提供另外5个方法的常规实现,如程序清单6-16那样,然后就可以准备显示你的树了。

现在让我们转向示例程序的具体实现,我们的树将包含类型为Variable的对象。

口注意:一旦使用了DefaultTreeModel,我们的节点就可以具有类型为DefaultMutableTree-Node、用户对象类型为Variable的对象。

例如,假设我们查看下面这个变量

Employee joe;

该变量的类型为Emp1oyee.class,名字为joe,值为对象引用joe的值。在程序清单6-18中,我们定义了Variable这个类,用来描述程序中的变量:

Variable v■new vrilbe(mlye.cass, "joe", joe);

如果该变量的类型为基本类型,必须为这个值使用对象包装器。

new Vari able(double.class, "salary", new Double(salar));

如果变量的类型是- -个类,那么该变量就会拥有一些域。使用反射机制可以将所有域枚举出来,并将它们收集存放到一个ArrayList中。因为Class类的getFields方法不返回超类的任何域,因此还必须调用超类中的getFields方法,你可以在Variable构造器中找到这些代码。Variab1e 类的getFields方法将返回包含域的-一个数组。最后,Variab1e类的toString方法将节点格式化为标签,这个标签通常包含变量的类型和名称。如果变量不是一个类,那么该标签还将包含变量的值。

口注意:如果类型是一个数组,那么我们不会显示数组中的元素。这并不难实现,因此我们就把它留作众所周知的“读者练习”了。

让我们继续介绍树模型,头两个方法很简单。

public 0bject getRootO
return root;
public int getChildCount(0bject parent)
retum ((Variable) parent).getFields0.size0;

getChild方法返回一个新的Variab1e对象,用于描述给定索引位置上的域。Field类的getType方法和getName方法用于产生域的类型和名称。通过使用反射机制,你可以按照f.get( parentvalue)这种方式读取域的值。该方法可以抛出一个异常I1ega1AccessException,不过,我们可以让所有域在Vari ab1e构造器中都是可访问的,这样,在实际应用中,就不会发生这种抛异常的情况。

下面是getChi1d方法的完整代码。

public 0bject getChild(0bject parent, int index)
Arraylist fields = ((Variable) parent) .getFields0;
Field f = (Field) fields.get(index);
0bject parentValue = (Variable) parent) .getValue0;
try
{
return new Variable(f.getTypeO, f.getNaneO, f.getparentValu));
}
catch 1legalccessException e)
retur null;
}

这3个方法展示了对象树到JTree构件之间的结构,其余的方法是- -些常规方法,源代码请见程序清单6-17。

关于该树模型,有一个不同寻常之处:它实际上描述的是一棵无限树。可以通过追踪WeakReference对象来证实这一一点。当你点击名字为referent的变量时,它会引导你回到初始的对象。你将获得- -棵相同的子树,并且可以再次展开它的WeakReference对象,周而复始,无穷无尽。当然,你无法存储-一个无限的节点集合。树模型只是在用户展开父节点时,按照需要来产生这些节点。


相关内容

文章评论

表情

共 0 条评论,查看全部
  • 这篇文章还没有收到评论,赶紧来抢沙发吧~