15.3 让数据在树中显示出来
本节实例的效果如图15.3所示。实现分以下几个步骤:

图15.3 树的效果图
15.3.1 创建树的数据结构
15.2节介绍了3个实体类的数据模型,接下来就要像第14章TableViewer的一样,用实体类来创建出各数据对象,并用集合List把国家、城市、人连接起来。和TableViewer一样,建立一个数据对象生成工厂来负责树的数据对象的创建。
public class DataFactory {
public static List<CountryEntity> createTreeData() {
// 生成国家的数据对象
CountryEntity cn = new CountryEntity("中国");
CountryEntity us = new CountryEntity("美国");
// 生成城市的数据对象
CityEntity city1 = new CityEntity("北京");
CityEntity city2 = new CityEntity("台湾");
CityEntity city3 = new CityEntity("桂林");
CityEntity city4 = new CityEntity("芝加哥");
CityEntity city5 = new CityEntity("纽约");
{// ----------往城市加人---------------------
// 北京
ArrayList<PeopleEntity> list = new ArrayList<PeopleEntity>();
list.add(new PeopleEntity("陈刚"));
list.add(new PeopleEntity("陈知行"));
list.add(new PeopleEntity("韩立新"));
city1.setChildren(list);
// 台湾
list = new ArrayList<PeopleEntity>();
list.add(new PeopleEntity("桃子"));
list.add(new PeopleEntity("林雅仕"));
list.add(new PeopleEntity("陈常恩"));
city2.setChildren(list);
// 纽约
list = new ArrayList<PeopleEntity>();
list.add(new PeopleEntity("Giles"));
list.add(new PeopleEntity("Tom"));
list.add(new PeopleEntity("Rose"));
city5.setChildren(list);
}
{// ---------城市和国家的关系------------------
// 北京、台湾、桂林属于中国
ArrayList<CityEntity> list = new ArrayList<CityEntity>();
list.add(city1);
list.add(city2);
list.add(city3);
cn.setChildren(list);
// 芝加哥、纽约属于美国
list = new ArrayList<CityEntity>();
list.add(city4);
list.add(city5);
us.setChildren(list);
}
{// 将所有国家放于一个集合中,也可以放到一个数组中
ArrayList<CountryEntity> list = new ArrayList<CountryEntity>();
list.add(cn);
list.add(us);
return list;
}
}
}
15.3.2 创建主程序
和TableViewer一样,TreeViewer也是用内容器和标签器来控制记录对象的显示,并且使用内容器和标签器的语句也一样。TreeViewer的主程序如下所示,它还是沿用了旧的SWT程序的代码框架,唯一的变化是单独封到了一个非静态方法中。
public class TreeViewer1 {
public static void main(String[] args) { new TreeViewer1().open(); }
public void open() {
final Display display = new Display();
final Shell shell = new Shell();
shell.setSize(200, 300);
// -----------界面核心代码-------------------
shell.setLayout(new FillLayout());
TreeViewer tv = new TreeViewer(shell, SWT.BORDER);
tv.setContentProvider(new TreeViewerContentProvider());
tv.setLabelProvider(new TreeViewerLableProvider());
// 和TableViewer一样,数据的入口也是setInput方法
List<CountryEntity> input = DataFactory.createTreeData();
tv.setInput(input);
// -----------END------------------
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}
15.3.3 标签器(实现ILabelProvider接口)
将标签器写成单独的外部类,以便于后面的实例共用,其代码如下。在getText方法中element的类型可能是国家、城市、人,由于它们都属于一个接口,所以getText的代码简洁不少。getImage()的实现参考TableViewer的标签器。
//标签器。控制结点在树中显示的文字和图像等
public class TreeViewerLableProvider implements ILabelProvider {
// 结点显示的文字。不能返回null值
public String getText(Object element) {
ITreeEntry entry = (ITreeEntry) element;
return entry.getName();
}
// 结点显示的图像,可以返回null值
public Image getImage(Object element) { return null; }
// --------以下方法暂不用,空实现----------
public void addListener(ILabelProviderListener listener) {}
public void removeListener(ILabelProviderListener listener) {}
public void dispose() {}
public boolean isLabelProperty(Object e, String p) { return false; }
}
15.3.4 内容器(实现ITreeContentProvider接口)
标签器还比较简单,在TreeViewer中最主要和最复杂的是内容器,熟悉内容器是掌握TreeViewer的要点。TreeViewer内容器的代码如下:
//内容器。由它决定哪些对象记录应该输出在TreeViewer里显示
public class TreeViewerContentProvider implements ITreeContentProvider {
// 由此方法决定树的“第一级”结点显示哪些对象。inputElement是用tv.setInput()方法
//输入的那个对象。Object[]是一个数组,数组中一个元素就是一个结点
public Object[] getElements(Object inputElement) {
if (inputElement instanceof List) {
List input = (List) inputElement;
return input.toArray();
}
return new Object[0]; // 空数组
}
// 判断参数element结点是否有子结点
// 返回true表示element有子结点,则其前面会显示有“+”号图标
public boolean hasChildren(Object element) {
ITreeEntry entry = (ITreeEntry) element;
List list = entry.getChildren();
return !(list == null || list.isEmpty()); // 判断list是否有子结点
}
// 当界面中单击某结点时,由此方法决定被单击结点应该显示哪些子结点
// parentElement就是被单击的结点对象。返回的数组就是应显示的子结点
public Object[] getChildren(Object parentElement) {
ITreeEntry entry = (ITreeEntry) parentElement;
List list = entry.getChildren();
//虽然通过界面单击方式,有子结点才会执行到此方法,但仍然要做非空判断,
//因为在调用TreeViewer的某些方法时其内部会附带调用此方法
if (list == null) return new Object[0];
return list.toArray();
}
// --------------以下方法暂时无用,空实现----------------
public void dispose() {}//树被销毁时触发
public void inputChanged(Viewer v,Object oldInput, Object newInput){} //每次tv.setInput触发
public Object getParent(Object element) {return null;} //取得element的父结点。极少使用
}
程序说明:在内容器中最关键的是getElements、hasChildren、getChildren这3个方法。
● getElements只在显示“第一级”结点时才会被执行。
● hasChildren主要用于判断当前所显示的结点是否有子结点,如果有子结点则前面显示一个“+”号图标,而有“+”号的结点则可以单击展开其下一级的子结点。
● 当单击有“子”的结点时,才会执行getChildren方法。展开其子结点后,又会对子结点执行一遍hasChildren方法,以决定其各子结点前是否显示“+”图标。
图15.4给出了内容器在启动、单击结点、关闭窗口这3种情况时的方法执行的时序图。

图15.4 内容器的方法的时序图
下面以本实例来解释此图:
● 树界面启动时:先执行inputChanged方法。接着执行getElements方法,其inputElement参数就是由setInput传入的对象:包含所有实体对象的List。此List在getElements中被转化成一个数组,数组包含第一级结点的两个元素中国和美国,它们将首先显示在界面上。接下来执行两次hasChildren方法,判断中国和美国是否有子结点。它们都有子结点,故方法返回True,两结点前都显示“+”图标。
● 单击有子结点的中国:先执行一次getChildren方法,方法的parentElement参数就是中国结点对象。方法中把中国的子结点取出并转化成一个数组返回,此数组包含3个元素“北京、台湾、桂林”。接下来连续执行3次hasChildren方法来判断“北京、台湾、桂林”是否有子结点,如果有,则在结点前显示一个“+”图标。
● 单击没有子结点的桂林:不会执行内容器中的任何方法。
● 关闭窗口:会先后执行inputChanged和dispose方法。






