《Eclipse SWT/JFACE 核心应用》 清华大学出版社 19 MVC的表格、树和列表
19.1 MVC概述
MVC是模型-视图=控制器(Model-View-Controller)设计模式的简称。MVC模式将数据与视图分开,通过控制器来控制与数据交互。JFace中有关实现MVC模式的控件都在org.eclipse.jface.viewers包下。所有的MVC控件都继承自View类,该类是一个抽象类,不能直接创建对象,需使用其实现的子类。常用的类如下:
◆ ListViewer类:列表控件。
◆ TreeViewer类:树控件。
◆ TableViewer类:表格控件。
◆ TextViewer、SourceViewer、ProjectionViewer为编辑文本的控件。
19.2 表格(TableViewer)
表格程序的代码:
package www.swt.com.ch19;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TableColumn;
import www.swt.com.util.ImageFactory;
public class TableWindow extends ApplicationWindow {
private TableViewer table;//声明一个表格对象
private List<PersonEO> persons ;//表格所存储的数据
//表格中列的索引号
public static final int ID = 0;
public static final int NAME = 1;
public static final int GENDER = 2;
public static final int COLOR = 3;
//表格个的列名称
public static final String[] COLUMN_NAME = { "ID号" ,"姓名","性别","喜欢颜色"};
public TableWindow() {
super(null);
initPersons();
}
//初始化表格数据,通常是从数据库中读出
private void initPersons() {
persons = new ArrayList<PersonEO>();
persons.add( new PersonEO(1,"张三","男","灰色"));
persons.add( new PersonEO(2,"李四","女","白色"));
}
//设置窗口的标题和大小
protected void configureShell(Shell shell) {
super.configureShell(shell);
shell.setSize(300, 200);
shell.setText("TableViewer程序示例");
}
//创建窗口的控件
protected Control createContents(Composite parent) {
//创建TableViewer对象
table = new TableViewer(parent ,SWT.FULL_SELECTION);
//table = CheckboxTableViewer.newCheckList( parent , SWT.FULL_SELECTION);
//创建表头
for ( int i =0; i<COLUMN_NAME.length;i++){
new TableColumn(table.getTable(), SWT.LEFT).setText(COLUMN_NAME[i]);
table.getTable().getColumn(i).pack();
}
//设置排序器
table.setSorter( new TableSorter());
//分别为表头的每一列注册事件
TableColumn column = table.getTable().getColumn(0);
column.addSelectionListener( new SelectionAdapter(){
public void widgetSelected(SelectionEvent e) {
//单击时,重新设置排序对象属性
((TableSorter)table.getSorter()).doSort(TableWindow.ID);
//刷新表格数据
table.refresh();
}
});
column = table.getTable().getColumn(1);
column.addSelectionListener( new SelectionAdapter(){
public void widgetSelected(SelectionEvent e) {
((TableSorter)table.getSorter()).doSort(TableWindow.NAME);
table.refresh();
}
});
column = table.getTable().getColumn(2);
column.addSelectionListener( new SelectionAdapter(){
public void widgetSelected(SelectionEvent e) {
((TableSorter)table.getSorter()).doSort(TableWindow.GENDER);
table.refresh();
}
});
column = table.getTable().getColumn(3);
column.addSelectionListener( new SelectionAdapter(){
public void widgetSelected(SelectionEvent e) {
((TableSorter)table.getSorter()).doSort(TableWindow.COLOR);
table.refresh();
}
});
//设置表头和表格线可见
table.getTable().setHeaderVisible(true);
table.getTable().setLinesVisible( true );
//设置数据
table.setContentProvider(new MyContentProvider());
//设置视图
table.setLabelProvider( new MyLabelProvider());
//设置表格数据对象,该方法非常重要,是表格数据入口
table.setInput(persons);
createContextMenu();
//设置列属性
table.setColumnProperties( COLUMN_NAME );
//设置单元格编辑器对象数组
CellEditor[] editors = new CellEditor[4];
editors[0] = null;
editors[1] = new TextCellEditor(table.getTable());
editors[2] = new TextCellEditor(table.getTable());
editors[3] = new TextCellEditor(table.getTable());
//设置单元格编辑器
table.setCellEditors(editors);
//设置单元个修改器
table.setCellModifier( new ICellModifier(){
//如果该列为第一列,设置不能修改
public boolean canModify(Object element, String property) {
if ( property.equals(COLUMN_NAME[0]))
return false;
return true;
}
//当处于编辑状态时所显示的值
public Object getValue(Object element, String property) {
PersonEO p = (PersonEO) element;
if ( property.equals(COLUMN_NAME[1]))
return p.getName();
else if ( property.equals(COLUMN_NAME[2]))
return p.getGender();
else if ( property.equals(COLUMN_NAME[3]))
return p.getColor();
return null;
}
//当修改后设置更新数据
public void modify(Object element, String property, Object value) {
if (element instanceof Item)
element = ((Item) element).getData();
PersonEO p = (PersonEO) element;
if ( property.equals(COLUMN_NAME[1]))
p.setName( (String)value );
else if ( property.equals(COLUMN_NAME[2]))
p.setGender( (String)value );
else if ( property.equals(COLUMN_NAME[3]))
p.setColor( (String)value );
//刷新表格
table.refresh();
}
});
table.addDoubleClickListener( new IDoubleClickListener(){
public void doubleClick(DoubleClickEvent event) {
StructuredSelection select = (StructuredSelection) event.getSelection();
PersonEO p = (PersonEO) select.getFirstElement();
System.out.println(p.getName());
}
} );
return parent;
}
//创建上下文菜单
private void createContextMenu() {
MenuManager menu = new MenuManager();
menu.add( new AddAction() );
menu.add( new DelAction() );
//menu.add( new FileterAction() );
//获得一个Menu对象
Menu m = menu.createContextMenu(getShell());
//将该对象设置为表格的菜单
table.getTable().setMenu( m );
}
public static void main(String[] args) {
TableWindow test = new TableWindow();
test.setBlockOnOpen(true);
test.open();
Display.getCurrent().dispose();
}
public class MyContentProvider implements IStructuredContentProvider {
//将初始化数据的入口对象转换成表格使用的数组对象
public Object[] getElements(Object inputElement) {
if ( inputElement instanceof List )
return ((List) inputElement).toArray();
return null;
}
//释放该对象时调用的方法
public void dispose() {
}
//当表格中的数据改变时调用该方法
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
}
public class MyLabelProvider implements ITableLabelProvider {
//设置每个单元格所显示的图标
public Image getColumnImage(Object element, int columnIndex) {
/*
//如果是性别所在的列
if (columnIndex == GENDER){
//类型转换,element代表表格中的一行
PersonEO person = (PersonEO)element;
//根据不同的值设置不同的图标
if ( person.getGender().equals("男"))
return ImageFactory.loadImage( Display.getCurrent(),ImageFactory.ICON_BOY);
else if ( person.getGender().equals("女"))
return ImageFactory.loadImage( Display.getCurrent(),ImageFactory.ICON_GIRL);
}
*/
return null;
}
//设置每个单元格所显示的文字
public String getColumnText(Object element, int columnIndex) {
//类型转换,element代表表格中的一行
PersonEO person = (PersonEO)element;
if ( columnIndex == ID)//如果是第一列
return person.getID()+"";
else if ( columnIndex == NAME)//如果是第二列
return person.getName()+"";
else if ( columnIndex == GENDER)//如果是第三列
return person.getGender();
else if ( columnIndex == COLOR)//如果是第四列
return person.getColor()+"";
return "";
}
//释放对象时释放图像资源
public void dispose() {
ImageFactory.dispose();
}
//以下方法为空实现
public void addListener(ILabelProviderListener listener) {
}
public boolean isLabelProperty(Object element, String property) {
return false;
}
public void removeListener(ILabelProviderListener listener) {
}
}
public class AddAction extends Action{
public AddAction(){
setText("添加");
}
public void run() {
//新建一个实体对象
PersonEO person = new PersonEO();
person.setID( table.getTable().getItemCount()+1);
person.setName("Janet");
person.setGender("女");
person.setColor("红色");
//添加一行数据
table.add( person );
persons.add(person); // smx:如果没有这行代码,在点击表头排序时新添加的记录会丢失
}
}
public class DelAction extends Action{
public DelAction(){
setText("删除");
}
public void run() {
//获得当前的所选中的行
StructuredSelection selection = (StructuredSelection) table.getSelection();
//注意,同时选中的可能是多行,这是返回的是数组对象
//获得数组对象的一个元素,也就是只有选中一行的情况
PersonEO p= (PersonEO)selection.getFirstElement();
//从表中删除该行
table.remove(p);
}
}
public class FileterAction extends Action{
//声明一个ViewerFilter对象
ViewerFilter femaleFilter;
public FileterAction(){
//设置标题,样式为复选框样式
super("筛选",AS_CHECK_BOX);
//内部类创建该对象
femaleFilter = new ViewerFilter(){
//需实现ViewerFilter类中的抽象方法
public boolean select(Viewer viewer, Object parentElement, Object element) {
PersonEO p = (PersonEO)element;
return p.getGender().equals("女");
}
};
//初始化时设置为取消选中状态
this.setChecked(false);
}
public void run() {
//如果此时选中,则为表格设置过滤器
if (isChecked())
table.addFilter(femaleFilter );
else//否则,移除表格过滤器
table.removeFilter(femaleFilter);
//刷新表格数据
table.refresh();
}
}
}
从以上程序的代码中总结出使用TableViewer对象时应注意以下问题:
◆ 创建TableViewer的构造方法:
◇ TableViewer(Composite parent):使用默认的样式。
◇ TableViewer(Composite parent, int style):可设置表格的样式,样式常量可以使用Table类使用的样式常量。
◇ TableViewer(Table table):可以将一个SWT表格对象作为构造函数。
◆ TableViewer中的Table对象:TableViewer实际上封装了SWT的Table对象,通过table.getTable()可以获得该Table对象,表头的设置和显示与SWT的表格的方法相同。
◆ 显示表格的一般步骤:虽然TableViewer内部使用了Table对象,但创建表格数据的代码中并不用创建TableItem来组织数据,因为TableViewer将组织和显示的数据交给了两个接口对象IContentProvider和ITableLabelProvider。
IContentProvider负责将setInput方法传入的对象转化为表格所需要的对象。ITableLabelProvider负责处理如何显示数据。
要组织表格数据并显示数据,必须使用以下3个方法:
//设置数据
table.setContentProvider(new MyContentProvider());
//设置视图
table.setLabelProvider( new MyLabelProvider());
//设置表格数据对象,该方法非常重要,是表格数据入口
table.setInput(persons);
◆ 表格中数据组织与两个方法有关:
◇ void setInput(Object input):该方法是表格数据最初的入口点,任何对象都可以设置为表格数据,因为该方法所设定的对象为Object对象,所有的类都是Object子类。但通常使用集合类来组织数据。
在实际的项目开发中,一般会使用实体对象(Entity Object)来装载数据库中的数据,例如本例中的PersonEO实体对象:
package www.swt.com.ch19;
public class PersonEO {
private int ID;
private String name;
private String gender;
private String color;
public PersonEO (){}
public PersonEO ( int id , String name , String gender, String color){
this.ID = id;
this.name = name;
this.gender = gender;
this.color = color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getID() {
return ID;
}
public void setID(int id) {
ID = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
从该实体类中可以看出,该类的属性对应表中的一列。除了列属性外,还有设置和获取属性的方法。
◇ setContentProvider方法为表格创建一个规则,使初始化的数据对象转化为表格中的数据。
void setContentProvider(IContentProvider provider)
IContentProvider 是一个接口,而在表格使用时实现IStructuredContentProvider这个接口,该接口继承自IContentProvider 。
参考本文中的:MyContentProvider 。
实现IStructuredContentProvider接口必须实现3个方法。其中最重要的是getElements方法。该方法主要将setInput设置的对象转化成表格所使用的数组对象。为防止类型转换异常,在不知道是何类型的情况下可以使用以下代码进行类型检查:
public Object[] getElements(Object inputElement) {
if ( inputElement instanceof List )
return ((List) inputElement).toArray();
return null;
}
◆ setLabelProvider(IBaseLabelProvider labelProvider):使用该方法显示数据。IBaseLabelProvider是一个接口,在表格中可以通过实现ITableLabelProvider接口,这个接口是IBaseLabelProvider的子类。
参考本文中的:MyLabelProvider。
在实现该接口中,最重要的是实现getColumnImage和getColumnText方法。一个是设置单元格的图标,一个是设置单元格显示的文本。
◆ createContextMenu():创建菜单。
参考本文中的:createContextMenu、AddAction、DelAction。
对表格操作的一些常用的方法:
◇ 添加一行数据:add(Object element)。
◇ 添加多行数据:add(Object[] elements)。
◇ 清空指定表格索引一行的数据:clear(int index)。
◇ 在表格指定的索引行插入一行:insert(Object element, int position)。
◇ 删除指定的行:remove(Object element)。
◇ 删除指定的多行:remove(Object[] elements)。
◇ 替换指定行数据:replace(Object element, int index)。
注意:这些方法只是对表格显示的数据进行添加和删除,并未改变数据模型中提供的数据,在本例中数据模型中的数据也就是List对象。也可以通过改变模型中的数据来实现对表格的添加和修改,然后使用refresh()方法刷新表格。
◆ setSorter(ViewerSorter sorter):为表格设置一个排序器。
设置排序器:
//设置排序器
table.setSorter( new TableSorter());
排序器的代码如下:
package www.swt.com.ch19;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
public class TableSorter extends ViewerSorter {
private static final int ASCENDING = 0;
private static final int DESCENDING = 1;
private int order;//判断是升序还是降序
private int column;//判断排序的列
public void doSort(int column) {
// 如果是同一列,改变排列的顺序
if (column == this.column) {
order = 1 - order;
} else {// 如果是另一列,则默认为升序排列
this.column = column;
order = ASCENDING;
}
}
//覆盖父类的方法,返回比较结果有-1,0,1三种情况
public int compare(Viewer viewer, Object e1, Object e2) {
int result = 0;
PersonEO p1 = (PersonEO) e1;
PersonEO p2 = (PersonEO) e2;
//默认是升序
switch (column) {
case TableWindow.ID:
result = p1.getID() > p2.getID() ? 1 : -1;
break;
case TableWindow.NAME:
result = collator.compare(p1.getName(), p2.getName());
break;
case TableWindow.GENDER:
result = collator.compare(p1.getGender(), p2.getGender());
break;
case TableWindow.COLOR:
result = collator.compare(p1.getColor(), p2.getColor());
break;
}
//如果此时为降序
if (order == DESCENDING)
result = -result;
return result;
}
}
doSort为单击表头时调用,判断数据排序的方法是覆盖父类中的compare()方法,该方法的返回值用于判断数据的顺序。
有了排序器对象后,下面为表头注册单击表头事件,在初始化表头对象后,在程序中增加以下代码:
//设置排序器
table.setSorter( new TableSorter());
//分别为表头的每一列注册事件
TableColumn column = table.getTable().getColumn(0);
column.addSelectionListener( new SelectionAdapter(){
public void widgetSelected(SelectionEvent e) {
//单击时,重新设置排序对象属性
((TableSorter)table.getSorter()).doSort(TableWindow.ID);
//刷新表格数据
table.refresh();
}
});
column = table.getTable().getColumn(1);
column.addSelectionListener( new SelectionAdapter(){
public void widgetSelected(SelectionEvent e) {
((TableSorter)table.getSorter()).doSort(TableWindow.NAME);
table.refresh();
}
});
column = table.getTable().getColumn(2);
column.addSelectionListener( new SelectionAdapter(){
public void widgetSelected(SelectionEvent e) {
((TableSorter)table.getSorter()).doSort(TableWindow.GENDER);
table.refresh();
}
});
column = table.getTable().getColumn(3);
column.addSelectionListener( new SelectionAdapter(){
public void widgetSelected(SelectionEvent e) {
((TableSorter)table.getSorter()).doSort(TableWindow.COLOR);
table.refresh();
}
});
当不需要排序器时,setSorter(null)方法将排序器设置为null就可以了。
◆ addFilter(ViewerFilter filter):为表格设置过滤器。
创建菜单项时增加:menu.add(new FilterAction());
FilterAction的代码参考本文中的:FileterAction。
当创建过滤器对象时,必须实现select方法,该方法返回布尔型,true表示显示该数据,false表示不显示该数据。
向表格中设置和移除过滤器的方法是addFilter和removeFilter方法。
◆ 编辑表格单元:所有的单元格编辑器都是从CellEditor继承而来的,该类是一个抽象类,创建对象时要使用实现了的子类。
//设置列属性
table.setColumnProperties( COLUMN_NAME );
//设置单元格编辑器对象数组
CellEditor[] editors = new CellEditor[4];
editors[0] = null;
editors[1] = new TextCellEditor(table.getTable());
editors[2] = new TextCellEditor(table.getTable());
editors[3] = new TextCellEditor(table.getTable());
//设置单元格编辑器
table.setCellEditors(editors);
//设置单元个修改器
table.setCellModifier( new ICellModifier(){
//如果该列为第一列,设置不能修改
public boolean canModify(Object element, String property) {
if ( property.equals(COLUMN_NAME[0]))
return false;
return true;
}
//当处于编辑状态时所显示的值
public Object getValue(Object element, String property) {
PersonEO p = (PersonEO) element;
if ( property.equals(COLUMN_NAME[1]))
return p.getName();
else if ( property.equals(COLUMN_NAME[2]))
return p.getGender();
else if ( property.equals(COLUMN_NAME[3]))
return p.getColor();
return null;
}
//当修改后设置更新数据
public void modify(Object element, String property, Object value) {
if (element instanceof Item)
element = ((Item) element).getData();
PersonEO p = (PersonEO) element;
if ( property.equals(COLUMN_NAME[1]))
p.setName( (String)value );
else if ( property.equals(COLUMN_NAME[2]))
p.setGender( (String)value );
else if ( property.equals(COLUMN_NAME[3]))
p.setColor( (String)value );
//刷新表格
table.refresh();
}
});
◆ 表格的事件处理:
◇ 鼠标双击事件:
table.addDoubleClickListener( new IDoubleClickListener(){
public void doubleClick(DoubleClickEvent event) {
StructuredSelection select = (StructuredSelection) event.getSelection();
PersonEO p = (PersonEO) select.getFirstElement();
System.out.println(p.getName());
}
} );
◇ 支持拖动事件:
addDragSupport(int operations, Transfer[] transferTypes, DragSourceListener listener)
addDropSupport(int operations, Transfer[] transferTypes, final DropTargetListener listener)
◇ 帮助事件:addHelpListener(HelpListener listener)。
◇ 选中改变事件:addSelectionChangedListener(ISelectionChangedListener listener)。
◇ 选中改变后事件:addPostSelectionChangedListener(ISelectionChangedListener listener)。
◇ 双击打开事件:addOpenListener(IOpenListener listener)。
◆ 带有复选框表格(CheckBoxTableViewer)
table = CheckboxTableViewer.newCheckList( parent , SWT.FULL_SELECTION);
CheckboxTableViewer类还提供了对选中状态操作的一些方法:
◇ 该行是否已选中:boolean getChecked(Object element)。
◇ 获得所有已选中的行:Object[] getCheckedElements()。
◇ 该行是否灰色显示:getGrayed(Object element)。
◇ 获得所有灰色显示的行:Object[] getGrayedElements()。
◇ 设置全选和取消全选:setAllChecked(boolean state)。
◇ 设置全部灰色显示和取消灰色显示:setAllGrayed(boolean state)。
◇ 设置指定行的选中状态:setChecked(Object element, boolean state)。
◇ 设置选中多行:setCheckedElements(Object[] elements)。
◇ 设置指定行是否灰色显示的状态:setGrayed(Object element, boolean state)。
◇ 设置多行灰色显示:setGrayedElements(Object[] elements)。
界面显示效果:
按性别排序:
筛选后,只显示性别为“女”的: