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(Listchildren);20 public List 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 Listpeoples;//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 @Override32 public void setChildren(List children) {33 //这个地方我犯了一错误,以前写习惯了,this.name = name34 //所以一开始这个地方我写的是this.peoples = peoples 35 //在Country实体类中也是这个错误,所以运行出来的程序只有一列国家名,国家下面的城市都没有了....36 this.peoples = children;37 }38 39 @Override40 public List 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 Listcities; //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 @Override33 public void setChildren(List children) {34 this.cities = children;35 }36 37 @Override38 public List 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 @Override46 public void setChildren(Listchildren) {47 }48 @Override49 public List 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 * @return15 */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 ArrayListlist1 = new ArrayList ();46 list1.add(people1);47 list1.add(people2);48 list1.add(people3);49 city1.setChildren(list1);50 51 //张D 张E 张F在广州 52 ArrayList list2 = new ArrayList ();53 list2.add(people4);54 list2.add(people5);55 list2.add(people6);56 city2.setChildren(list2); 57 58 //张G 在东京59 ArrayList list3 = new ArrayList ();60 list3.add(people7);61 city3.setChildren(list3); 62 63 //张I 在芝加哥64 ArrayList list4 = new ArrayList ();65 list4.add(people8);66 city4.setChildren(list4); 67 68 //张H 在洛杉矶69 ArrayList list5 = new ArrayList ();70 list5.add(people9);71 city5.setChildren(list5); 72 73 //2.城市和国家的关系74 //北京 上海 广州是中国的75 ArrayList list6 = new ArrayList ();76 list6.add(city1);77 list6.add(city2);78 country1.setChildren(list6);79 80 //东京是日本的81 ArrayList list7 = new ArrayList ();82 list7.add(city3);83 country2.setChildren(list7);84 85 //芝加哥和洛杉矶是美国的86 ArrayList list8 = new ArrayList ();87 list8.add(city4);88 list8.add(city5);89 country3.setChildren(list8);90 91 //3.将国家置于一个对象之下,这个对象可以是一个List也可以是个数组92 //国家是这个树上的最上层的节点.国家和国家之间是并列的关系,把这几个国家放到一个List集合对象中.93 ArrayList list9 = new ArrayList ();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 @Override14 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 @Override31 public boolean hasChildren(Object element) {32 ITreeEntry entry = (ITreeEntry) element;33 Listlist = 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 @Override49 public Object[] getChildren(Object parentElement) {50 ITreeEntry entry = (ITreeEntry) parentElement;51 List 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 @Override61 public Object getParent(Object element) {62 return null;63 }64 65 @Override66 public void dispose() {67 }68 69 @Override70 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 @Override11 public String getText(Object element) {12 //得到这个节点对应的名字,首先对element进行强制类型转换13 ITreeEntry entry = (ITreeEntry) element;14 return entry.getName();15 }16 17 /**18 * 记录显示的图像,可以返回NULL值19 */ 20 @Override21 public Image getImage(Object element) {22 return null;23 }24 25 //>>>>>>>>>>>>>>>>>>>>>>书上说一下几个方法没有用,空实现就可以了.>>>>>>>>>>>>>>>>>>>>>>>>> 26 @Override27 public void addListener(ILabelProviderListener listener) {28 // TODO Auto-generated method stub29 30 }31 32 @Override33 public void removeListener(ILabelProviderListener listener) {34 // TODO Auto-generated method stub35 36 }37 @Override38 public void dispose() {39 // TODO Auto-generated method stub40 41 }42 @Override43 public boolean isLabelProperty(Object element, String property) {44 // TODO Auto-generated method stub45 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 @Override109 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 @Override141 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 @Override158 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的布局为FillLayout16 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方法修改如下: