TreeViewer和TableViewer在使用上还是有很多相似之处.TreeViewer中冶有TableViewer中的过滤器和排序器.具体使用看TableViewer中的使用.
和Table有JFace的扩展TableViewer一样,Tree也有一个JFace中的扩展,那就是TreeViewer.因为TreeViewer和TableViewer继承自同一个父类StructuredViewer所以两者有很多方法的使用是一样的.例如:都使用setInput方法输入数据,都有内容器,标签器,以及排序器,过滤器等.
建立一个树节点的接口类:
树节点由两个基本特征,名称和子节点.这里把这两个特征抽象出来写成一个接口,然后将要做树节点的实体类实现此接口.
主义这个接口不是必须的.仅仅是为了今后操作方便,以及规范化涉及才建立的.
但是每个实体对应的都是树上的一个节点.这里定义一个树中节点的通用接口.
然后每个实体类都实现这个接口.
接着建立几个实体类:
ITreeEntry.java
/**
* 树上的每个一个节点都对应的是一个实体,这个实体是树上的一个节点.
* 首先在定义实体类(People City Country)之前要先定义这个接口ITreeEntry
* 树的节点接口
* @author kongxiaohan
*/
public interface ITreeEntry {
/*
* 设置dedao 树节点的名称
* 只声明抽象方法,不声明字段名
*/
public String getName();
public void setName(String name); /*
* 设置与得到子节点集合.
*/
public void setChildren(List<ITreeEntry> children);
public List<ITreeEntry> getChildren();
}
City.java
/**
* City城市实体类
* @author kongxiaohan
*
*/
public class City implements ITreeEntry{
private Long id; // 唯一识别码,在数据库里常为自动递增的ID列
private String name;// 城市名 private List<ITreeEntry> peoples;//City实体的子节点 城市中的人,装在一个List集合中 public City(String name) {
super();
this.name = name;
} public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
} //这个地方是设置子节点....当前是City实体,其子节点是People实体
@Override
public void setChildren(List<ITreeEntry> children) {
//这个地方我犯了一错误,以前写习惯了,this.name = name
//所以一开始这个地方我写的是this.peoples = peoples
//在Country实体类中也是这个错误,所以运行出来的程序只有一列国家名,国家下面的城市都没有了....
this.peoples = children;
} @Override
public List<ITreeEntry> getChildren() {
return peoples;
}
}
Country.java
/**
* Country国家实体类
* @author kongxiaohan
*/
public class Country implements ITreeEntry {
private Long id; // 唯一识别码,在数据库里常为自动递增的ID列
private String name; // 国家名 private List<ITreeEntry> cities; //Country实体的子节点是城市 City 此国家所包含的的城市的集合,集合元素为City对象 public Country() {
} public Country(String name) {
super();
this.name = name;
} public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
} @Override
public void setChildren(List<ITreeEntry> children) {
this.cities = children;
} @Override
public List<ITreeEntry> getChildren() {
return cities;
}
}
People.java
public class People implements ITreeEntry {
private Long id; // 唯一识别码,在数据库里常为自动递增的ID列
private String name; // 姓名
private boolean sex; // 性别 true男,flase女
private int age; // 年龄
private Date createDate; // 记录的建立日期,是java.util.Date,而不是java.sql.Date public People(String name) {
super();
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isSex() {
return sex;
}
public void setSex(boolean sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
} //因为人是这个树的最小子节点,所以这个地方空实现接口中的这两个方法就可以了
@Override
public void setChildren(List<ITreeEntry> children) {
}
@Override
public List<ITreeEntry> getChildren() {
return null;
}
}
制造各个实体类的工具类
DataFactory.java
/**
* 此类负责生成TreeViewer的方法setInput所需要的参数.
* @author kongxiaohan
*
* 这个地方我没有用书中用的局部代码块,在组织代码的时候确实犯了一些小错误,
* 确实体会到了局部代码块的意义,代码读起来容易,而且节省了内存.
*/
public class DataFactory {
/**
* 在这个方法中定义生成的数据
* 要有人People的数据对象
* 要有国家Country的数据对象
* 要有城市City的数据对象
* @return
*/
public static Object createTreeData(){
//生成人People的数据对象
People people1 = new People("张A");
People people2 = new People("张B");
People people3 = new People("张C");
People people4 = new People("张D");
People people5 = new People("张E");
People people6 = new People("张F");
People people7 = new People("张G");
People people8 = new People("张H");
People people9 = new People("张I"); //生成城市City的数据对象
City city1 = new City("北京");
City city2 = new City("广州");
City city3 = new City("东京");
City city4 = new City("芝加哥");
City city5 = new City("洛杉矶"); //生成国家Country的的数据对象
Country country1 = new Country("中国");
Country country2 = new Country("日本");
Country country3 = new Country("美国"); /**
* 将这些封装的对象建立关系.
*/
//1.人和城市的关系
//张A 张B 张C 在帝都
ArrayList<ITreeEntry> list1 = new ArrayList<ITreeEntry>();
list1.add(people1);
list1.add(people2);
list1.add(people3);
city1.setChildren(list1); //张D 张E 张F在广州
ArrayList<ITreeEntry> list2 = new ArrayList<ITreeEntry>();
list2.add(people4);
list2.add(people5);
list2.add(people6);
city2.setChildren(list2); //张G 在东京
ArrayList<ITreeEntry> list3 = new ArrayList<ITreeEntry>();
list3.add(people7);
city3.setChildren(list3); //张I 在芝加哥
ArrayList<ITreeEntry> list4 = new ArrayList<ITreeEntry>();
list4.add(people8);
city4.setChildren(list4); //张H 在洛杉矶
ArrayList<ITreeEntry> list5 = new ArrayList<ITreeEntry>();
list5.add(people9);
city5.setChildren(list5); //2.城市和国家的关系
//北京 上海 广州是中国的
ArrayList<ITreeEntry> list6 = new ArrayList<ITreeEntry>();
list6.add(city1);
list6.add(city2);
country1.setChildren(list6); //东京是日本的
ArrayList<ITreeEntry> list7 = new ArrayList<ITreeEntry>();
list7.add(city3);
country2.setChildren(list7); //芝加哥和洛杉矶是美国的
ArrayList<ITreeEntry> list8 = new ArrayList<ITreeEntry>();
list8.add(city4);
list8.add(city5);
country3.setChildren(list8); //3.将国家置于一个对象之下,这个对象可以是一个List也可以是个数组
//国家是这个树上的最上层的节点.国家和国家之间是并列的关系,把这几个国家放到一个List集合对象中.
ArrayList<ITreeEntry> list9 = new ArrayList<ITreeEntry>();
list9.add(country1);
list9.add(country2);
list9.add(country3);
return list9;
}
}
内容器:TreeViewerContentProvider.java
标签器还比较简单,在TreeViewer中最主要和最复杂的是内容器,熟悉内容器是掌握TreeViewer的要点.
/**
* "内容器" 由它决定哪些对象记录应该输出在TreeViewer里显示.
* @author kongxiaohan
*
*/
public class TreeViewerContentProvider implements ITreeContentProvider{ /**
* 这个方法决定树的一级目录显示哪些对象
* @param inputElement 是用tv.setInput()方法输入的那个对象
* @return Object[]一个数组,数组中一个元素就是一个结点
*/
@Override
public Object[] getElements(Object inputElement) {
if (inputElement instanceof List) {
List list = (List) inputElement;
return list.toArray();
} else {
return new Object[0]; // 生成一个空数组
}
} /**
* 判断某结点是否有子结点。如果有子结点,这时结点前都有一个“+”号图标
*
* @param element 需要判断是否有子的结点
* @return true有子结点,false无子结点
*/
@Override
public boolean hasChildren(Object element) {
ITreeEntry entry = (ITreeEntry) element;
List<ITreeEntry> list = entry.getChildren();
if (list == null || list.isEmpty()) {
return false;
} else {
return true;
}
} /**
* 由这个方法决定父结点应该显示那些子结点。
*
* @param parentElement 当前被点击的结点对象
* @return 由子结点做为元素的数组
*/
@Override
public Object[] getChildren(Object parentElement) {
ITreeEntry entry = (ITreeEntry) parentElement;
List<ITreeEntry> list = entry.getChildren();
if (list == null || list.isEmpty()) {
return new Object[0];
} else {
return list.toArray();
}
} //>>>>>>>>>>>>>>>>>>>>>>>>>书上说以下三个方法是没有用的,空实现就哦了>>>>>>>>>>>>>>>>>>>>>>
@Override
public Object getParent(Object element) {
return null;
} @Override
public void dispose() {
} @Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
}
程序说明:在内容器中最关键的是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的标签器.
/**
* "标签器" 控制记录在树中显示的文字和图像等.
* @author kongxiaohan
*/
public class TreeViewerLableProvider implements ILabelProvider { /**
* 记录显示的文字。不能返回NULL值
*/
@Override
public String getText(Object element) {
//得到这个节点对应的名字,首先对element进行强制类型转换
ITreeEntry entry = (ITreeEntry) element;
return entry.getName();
} /**
* 记录显示的图像,可以返回NULL值
*/
@Override
public Image getImage(Object element) {
return null;
} //>>>>>>>>>>>>>>>>>>>>>>书上说一下几个方法没有用,空实现就可以了.>>>>>>>>>>>>>>>>>>>>>>>>>
@Override
public void addListener(ILabelProviderListener listener) {
// TODO Auto-generated method stub } @Override
public void removeListener(ILabelProviderListener listener) {
// TODO Auto-generated method stub }
@Override
public void dispose() {
// TODO Auto-generated method stub }
@Override
public boolean isLabelProperty(Object element, String property) {
// TODO Auto-generated method stub
return false;
}
}
给TreeViewer加上右键菜单的方法和TableViewer相似,也要用到Action,ActionGroup,MenuManager类.当然程序要根据树的特点稍作改动.
MyActionGroup内含有各Action类,在实现Action时,对传入的结点对象要记得进行空值判断.因为TreeViewer的大部分方法都不支持空值参数.会导致异常并中断程序.
MyActionGroup.java
public class MyActionGroup extends ActionGroup {
//ActionGroup这是个抽象类,但是其中并没有抽象方法.
private TreeViewer treeViewer; //构造方法
public MyActionGroup(TreeViewer treeViewer) {
this.treeViewer = treeViewer;
} /**
* 生成菜单Menu,并将两个Action传入
*/
public void fillContextMenu(IMenuManager mgr) {
/*
* 加入两个Action对象到菜单管理器
*/
MenuManager menuManager = (MenuManager) mgr; // 把接口类转换成其实现类.
//用MenuManager管理Action
menuManager.add(new OpenAction());
menuManager.add(new RefreshAction());
menuManager.add(new ExpandAction());
menuManager.add(new CollapseAction());
menuManager.add(new AddEntryAction());
menuManager.add(new RemoveEntryAction());
menuManager.add(new ModifyEntryAction());
/*
* 把这些功能加入到右键的Menu菜单中.
*/
Tree tree = treeViewer.getTree();
Menu menu = menuManager.createContextMenu(tree);
tree.setMenu(menu);
} /**
* 打开"菜单"对应的Action类
*/
private class OpenAction extends Action {
public OpenAction() {
setText("打开");
}
/**
* 继承自Action的方法,动作代码写此方法中
*/
//覆盖Action中的run()方法.
public void run() {
ITreeEntry obj = getSelTreeEntry();//getgetSelTreeEntry()得到当前节点
if (obj != null) {
//弹出一个这个节点名字的对话框.
MessageDialog.openInformation(null, null, obj.getName());
}
}
} /**
* 刷新对应的Action类.
*/
private class RefreshAction extends Action {
public RefreshAction() {
setText("刷新");
} //覆盖Action类中的run()方法,里面是调用的refresh()方法.
public void run() {
treeViewer.refresh();// 调用TreeViewer的刷新方法
}
} /**
* 展开当前结点的Action类
*/
private class ExpandAction extends Action {
public ExpandAction() {
setText("展开");
}
//重写run()方法
public void run() {
ITreeEntry obj = getSelTreeEntry();
if (obj != null) {
treeViewer.expandToLevel(obj, 1);
// 这个方法后面的数字是展开的层级数.这个地方设置成仅仅展开1个层级.
}
}
} /**
* 收缩当前结点的Action类
*/
private class CollapseAction extends Action {
public CollapseAction() {
setText("收缩");
}
//重写run()方法
public void run() {
ITreeEntry obj = getSelTreeEntry();
if (obj != null) {
treeViewer.collapseToLevel(obj, -1); // -1为将当前结点的所有子结点收缩
}
}
} /**
* 给当前结点增加一个子结点的Action类
*/
private class AddEntryAction extends Action {
public AddEntryAction() {
setText("增加");
}
@Override
public void run() {
ITreeEntry obj = getSelTreeEntry();
if (obj == null || obj instanceof People) {
return;
}
InputDialog dialog = new InputDialog(null, "给当前结点增加一个子结点", "输入名称", "请输入你要增加的节点的名字", null);
if (dialog.open() == InputDialog.OK) {// 如果单击OK按钮
String entryName = dialog.getValue(); // 得到Dialog输入值
/* 根据单击结点的不同类型生成相应的子结点 */
ITreeEntry newEntry = null;
if (obj instanceof Country) {
newEntry = new City(entryName);
} else if (obj instanceof City) {
newEntry = new People(entryName);
}
/* 在增加子结点之前将父结点展开 */
if (!treeViewer.getExpandedState(obj)) {
treeViewer.expandToLevel(obj, 1);
}
treeViewer.add(obj, newEntry);// 增加结点
}
}
} /**
* 删除结点的Action类
*/
private class RemoveEntryAction extends Action {
public RemoveEntryAction() {
setText("删除");
}
@Override
public void run() {
ITreeEntry obj = getSelTreeEntry();
if (obj == null) {
return;
}
treeViewer.remove(obj);
}
} /**
* 修改结点名称的Action类
*/
private class ModifyEntryAction extends Action {
public ModifyEntryAction() {
setText("修改");
}
@Override
public void run() {
ITreeEntry obj = getSelTreeEntry();
if (obj == null) {
return;
}
InputDialog dialog = new InputDialog(null, "修改结点", "输入新名称", obj.getName(), null);
if (dialog.open() == InputDialog.OK) {
String entryName = dialog.getValue();//得到对话框中的值.
obj.setName(entryName);//给这个节点设置成得到的对话框中的名字.
treeViewer.refresh(obj); // 刷新结点
}
}
}
/**
* 这个方法是自定义的方法,这个方法的作用就是得到当前选择的节点.
*/
private ITreeEntry getSelTreeEntry() {
IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection();
ITreeEntry treeEntry = (ITreeEntry) (selection.getFirstElement());
return treeEntry;
}
}
TreeViewer.java的实例
public class TreeViewer1 {
public static void main(String[] args) {
TreeViewer1 window = new TreeViewer1();
window.open();
} //定义这个open()方法.就是创建一个典型的SWT程序的步骤
public void open(){
//1.Display负责管理一实现循环和控制UI线程和其他线程之间的通信
final Display display = new Display();
//2.创建一个或者多个Shell(shell是程序的主窗口)
final Shell shell = new Shell();
//3.设置shell的布局.
shell.setSize(200, 300);
//设置shell的布局为FillLayout
shell.setLayout(new FillLayout());
shell.setText("TreeViewer的第一个例子"); Composite c = new Composite(shell, SWT.NONE);
c.setLayout(new FillLayout());
TreeViewer treeViewer = new TreeViewer(c, SWT.BORDER);
treeViewer.setContentProvider(new TreeViewerContentProvider());
treeViewer.setLabelProvider(new TreeViewerLableProvider());
// 和TableViewer一样,数据的入口也是setInput方法
Object inputObj = DataFactory.createTreeData();
treeViewer.setInput(inputObj); /*
//调用自定义的方法创建表格
createTableViewer(shell);
//4.设定内容器
tableviewer.setContentProvider(new TableViewerContentProvider());
//5.设定标签器
tableviewer.setLabelProvider(new TableViewerLabelProvider());
//6.用setInput输入数据(把PeopleFactory产生的List集合传进来)
tableviewer.setInput(PeopleFactory.getPeoples());
*/ //7.创建Shell中的组件(这个例子中没有加入组件,只有一个空窗口)
shell.open();
//8.写一个时间转发循环
while(!shell.isDisposed()){//如果主窗口没有关闭,则一直循环
//dispose 是"处理,处置,毁掉"的意思
if(!display.readAndDispatch()){//// 如果display不忙
display.sleep();// display休眠
}
}
}
}
TreeViewer1.java的运行结果图
TreeViewer2.java
public class TreeViewer2 { public static void main(String[] args) {
TreeViewer2 window = new TreeViewer2();
window.open();
} public void open() {
final Display display = new Display();
final Shell shell = new Shell();
shell.setSize(200, 300); shell.setLayout(new FillLayout());
Composite c = new Composite(shell, SWT.NONE);
c.setLayout(new FillLayout());
TreeViewer treeViewer = new TreeViewer(c, SWT.BORDER);
treeViewer.setContentProvider(new TreeViewerContentProvider());
treeViewer.setLabelProvider(new TreeViewerLableProvider());
Object inputObj = DataFactory.createTreeData();
//>>>>>>>>>>>>>相比于TreeViewer1.java增加的>>>>>>>>>>>>>>>>>>>>>>>>>
//生成一个ActionGroup对象
MyActionGroup actionGroup = new MyActionGroup(treeViewer);
// 调用fillContextMenu方法将按钮注入到菜单对象中
actionGroup.fillContextMenu(new MenuManager());
// --------------加入代码:END--------------------
treeViewer.setInput(inputObj);
// -----------------------------
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
}
TreeViewer2.java的运行结果图:
上面的这个是<<Eclispe从入门到精通>>中第一版的代码,在第二版中对一些功能进行了一些改进.
该进在于"不同的结点显示不同的菜单".
树的"人"结点是没有子节点的.因此对于"人"这个节点来说,右键菜单中的"展开,收缩,增加"菜单项都没有任何意义,应该隐藏起来,再根据当前节点的类型决定要将哪些Action加入菜单中.按着这个思路将MyActionGroup类的fillContextMenu方法修改如下: