TreeViewer和TableViewer在使用上还是有很多相似之处.TreeViewer中冶有TableViewer中的过滤器和排序器.具体使用看TableViewer中的使用.
和Table有JFace的扩展TableViewer一样,Tree也有一个JFace中的扩展,那就是TreeViewer.因为TreeViewer和TableViewer继承自同一个父类StructuredViewer所以两者有很多方法的使用是一样的.例如:都使用setInput方法输入数据,都有内容器,标签器,以及排序器,过滤器等.
建立一个树节点的接口类:
树节点由两个基本特征,名称和子节点.这里把这两个特征抽象出来写成一个接口,然后将要做树节点的实体类实现此接口.
主义这个接口不是必须的.仅仅是为了今后操作方便,以及规范化涉及才建立的.
但是每个实体对应的都是树上的一个节点.这里定义一个树中节点的通用接口.
然后每个实体类都实现这个接口.
接着建立几个实体类:
ITreeEntry.java
1 /** 2 * 树上的每个一个节点都对应的是一个实体,这个实体是树上的一个节点. 3 * 首先在定义实体类(People City Country)之前要先定义这个接口ITreeEntry 4 * 树的节点接口 5 * @author kongxiaohan 6 */ 7 public interface ITreeEntry { 8 /* 9 * 设置dedao 树节点的名称 10 * 只声明抽象方法,不声明字段名 11 */ 12 public String getName(); 13 public void setName(String name); 14 15 16 /* 17 * 设置与得到子节点集合. 18 */ 19 public void setChildren(List<ITreeEntry> children); 20 public List<ITreeEntry> getChildren(); 21 }
City.java
1 /** 2 * City城市实体类 3 * @author kongxiaohan 4 * 5 */ 6 public class City implements ITreeEntry{ 7 private Long id; // 唯一识别码,在数据库里常为自动递增的ID列 8 private String name;// 城市名 9 10 private List<ITreeEntry> peoples;//City实体的子节点 城市中的人,装在一个List集合中 11 12 public City(String name) { 13 super(); 14 this.name = name; 15 } 16 17 public Long getId() { 18 return id; 19 } 20 public void setId(Long id) { 21 this.id = id; 22 } 23 public String getName() { 24 return name; 25 } 26 public void setName(String name) { 27 this.name = name; 28 } 29 30 //这个地方是设置子节点....当前是City实体,其子节点是People实体 31 @Override 32 public void setChildren(List<ITreeEntry> children) { 33 //这个地方我犯了一错误,以前写习惯了,this.name = name 34 //所以一开始这个地方我写的是this.peoples = peoples 35 //在Country实体类中也是这个错误,所以运行出来的程序只有一列国家名,国家下面的城市都没有了.... 36 this.peoples = children; 37 } 38 39 @Override 40 public List<ITreeEntry> getChildren() { 41 return peoples; 42 } 43 }
Country.java
1 /** 2 * Country国家实体类 3 * @author kongxiaohan 4 */ 5 public class Country implements ITreeEntry { 6 private Long id; // 唯一识别码,在数据库里常为自动递增的ID列 7 private String name; // 国家名 8 9 private List<ITreeEntry> cities; //Country实体的子节点是城市 City 此国家所包含的的城市的集合,集合元素为City对象 10 11 public Country() { 12 } 13 14 public Country(String name) { 15 super(); 16 this.name = name; 17 } 18 19 public Long getId() { 20 return id; 21 } 22 public void setId(Long id) { 23 this.id = id; 24 } 25 public String getName() { 26 return name; 27 } 28 public void setName(String name) { 29 this.name = name; 30 } 31 32 @Override 33 public void setChildren(List<ITreeEntry> children) { 34 this.cities = children; 35 } 36 37 @Override 38 public List<ITreeEntry> getChildren() { 39 return cities; 40 } 41 }
People.java
1 public class People implements ITreeEntry { 2 private Long id; // 唯一识别码,在数据库里常为自动递增的ID列 3 private String name; // 姓名 4 private boolean sex; // 性别 true男,flase女 5 private int age; // 年龄 6 private Date createDate; // 记录的建立日期,是java.util.Date,而不是java.sql.Date 7 8 public People(String name) { 9 super(); 10 this.name = name; 11 } 12 public Long getId() { 13 return id; 14 } 15 public void setId(Long id) { 16 this.id = id; 17 } 18 public String getName() { 19 return name; 20 } 21 public void setName(String name) { 22 this.name = name; 23 } 24 public boolean isSex() { 25 return sex; 26 } 27 public void setSex(boolean sex) { 28 this.sex = sex; 29 } 30 public int getAge() { 31 return age; 32 } 33 public void setAge(int age) { 34 this.age = age; 35 } 36 public Date getCreateDate() { 37 return createDate; 38 } 39 public void setCreateDate(Date createDate) { 40 this.createDate = createDate; 41 } 42 43 44 //因为人是这个树的最小子节点,所以这个地方空实现接口中的这两个方法就可以了 45 @Override 46 public void setChildren(List<ITreeEntry> children) { 47 } 48 @Override 49 public List<ITreeEntry> getChildren() { 50 return null; 51 } 52 }
制造各个实体类的工具类
DataFactory.java
1 /** 2 * 此类负责生成TreeViewer的方法setInput所需要的参数. 3 * @author kongxiaohan 4 * 5 * 这个地方我没有用书中用的局部代码块,在组织代码的时候确实犯了一些小错误, 6 * 确实体会到了局部代码块的意义,代码读起来容易,而且节省了内存. 7 */ 8 public class DataFactory { 9 /** 10 * 在这个方法中定义生成的数据 11 * 要有人People的数据对象 12 * 要有国家Country的数据对象 13 * 要有城市City的数据对象 14 * @return 15 */ 16 public static Object createTreeData(){ 17 //生成人People的数据对象 18 People people1 = new People("张A"); 19 People people2 = new People("张B"); 20 People people3 = new People("张C"); 21 People people4 = new People("张D"); 22 People people5 = new People("张E"); 23 People people6 = new People("张F"); 24 People people7 = new People("张G"); 25 People people8 = new People("张H"); 26 People people9 = new People("张I"); 27 28 //生成城市City的数据对象 29 City city1 = new City("北京"); 30 City city2 = new City("广州"); 31 City city3 = new City("东京"); 32 City city4 = new City("芝加哥"); 33 City city5 = new City("洛杉矶"); 34 35 //生成国家Country的的数据对象 36 Country country1 = new Country("中国"); 37 Country country2 = new Country("日本"); 38 Country country3 = new Country("美国"); 39 40 /** 41 * 将这些封装的对象建立关系. 42 */ 43 //1.人和城市的关系 44 //张A 张B 张C 在帝都 45 ArrayList<ITreeEntry> list1 = new ArrayList<ITreeEntry>(); 46 list1.add(people1); 47 list1.add(people2); 48 list1.add(people3); 49 city1.setChildren(list1); 50 51 //张D 张E 张F在广州 52 ArrayList<ITreeEntry> list2 = new ArrayList<ITreeEntry>(); 53 list2.add(people4); 54 list2.add(people5); 55 list2.add(people6); 56 city2.setChildren(list2); 57 58 //张G 在东京 59 ArrayList<ITreeEntry> list3 = new ArrayList<ITreeEntry>(); 60 list3.add(people7); 61 city3.setChildren(list3); 62 63 //张I 在芝加哥 64 ArrayList<ITreeEntry> list4 = new ArrayList<ITreeEntry>(); 65 list4.add(people8); 66 city4.setChildren(list4); 67 68 //张H 在洛杉矶 69 ArrayList<ITreeEntry> list5 = new ArrayList<ITreeEntry>(); 70 list5.add(people9); 71 city5.setChildren(list5); 72 73 //2.城市和国家的关系 74 //北京 上海 广州是中国的 75 ArrayList<ITreeEntry> list6 = new ArrayList<ITreeEntry>(); 76 list6.add(city1); 77 list6.add(city2); 78 country1.setChildren(list6); 79 80 //东京是日本的 81 ArrayList<ITreeEntry> list7 = new ArrayList<ITreeEntry>(); 82 list7.add(city3); 83 country2.setChildren(list7); 84 85 //芝加哥和洛杉矶是美国的 86 ArrayList<ITreeEntry> list8 = new ArrayList<ITreeEntry>(); 87 list8.add(city4); 88 list8.add(city5); 89 country3.setChildren(list8); 90 91 //3.将国家置于一个对象之下,这个对象可以是一个List也可以是个数组 92 //国家是这个树上的最上层的节点.国家和国家之间是并列的关系,把这几个国家放到一个List集合对象中. 93 ArrayList<ITreeEntry> list9 = new ArrayList<ITreeEntry>(); 94 list9.add(country1); 95 list9.add(country2); 96 list9.add(country3); 97 return list9; 98 } 99 }
内容器:TreeViewerContentProvider.java
标签器还比较简单,在TreeViewer中最主要和最复杂的是内容器,熟悉内容器是掌握TreeViewer的要点.
1 /** 2 * "内容器" 由它决定哪些对象记录应该输出在TreeViewer里显示. 3 * @author kongxiaohan 4 * 5 */ 6 public class TreeViewerContentProvider implements ITreeContentProvider{ 7 8 /** 9 * 这个方法决定树的一级目录显示哪些对象 10 * @param inputElement 是用tv.setInput()方法输入的那个对象 11 * @return Object[]一个数组,数组中一个元素就是一个结点 12 */ 13 @Override 14 public Object[] getElements(Object inputElement) { 15 if (inputElement instanceof List) { 16 List list = (List) inputElement; 17 return list.toArray(); 18 } else { 19 return new Object[0]; // 生成一个空数组 20 } 21 } 22 23 24 /** 25 * 判断某结点是否有子结点。如果有子结点,这时结点前都有一个“+”号图标 26 * 27 * @param element 需要判断是否有子的结点 28 * @return true有子结点,false无子结点 29 */ 30 @Override 31 public boolean hasChildren(Object element) { 32 ITreeEntry entry = (ITreeEntry) element; 33 List<ITreeEntry> list = entry.getChildren(); 34 if (list == null || list.isEmpty()) { 35 return false; 36 } else { 37 return true; 38 } 39 } 40 41 42 /** 43 * 由这个方法决定父结点应该显示那些子结点。 44 * 45 * @param parentElement 当前被点击的结点对象 46 * @return 由子结点做为元素的数组 47 */ 48 @Override 49 public Object[] getChildren(Object parentElement) { 50 ITreeEntry entry = (ITreeEntry) parentElement; 51 List<ITreeEntry> list = entry.getChildren(); 52 if (list == null || list.isEmpty()) { 53 return new Object[0]; 54 } else { 55 return list.toArray(); 56 } 57 } 58 59 //>>>>>>>>>>>>>>>>>>>>>>>>>书上说以下三个方法是没有用的,空实现就哦了>>>>>>>>>>>>>>>>>>>>>> 60 @Override 61 public Object getParent(Object element) { 62 return null; 63 } 64 65 @Override 66 public void dispose() { 67 } 68 69 @Override 70 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 71 } 72 }
程序说明:在内容器中最关键的是getElements,hasChildren,getChildren这三个方法.
1.getElements自在显示"第一级"结点的时候才会被执行.
2.hasChildren主要用于判断当前说系那是的节点是否有子节点,如果有子节点则前面显示一个"+"号图标,而有"+"号的结点则可以单击展开其下一级的子节点.
3.当单击右"子"的结点的时候,才会执行getChildren方法.展开其子节点后,又会对子节点执行一遍hasChildren方法,以决定其各子结点前是否显示"+"图标.
下面是"内容器"在启动,单击,关闭窗口时执行的过程.
下面以本例来解释这个图:
1.树界面启动的时候 ,先执行inputChanged()方法,接着执行getElements方法,其inputElement参数就是由setInput传入的对象:包含所有实体对象的List,此List在getElements中被转化成一个数组,数组包含第一级结点的两个元素,中国,美国,日本,他们将首先显示在页面上.接下来执行三次hasChildren方法,判断中国,日本和美国是否有子节点.他们都有子节点.,所以方法返回True,两结点前都显示"+"图标.
2.单击右子节点的中国:先执行一次getChildren方法,方法的parentElement参数就是中国结点对象.方法中把中国的子节点取出转换陈给一个数组返回,此数组包含3个元素"北京,上海,济南".接下来连续执行3次hasChildren方法来判断"北京,上海,济南"是否有子节点,如果有在结点的前面显示一个"+"图标.
3.单击没有子节点的结点:不会有执行内容器中的任何方法.
4.关闭窗口:会先后执行inputChanged和dispose方法.
标签器TreeViewerLableProvider.java
将标签器写成单独的外部类,以便于后面的实例共用,其代码如下,在getText方法中element的类型可以是国家,城市,人,由于他们都是属于一个接口,所以getText的代码简洁不少.getImage()的实现参考TableViewer的标签器.
1 /** 2 * "标签器" 控制记录在树中显示的文字和图像等. 3 * @author kongxiaohan 4 */ 5 public class TreeViewerLableProvider implements ILabelProvider { 6 7 /** 8 * 记录显示的文字。不能返回NULL值 9 */ 10 @Override 11 public String getText(Object element) { 12 //得到这个节点对应的名字,首先对element进行强制类型转换 13 ITreeEntry entry = (ITreeEntry) element; 14 return entry.getName(); 15 } 16 17 /** 18 * 记录显示的图像,可以返回NULL值 19 */ 20 @Override 21 public Image getImage(Object element) { 22 return null; 23 } 24 25 //>>>>>>>>>>>>>>>>>>>>>>书上说一下几个方法没有用,空实现就可以了.>>>>>>>>>>>>>>>>>>>>>>>>> 26 @Override 27 public void addListener(ILabelProviderListener listener) { 28 // TODO Auto-generated method stub 29 30 } 31 32 @Override 33 public void removeListener(ILabelProviderListener listener) { 34 // TODO Auto-generated method stub 35 36 } 37 @Override 38 public void dispose() { 39 // TODO Auto-generated method stub 40 41 } 42 @Override 43 public boolean isLabelProperty(Object element, String property) { 44 // TODO Auto-generated method stub 45 return false; 46 } 47 }
给TreeViewer加上右键菜单的方法和TableViewer相似,也要用到Action,ActionGroup,MenuManager类.当然程序要根据树的特点稍作改动.
MyActionGroup内含有各Action类,在实现Action时,对传入的结点对象要记得进行空值判断.因为TreeViewer的大部分方法都不支持空值参数.会导致异常并中断程序.
MyActionGroup.java
1 public class MyActionGroup extends ActionGroup { 2 //ActionGroup这是个抽象类,但是其中并没有抽象方法. 3 private TreeViewer treeViewer; 4 5 //构造方法 6 public MyActionGroup(TreeViewer treeViewer) { 7 this.treeViewer = treeViewer; 8 } 9 10 /** 11 * 生成菜单Menu,并将两个Action传入 12 */ 13 public void fillContextMenu(IMenuManager mgr) { 14 /* 15 * 加入两个Action对象到菜单管理器 16 */ 17 MenuManager menuManager = (MenuManager) mgr; // 把接口类转换成其实现类. 18 //用MenuManager管理Action 19 menuManager.add(new OpenAction()); 20 menuManager.add(new RefreshAction()); 21 menuManager.add(new ExpandAction()); 22 menuManager.add(new CollapseAction()); 23 menuManager.add(new AddEntryAction()); 24 menuManager.add(new RemoveEntryAction()); 25 menuManager.add(new ModifyEntryAction()); 26 /* 27 * 把这些功能加入到右键的Menu菜单中. 28 */ 29 Tree tree = treeViewer.getTree(); 30 Menu menu = menuManager.createContextMenu(tree); 31 tree.setMenu(menu); 32 } 33 34 /** 35 * 打开"菜单"对应的Action类 36 */ 37 private class OpenAction extends Action { 38 public OpenAction() { 39 setText("打开"); 40 } 41 /** 42 * 继承自Action的方法,动作代码写此方法中 43 */ 44 //覆盖Action中的run()方法. 45 public void run() { 46 ITreeEntry obj = getSelTreeEntry();//getgetSelTreeEntry()得到当前节点 47 if (obj != null) { 48 //弹出一个这个节点名字的对话框. 49 MessageDialog.openInformation(null, null, obj.getName()); 50 } 51 } 52 } 53 54 /** 55 * 刷新对应的Action类. 56 */ 57 private class RefreshAction extends Action { 58 public RefreshAction() { 59 setText("刷新"); 60 } 61 62 //覆盖Action类中的run()方法,里面是调用的refresh()方法. 63 public void run() { 64 treeViewer.refresh();// 调用TreeViewer的刷新方法 65 } 66 } 67 68 /** 69 * 展开当前结点的Action类 70 */ 71 private class ExpandAction extends Action { 72 public ExpandAction() { 73 setText("展开"); 74 } 75 //重写run()方法 76 public void run() { 77 ITreeEntry obj = getSelTreeEntry(); 78 if (obj != null) { 79 treeViewer.expandToLevel(obj, 1); 80 // 这个方法后面的数字是展开的层级数.这个地方设置成仅仅展开1个层级. 81 } 82 } 83 } 84 85 /** 86 * 收缩当前结点的Action类 87 */ 88 private class CollapseAction extends Action { 89 public CollapseAction() { 90 setText("收缩"); 91 } 92 //重写run()方法 93 public void run() { 94 ITreeEntry obj = getSelTreeEntry(); 95 if (obj != null) { 96 treeViewer.collapseToLevel(obj, -1); // -1为将当前结点的所有子结点收缩 97 } 98 } 99 } 100 101 /** 102 * 给当前结点增加一个子结点的Action类 103 */ 104 private class AddEntryAction extends Action { 105 public AddEntryAction() { 106 setText("增加"); 107 } 108 @Override 109 public void run() { 110 ITreeEntry obj = getSelTreeEntry(); 111 if (obj == null || obj instanceof People) { 112 return; 113 } 114 InputDialog dialog = new InputDialog(null, "给当前结点增加一个子结点", "输入名称", "请输入你要增加的节点的名字", null); 115 if (dialog.open() == InputDialog.OK) {// 如果单击OK按钮 116 String entryName = dialog.getValue(); // 得到Dialog输入值 117 /* 根据单击结点的不同类型生成相应的子结点 */ 118 ITreeEntry newEntry = null; 119 if (obj instanceof Country) { 120 newEntry = new City(entryName); 121 } else if (obj instanceof City) { 122 newEntry = new People(entryName); 123 } 124 /* 在增加子结点之前将父结点展开 */ 125 if (!treeViewer.getExpandedState(obj)) { 126 treeViewer.expandToLevel(obj, 1); 127 } 128 treeViewer.add(obj, newEntry);// 增加结点 129 } 130 } 131 } 132 133 /** 134 * 删除结点的Action类 135 */ 136 private class RemoveEntryAction extends Action { 137 public RemoveEntryAction() { 138 setText("删除"); 139 } 140 @Override 141 public void run() { 142 ITreeEntry obj = getSelTreeEntry(); 143 if (obj == null) { 144 return; 145 } 146 treeViewer.remove(obj); 147 } 148 } 149 150 /** 151 * 修改结点名称的Action类 152 */ 153 private class ModifyEntryAction extends Action { 154 public ModifyEntryAction() { 155 setText("修改"); 156 } 157 @Override 158 public void run() { 159 ITreeEntry obj = getSelTreeEntry(); 160 if (obj == null) { 161 return; 162 } 163 InputDialog dialog = new InputDialog(null, "修改结点", "输入新名称", obj.getName(), null); 164 if (dialog.open() == InputDialog.OK) { 165 String entryName = dialog.getValue();//得到对话框中的值. 166 obj.setName(entryName);//给这个节点设置成得到的对话框中的名字. 167 treeViewer.refresh(obj); // 刷新结点 168 } 169 } 170 } 171 /** 172 * 这个方法是自定义的方法,这个方法的作用就是得到当前选择的节点. 173 */ 174 private ITreeEntry getSelTreeEntry() { 175 IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection(); 176 ITreeEntry treeEntry = (ITreeEntry) (selection.getFirstElement()); 177 return treeEntry; 178 } 179 }
TreeViewer.java的实例
1 public class TreeViewer1 { 2 public static void main(String[] args) { 3 TreeViewer1 window = new TreeViewer1(); 4 window.open(); 5 } 6 7 //定义这个open()方法.就是创建一个典型的SWT程序的步骤 8 public void open(){ 9 //1.Display负责管理一实现循环和控制UI线程和其他线程之间的通信 10 final Display display = new Display(); 11 //2.创建一个或者多个Shell(shell是程序的主窗口) 12 final Shell shell = new Shell(); 13 //3.设置shell的布局. 14 shell.setSize(200, 300); 15 //设置shell的布局为FillLayout 16 shell.setLayout(new FillLayout()); 17 shell.setText("TreeViewer的第一个例子"); 18 19 20 Composite c = new Composite(shell, SWT.NONE); 21 c.setLayout(new FillLayout()); 22 TreeViewer treeViewer = new TreeViewer(c, SWT.BORDER); 23 treeViewer.setContentProvider(new TreeViewerContentProvider()); 24 treeViewer.setLabelProvider(new TreeViewerLableProvider()); 25 // 和TableViewer一样,数据的入口也是setInput方法 26 Object inputObj = DataFactory.createTreeData(); 27 treeViewer.setInput(inputObj); 28 29 /* 30 //调用自定义的方法创建表格 31 createTableViewer(shell); 32 //4.设定内容器 33 tableviewer.setContentProvider(new TableViewerContentProvider()); 34 //5.设定标签器 35 tableviewer.setLabelProvider(new TableViewerLabelProvider()); 36 //6.用setInput输入数据(把PeopleFactory产生的List集合传进来) 37 tableviewer.setInput(PeopleFactory.getPeoples()); 38 */ 39 40 //7.创建Shell中的组件(这个例子中没有加入组件,只有一个空窗口) 41 shell.open(); 42 //8.写一个时间转发循环 43 while(!shell.isDisposed()){//如果主窗口没有关闭,则一直循环 44 //dispose 是"处理,处置,毁掉"的意思 45 if(!display.readAndDispatch()){//// 如果display不忙 46 display.sleep();// display休眠 47 } 48 } 49 } 50 }
TreeViewer1.java的运行结果图
TreeViewer2.java
1 public class TreeViewer2 { 2 3 public static void main(String[] args) { 4 TreeViewer2 window = new TreeViewer2(); 5 window.open(); 6 } 7 8 public void open() { 9 final Display display = new Display(); 10 final Shell shell = new Shell(); 11 shell.setSize(200, 300); 12 13 shell.setLayout(new FillLayout()); 14 Composite c = new Composite(shell, SWT.NONE); 15 c.setLayout(new FillLayout()); 16 TreeViewer treeViewer = new TreeViewer(c, SWT.BORDER); 17 treeViewer.setContentProvider(new TreeViewerContentProvider()); 18 treeViewer.setLabelProvider(new TreeViewerLableProvider()); 19 Object inputObj = DataFactory.createTreeData(); 20 //>>>>>>>>>>>>>相比于TreeViewer1.java增加的>>>>>>>>>>>>>>>>>>>>>>>>> 21 //生成一个ActionGroup对象 22 MyActionGroup actionGroup = new MyActionGroup(treeViewer); 23 // 调用fillContextMenu方法将按钮注入到菜单对象中 24 actionGroup.fillContextMenu(new MenuManager()); 25 // --------------加入代码:END-------------------- 26 treeViewer.setInput(inputObj); 27 // ----------------------------- 28 shell.open(); 29 while (!shell.isDisposed()) { 30 if (!display.readAndDispatch()) { 31 display.sleep(); 32 } 33 } 34 } 35 }
TreeViewer2.java的运行结果图:
上面的这个是<<Eclispe从入门到精通>>中第一版的代码,在第二版中对一些功能进行了一些改进.
该进在于"不同的结点显示不同的菜单".
树的"人"结点是没有子节点的.因此对于"人"这个节点来说,右键菜单中的"展开,收缩,增加"菜单项都没有任何意义,应该隐藏起来,再根据当前节点的类型决定要将哪些Action加入菜单中.按着这个思路将MyActionGroup类的fillContextMenu方法修改如下: