表格(TableViewer類)

前面加一點自己關於TableViewer的筆記

    設置table只顯示有數據的部分 放置table過大 出現過多的空行 可以通過設置table的layout。

第14章  表格(TableViewer類) 

源碼下載地址(免費):點擊打開鏈接 

TableViewer表格類是JFace組件中重要且典型的一個組件,其中涉及了JFace的衆多重要概念:內容器、標籤器、過濾器、排序器和修改器,這些概念對後面JFace組件特別是TreeViewer的學習非常重要。從本章也可以體會到JFace非常突出的面向對象特性。

14.1  概    述

JFace是SWT的擴展,它提供了一組功能強大的界面組件,其中包含表格、樹、列表、對話框、嚮導對話框等,從本章之後就開始專門來介紹這些JFace組件。

表格是一種在軟件系統很常見的數據表現形式,特別是基於數據庫的應用系統,表格更是不可缺少的界面組件。SWT的表格組件(Table類)前面已經介紹過了,但在實際項目開發中一般還是用JFace的表格組件TableViewer比較多。TableViewer組件是在SWT的Table組件基礎上採用MVC模式擴展而來的,但Table並非TableViewer的父類,從圖14.1兩個類的譜系圖就可以看出這兩個類不屬於同一族系。

從下面的TableViewer類源代碼可以看到,TableViewer把Table作爲一個實例變量,從而實現了對Table功能的擴展。

public class TableViewer
 extends StructuredViewer {

private TableViewerImpl tableViewerImpl;

private Table table; //把Table類作爲一個實例變量

private TableEditor tableEditor;

……

}

本章就如何使用表格組件TableViewer類來展開講解,並通過一步步地創建一個完整的表格應用實例來串起表格的知識點,實例的最後界面如圖14.2所示。

 

圖14.1 譜系圖

 

圖14.2 本章實例的最後界面

14.2  創建表格並顯示數據

作爲起步,本節將演示如何創建一個TableViewer對象,如何用TableViewer來顯示數據記錄,實例運行效果如圖14.3所示。

 

圖14.3 TableViewer效果圖

14.2.1  實例的數據模型介紹

本實例用TableViewer來顯示一個數據表中的3條記錄,每一條記錄對應某一個人的基本資料,記錄有5個字段:ID號(數值型)、姓名(字符型)、性別(布爾型)、年齡(數值型)和記錄建立時間(日期型)。

如何在程序中體現和操作這些數據記錄呢?在過去,像ASP、PHP這類面向過程的編程模式,人們習慣了這樣操作數據:從數據庫中讀取數據,並不對數據做任何封裝,直接將數據一條條地顯示在表格中。

現在用Java這種面向對象的編程語言,應該用更規範的方式來操作數據:將數據庫中的記錄看作一個數據對象,用一個類來表示它,數據表的字段寫成類的實例變量,這樣的類在Java中叫做實體類(或稱數據類)。EJB和Hibernate的數據操作方式都是這樣的。

數據庫與表格顯示之間加上了實體類,如此一來,以前的“數據表→表格顯示”方式就分成了兩個步驟“數據庫→實體類→表格顯示”。有些習慣了以前編程方式的人也許會覺得多了一個步驟太麻煩,但其實這種方式很有好處:

表格顯示的代碼不再和數據庫表相關。例如,將數據庫由Oracle移植到MySQL時就不需要更改“數據庫→實體類”這個環節的代碼。

零散的字段變量統一在一個類中,程序代碼結構更緊湊、清晰,有利於今後代碼的維護。不要小看維護問題,很多系統做好後不敢再改,害怕改動後會牽涉到其他模塊,其中原因之一就是代碼結構太亂、編程不規範所致。

將數據封裝在一個實體類中,在數據傳遞時方便許多,可以將實體類作爲一個參數在方法與方法之間來回傳遞。

14.2.2  創建數據表的實體類

下面依照表中的字段來創建一個相應的實體類,類名爲PeopleEntity,代碼如下所示。

 

//------------- 文件名:PeopleEntity.java --------------

// 本類包含5個不同數據類型的變量,分別對應數據庫表中的5個字段。變量爲private型,即只能

// 由類的內部代碼訪問,外界只能通過這些變量相應的Setter/Getter方法來訪問它們

public class PeopleEntity {

    private Long id; //唯一識別碼,在數據庫裏常爲自動遞增的ID列

    private String name; //姓名

    private boolean sex; //性別 true男,flase女

    private int age; //年齡

    private Date createDate; //記錄的建立日期。Date類型是java.util.Date,而不是java.sql.Date



    //以下代碼爲字段各自的Setter/Getter方法。參考第3.5.2節,這些方法在Eclipse可自動生成

public Long getId() { return id;}

public void setId(Long long1) {id = long1;}

public String getName() {return name;}

public void setName(String string) {name = string;}

public boolean isSex() { return sex;}

public void setSex(boolean sex) { this.sex = sex; }

public int getAge() {return age;}

public void setAge(int i) {age = i;}

public Date getCreateDate() {return createDate;}

public void setCreateDate(Date date) {createDate = date;}

}

14.2.3  數據的生成

由於數據操作是分兩步走:“數據庫→實體類→表格顯示”,實體類隔離了代碼對數據庫的依賴,所以“數據庫→實體類”這一步就不再講解,這部分的代碼與JFace組件的使用無關緊要,也不會影響表格組件的講解。關於TableViewer和數據庫結合使用方面的內容,在後面“插件項目實戰”中會有詳細示例。

那麼如何生成實體類的對象呢?因爲數據記錄和實體對象相對應,新創建的實體對象就相當於一個空記錄,可以用其set方法一個個地將值設入實體對象中,這樣就能得到帶有數據的實體對象了。

爲了今後便於擴展,將創建實體對象的方法集中在一個類中,這種專門負責創建對象的類又叫對象工廠。此類的代碼如下:

 

//-----------文件名:PeopleFactory.java ----------------

//創建PeopleEntity對象的工廠,創建3個PeopleEntry對象,並裝入List集合返回

public class PeopleFactory {

public static List getPeoples() { // 工廠的靜態方法

List list = new ArrayList();

{ // 第1個實體類對象

PeopleEntity o = new PeopleEntity();

o.setId(new Long(1));// id字段的類型被定義成了Long,所以要轉化一下

o.setName("陳剛");

o.setSex(true);

o.setAge(28);

o.setCreateDate(new Date()); // 當前日期

list.add(o);

}

{ // 第2個實體類對象

PeopleEntity o = new PeopleEntity();

o.setId(2L); // 利用JDK5.0的自動裝箱功能,省了long到Long對象的轉化

o.setName("周閱");

o.setSex(false);

o.setAge(18);

o.setCreateDate(new Date());

list.add(o);

}

{ // 第3個實體類對象

PeopleEntity o = new PeopleEntity();

o.setId(3L);

o.setName("陳常恩");

o.setSex(true);

o.setAge(27);

o.setCreateDate(new Date());

list.add(o);

}

return list;

}

}

程序說明:

在實際應用中,getPeoples方法可由硬性生成PeopleEntity對象,改爲從數據庫中取出數據後生成PeopleEntity對象。

這裏的List不是SWT組件的List,而是Java的集合類java.util.List。根據實際開發情況也可以用數組或Set、Map等代替List。

List是接口,而ArrayList是實際用的類。由於其後代碼是基於List接口編寫的,所以換用其他List接口的實現類,如Vector、LinkedList等,而不必修改其後的代碼。面向接口編程,儘量讓定義類型(如List)比實際類型(如ArrayList)更寬泛些,有利於以後的修改維護。

這裏new ArrayList()使用了JDK5.0的泛型功能,關於泛型可參閱www.chengang.com.cn上的Java類文章。

在數據庫編程中,Java集合類起着重要作用。一定要很熟悉各集合類在特性上的差別,這樣才能根據實際開發情況作出適當的選擇(集合類的詳細資料可查閱Java基礎書籍)。

14.2.4  在表格中顯示數據

在得到由List裝載的包含數據信息的實體類對象後,接下來就是使用TableViewer來顯示這些數據,實現過程一般要經過如下步驟:

第一步:創建一個TableViewer對象,並在構造函數中用式樣設置好表格的外觀,這與其他SWT組件的用法一樣。

第二步:通過表格內含的Table對象設置佈局方式,一般都使用TableViewer的專用佈局管理器TableLayout。該佈局方式將用來管理表格內的其他組件(如TableColumn表格列)。

第三步:用TableColumn類創建表格列。

第四步:設置內容器和標籤器。內容器和標籤器是JFace組件中的重要概念,它們分別是IStructuredContentProvider、ITableLabelProvider兩個接口的實現類,它們的作用就是定義好數據應該如何在TableViewer中顯示。

第五步:用TableViewer的setInput方法將數據輸入到表格。就像人的嘴巴,setInput就是TableViewer的嘴巴。

圖14.4是TableViewer整個數據流程的示意圖。

 

圖14.4 TableViewer數據流程示意圖

程序代碼如下(內容器和標籤器寫成兩個單獨的類):

 

//-------------文件名:TableViewer1.java-------------------

shell.setLayout(new FillLayout());

// 第一步:創建一個TableViewer
對象。式樣:MULTI可多選、H_SCROLL有水平滾動條、V_SCROLL

// 有垂直滾動條、BORDER有邊框、FULL_SELECTION整行選擇

TableViewer
 tv=new TableViewer
(shell, SWT.MULTI |SWT.BORDER |SWT.FULL_SELECTION);

// 第二步:通過表格內含的Table對象設置佈局方式

Table table = tv.getTable();

table.setHeaderVisible(true); // 顯示錶頭

table.setLinesVisible(true); // 顯示錶格線

TableLayout layout = new TableLayout(); // 專用於表格的佈局

table.setLayout(layout);

// 第三步:用TableColumn類創建表格列

layout.addColumnData(new ColumnWeightData(13));// ID列寬13像素

new TableColumn(table, SWT.NONE).setText("ID號");

layout.addColumnData(new ColumnWeightData(40));

new TableColumn(table, SWT.NONE).setText("姓名");

layout.addColumnData(new ColumnWeightData(20));

new TableColumn(table, SWT.NONE).setText("性別");

layout.addColumnData(new ColumnWeightData(20));

new TableColumn(table, SWT.NONE).setText("年齡");

layout.addColumnData(new ColumnWeightData(60));

new TableColumn(table, SWT.NONE).setText("記錄建立時間");

// 第四步:設置內容器和標籤器

tv.setContentProvider(new TableViewerContentProvider());

tv.setLabelProvider(new TableViewerLabelProvider());

// 第五步:用TableViewer
的setInput方法將數據輸入到表格

Object data = PeopleFactory.getPeoples();

tv.setInput(data);

 

 

//-------------文件名:TableViewerContentProvider.java-------------------

//內容器。由此類對輸入到表格的數據進行篩選和轉化。此類要實現接口的3種方法,其中           

//getElements是主要方法,另外兩個方法很少用到,空實現就行了

public class TableViewerContentProvider implements IStructuredContentProvider {

// 對輸入到表格的數據集合進行篩選和轉化。輸入的數據集全部要轉化成數組,每一個數組元素

//就是一個實體類對象,也就是表格中的一條記錄

public Object[] getElements(Object element) {

// 參數element就是通過setInput(Object input)輸入的對象input

// 本例中輸入給setInput是List集合

if (element instanceof List)// 加一個List類型判斷

return ((List) element).toArray(); // 將數據集List轉化爲數組

else

return new Object[0]; // 如非List類型則返回一個空數組

}



// 當TableViewer
對象被關閉時觸發執行此方法

public void dispose() {}



// 當TableViewer
再次調用setInput()時觸發執行此方法

public void inputChanged(Viewer v, Object oldInput, Object newInput) {}

}

 

 

//-------------文件名:TableViewerLabelProvider.java-------------------

//標籤器。如果說內容器是對輸入表格的數據集作處理,那麼標籤器則是對數據集中的單個實體對象

//進行處理和轉化,由標籤器來決定實體對象中的字段顯示在表格的哪一列中

public class TableViewerLabelProvider implements ITableLabelProvider {

//創建幾個圖像

private Image[] images = new Image[] { 

new Image(null, "icons/refresh.gif"), 

new Image(null, "icons/star.jpg"), 

new Image(null, "icons/moon.jpg") };

// 由此方法決定數據記錄在表格的每一列顯示什麼文字。 element參數是一個實體類對象

// col是當前要設置的列的列號,0是第一列

public String getColumnText(Object element, int col) {

PeopleEntity o = (PeopleEntity) element; // 類型轉換

if (col == 0)// 第一列要顯示什麼數據

return o.getId().toString();

if (col == 1)

return o.getName();

if (col == 2)

return o.isSex() ? "男" : "女";

if (col == 3)

return String.valueOf(o.getAge()); // 將int型轉爲String型

if (col == 4)

return o.getCreateDate().toString();

return null; // 方法可以返回空值

}



// getColumnText方法用於顯示文字,本方法用於顯示圖片

public Image getColumnImage(Object element, int col) {

PeopleEntity o = (PeopleEntity) element;

// 只讓“陳剛”這條記錄顯示圖片

if (o.getName().equals("陳剛")||o.getName().equals("周閱")) {

if (col == 0)// 第一列要顯示的圖片

return images[0];

if (col == 2)//根據性別顯示不同的圖標

return o.isSex() ? images[1] : images[2];

}

return null; // 方法可以返回空值

}



// 當TableViewer
對象被關閉時觸發執行此方法

public void dispose() {

//別忘了SWT組件的原則:自己創建,自釋放

for (Image image : images) {

image.dispose();

}

}



// -------------以下方法很少使用,先不用管,讓它們空實現-----------------

public boolean isLabelProperty(Object element, String property) {return false;}

public void addListener(ILabelProviderListener listener) {}

public void removeListener(ILabelProviderListener listener) {}

}

程序說明:TableViewer的setInput方法的參數類型是Object,所以它可以接受任何類型的參數,因此在內容器中要將參數轉換過來,如(List)element。但如果setInput不是List類型的參數,程序就會出錯,所以最好用element instanceofList來作一下類型判斷會比較穩妥,在SWT/JFace編程中很多BUG都出在這種地方。當然,本例的setInput參數定的就是List類型,不用instanceof判斷直接類型轉換也沒什麼問題。

14.3  響應鼠標雙擊事件

如何讓TableViewer的每一行響應鼠標的雙擊或單擊事件呢?又如何取得被選擇中的記錄數據呢?本節將解決這個問題。本節實例的效果如圖14.5所示,雙擊表格中的某條記錄時彈出一個提示框,框中的文字信息顯示該記錄的人名。

 

圖14.5 鼠標響應的效果圖

本節實例在14.2節的代碼基礎上修改完成(完整代碼見配書光盤的TableViewer2.java文件),具體如下。

在tv.setInput(data)一句之後,添加一個自定義方法addListener(tv),在此方法中給TableViewer添加監聽器。addListener方法的代碼如下:

 

private void addListener(TableViewer
 tv) {

// 鼠標雙擊事件監聽

tv.addDoubleClickListener(new IDoubleClickListener() {

public void doubleClick(DoubleClickEvent event) {

//得到表格的選擇對象,裏面封裝了表格中被選擇的記錄信息

IStructuredSelection selection = (IStructuredSelection) event.getSelection();

// 得到所選擇的第一條實體對象(表格可以有多選),並進行類型轉換

PeopleEntity o = (PeopleEntity) selection.getFirstElement();

// 彈出一個提示框

MessageDialog.openInformation(null, "提示", o.getName());

}

});



// 選擇事件(單擊)監聽

tv.addSelectionChangedListener(new ISelectionChangedListener() {

public void selectionChanged(SelectionChangedEvent event) {

// 事件處理代碼……(略)

}

});

}

14.4  給表格加上右鍵菜單(Action類、ActionGroup類、MenuManager類)

本節來給表格加上如圖14.6所示的右鍵菜單。本節實例在前兩節的代碼基礎上修改完成(完整代碼見配書光盤的TableViewer3.java文件)。

 

圖14.6 右鍵菜單的效果圖

14.4.1  Action、ActionGroup、MenuManager介紹

SWT中菜單是Menu類,本書在前面章節中已經介紹過Menu類的使用,在第11章中菜單項用MenuItem類來實現。但在實際項目中,同一種功能會有多種表現形式,例如Eclipse中的“新建”功能,它會分別出現在主菜單、主工具欄、右鍵菜單裏。如果都是用MenuItem來實現,就需要寫3份類似的代碼,以後也要維護3份代碼。當然也可以將事件處理寫成外部類來共享代碼,但名稱、圖像以及一些其他的信息寫成外部類來共享則不太方便。

JFace包中已經對以上問題提供瞭解決方案,JFace提供了一個Action類,它將名稱、圖像、動作處理程序等集成在其中,這樣就可以共享這些Action來形成菜單項、工具欄按鈕等。

當然在底層最後用於Menu的還是MenuItem對象,將Action轉化成MenuItem是由MenuManager(菜單管理器)來完成的。MenuManager簡化了菜單的創建,一旦生成了MenuManager對象,就可以通用於菜單欄、彈出菜單、工具欄下拉菜單。

另外,Action寫成一個個的類會很零亂,JFace又提供了一個ActionGroup類用於統一管理Action,然後讓外界程序通過ActionGroup來訪問Action。當然,並非一定要使用ActionGroup類來管理Action,只是用它會更好。

14.4.2  創建Action和ActionGroup

以下代碼演示瞭如何創建Action、ActionGroup,以及如何使用MenuManager。

 

//--------文件名:MyActionGroup.java----------

public class MyActionGroup extends ActionGroup {

private TableViewer
 tv;



// 在Action要使用到TableViewer
對象,所以通過構造函數把它傳進來

public MyActionGroup(TableViewer
 tv) {

this.tv = tv;

}



// 生成菜單Menu,並將兩個Action傳入

public void fillContextMenu(IMenuManager mgr) {

// 加入兩個Action對象到菜單管理器

MenuManager menuManager = (MenuManager) mgr;

menuManager.add(new OpenAction());

menuManager.add(new RefreshAction());

// 生成Menu並掛在表格table上。menu和table兩個對象互爲對方的參數

Table table = tv.getTable();

Menu menu = menuManager.createContextMenu(table);

table.setMenu(menu);

}



// “打開”的Action類

private class OpenAction extends Action {

public OpenAction() {setText("打開");}

public void run() {// 繼承自Action的方法,動作代碼寫在此方法中

IStructuredSelection selection = (IStructuredSelection) tv.getSelection();

PeopleEntity o = (PeopleEntity) selection.getFirstElement();

if (o == null)

MessageDialog.openInformation(null, null, "請先選擇記錄");

else

MessageDialog.openInformation(null, null, o.getName());

}

}



// “刷新”的Action類

private class RefreshAction extends Action {

public RefreshAction() {setText("刷新");}

public void run() {

tv.refresh(); //表格的刷新方法,界面會重新讀取數據並顯示

}

}

}

14.4.3  在主程序中使用ActionGroup、MenuManager

MyActionGroup類封裝了Action以及Action和菜單Menu之間的交互代碼。最後,只需將以下代碼加入到shell.open()語句之前即可。

 

//生成一個ActionGroup對象,並調用fillContextMenu方法將按鈕注入到菜單對象中

MyActionGroup actionGroup = new MyActionGroup(tv); 

actionGroup.fillContextMenu(new MenuManager());

程序說明:圖14.7說明了在程序中是如何創建右鍵菜單的,在主程序生成一個MenuManager對象傳給ActionGroup對象,然後再通過ActionGroup內部的createContextMenu方法生成一個菜單對象Menu,最後用Menu的add()方法將Action加入。

 

圖14.7 程序分析圖

14.5  表格的排序(ViewerSorter類)

本節實例將實現表格的單擊表頭排序功能(以ID、姓名兩字段的排序爲例),本節實例在前面幾節的代碼基礎上修改完成(完整代碼見配書光盤的TableViewer4.java文件)。

14.5.1  編寫排序器ViewerSorter

TableViewer是根據排序器ViewerSorter中的設置來進行排序的,所以編寫ViewerSorter類是排序的關鍵。編寫排序器的代碼如下:

 

//------文件名:MySorter.java-----------

public class MySorter extends ViewerSorter {

// 每列對應一個不同的常量,正數表示要升序、相反數表示要降序

private static final int ID = 1;

private static final int NAME = 2;

//給外界使用排序器對象

public static final MySorter ID_ASC=new MySorter(ID);

public static final MySorter ID_DESC=new MySorter(-ID);

public static final MySorter NAME_ASC=new MySorter(NAME);

public static final MySorter NAME_DESC=new MySorter(-NAME);

// 當前所要排序的列,取自上面的ID、NAME兩值或其相反數

private int sortType;



// 構造函數用private,表示不能在外部創建MySorter對象

private MySorter(int sortType) {

this.sortType = sortType;

}



// 最關鍵的比較方法compare,改寫自ViewerSorter。方法返回值是一個int值:正數則

//obj1移到obj2之前;零則obj1和obj2的位置不動;負數則obj1移到obj2之後

public int compare(Viewer viewer, Object obj1, Object obj2) {

// 傳入兩條記錄(實體類),然後依列給出它們的先後順序

PeopleEntity o1 = (PeopleEntity) obj1;

PeopleEntity o2 = (PeopleEntity) obj2;

switch (sortType) {

case ID: {

Long l1 = o1.getId();

Long l2 = o2.getId();

// Long的compareTo方法返回值有三個可能值1,0,-1: 

//如l1>l2則返回1;如l1=l2則返回0;如l1<l2則返回-1

return l1.compareTo(l2);

}

case -ID: {

Long l1 = o1.getId();

Long l2 = o2.getId();

return l2.compareTo(l1);

}

case NAME: {

String s1 = o1.getName();

String s2 = o2.getName();

return s1.compareTo(s2);

}

case -NAME: {

String s1 = o1.getName();

String s2 = o2.getName();

return s2.compareTo(s1);

}

}

return 0;

}

}

程序說明:排序器的代碼雖多,但要點就兩個。

q 要用MySorter類生成4個不同的排序對象:ID列的升序、ID列的降序、姓名列的升序、姓名列的降序,那麼MySorter首先就要解決如何生成這4個不同的排序對象。方法就是讓不同的列對應不同的int值,而int值的正負數對應升、降序,然後根據MySorter類構造函數傳入的int值就可以判斷生成不同的排序器對象。另外,由於MySorter是無狀態類,所以多個表格可以安全地共享MySorter所提供的4個排序器對象。

排序的算法由MySorter類的compare方法負責,它實際調用的是JDK中同類型對象之間進行比較的compareTo方法,TableViewer則根據MySorter返回的int值來進行記錄的排序

14.5.2  爲表格列添加事件監聽器

表格列是TableColumn對象,把原來新增ID列和姓名列的4句代碼修改如下:

 

// layout.addColumnData(new ColumnWeightData(13));// ID列寬13像素

// new TableColumn(table, SWT.NONE).setText("ID號");

// layout.addColumnData(new ColumnWeightData(40));

// new TableColumn(table, SWT.NONE).setText("姓名");



layout.addColumnData(new ColumnWeightData(13));

TableColumn col1 = new TableColumn(table, SWT.NONE);

col1.setText("ID號");

col1.addSelectionListener(new SelectionAdapter() {

boolean asc = true; // 記錄上一次的排序方式,默認爲升序

public void widgetSelected(SelectionEvent e) {

// asc=true則ID的升序排序器,否則用降序

tv.setSorter(asc ? MySorter.ID_ASC : MySorter.ID_DESC);

asc = !asc;// 得到下一次排序方式

}

});



layout.addColumnData(new ColumnWeightData(40));

TableColumn col2 = new TableColumn(table, SWT.NONE);

col2.setText("姓名");

col2.addSelectionListener(new SelectionAdapter() {

boolean asc = true;

public void widgetSelected(SelectionEvent e) {

tv.setSorter(asc ? MySorter.NAME_ASC : MySorter.NAME_DESC);

asc = !asc;

}

});

14.6  給表格加上工具欄(ToolBarManager類)

如圖14.8所示,本節將給表格加上一個工具欄。本節實例在前幾節的代碼基礎上修改完成(完整代碼見配書光盤的TableViewer5.java文件)。

 

圖14.8 工具欄效果圖

和14.4節給表格加上右鍵菜單的方法相似,也是用ActionGroup、Action類。不同的是,菜單用MenuManager,這裏的工具欄則用ToolBarManager。此實例分成如下幾步完成。

14.6.1  創建Action類並填充進工具欄

將圖14.8中的按鈕都寫成一個個的Action類,關於Action類的寫法在14.4節已經講過,只需依樣擴充MyActionGroup類中的Action的個數即可,而刷新按鈕則和刷新菜單共用RefreshAction。MyActionGroup的代碼修改示意如下:

 

//------文件名:MyActionGroup.java-----------

……(省略了原來的老代碼)



private class AddAction extends Action {

public AddAction() {

setHoverImageDescriptor(getImageDesc("project.gif"));// 正常狀態下的圖標

setText("增加");

}

public void run() {

PeopleEntity o = createPeople();// 創建一個新實體對象

tv.add(o);// 增加到表格界面中

List list = (List) tv.getInput();

list.add(o); // 增加到數據模型的List容器中

// ....向數據庫增加記錄(略)

}

private PeopleEntity createPeople() {// 創建一個新實體對象

PeopleEntity o = new PeopleEntity();

o.setId(5L);

o.setName("新人");

o.setSex(true);

o.setAge(15);

o.setCreateDate(new Date());

return o;

}

}



private class RemoveAction extends Action {

public RemoveAction() {

setHoverImageDescriptor(getImageDesc("remove.gif"));// 正常狀態下的圖標

// 按鈕無效狀態下的圖標。不設也可以,當按鈕失效時會自動使正常圖片變灰

setDisabledImageDescriptor(getImageDesc("disremove.gif"));

setText("刪除");

}

// 這裏演示瞭如何從表格中刪除所選的多個記錄

public void run() {

    IStructuredSelection s = (IStructuredSelection) tv.getSelection();// 得到選擇的對象集

if (s.isEmpty()) {// 判斷是否有選擇

MessageDialog.openInformation(null, "提示", "請先選擇");

return;

}

for (Iterator it = s.iterator(); it.hasNext();) {

PeopleEntity o = (PeopleEntity) it.next();

tv.remove(o);// 從表格界面上刪除

List list = (List) tv.getInput();

list.remove(o); // 從數據模型的List容器中刪除

// ....,從數據庫中刪除記錄(略)

}

}

}



// 自定義方法。生成Action對象,並通過工具欄管理器ToolBarManager填充進工具欄

public void fillActionToolBars(ToolBarManager actionBarManager) {

// 創建Action對象,一個按鈕對應一個個的Action

Action refreshAction = new RefreshAction();

Action addAction = new AddAction();

Action removeAction = new RemoveAction();

// 將按鈕通過工具欄管理器ToolBarManager填充進工具欄,如果用add(action)

// 也是可以的,只不過只有文字沒有圖像。要顯示圖像需要將Action包裝成

// ActionContributionItem,在這裏我們將包裝的處理過程寫成了一個方法

actionBarManager.add(createContributionItem(refreshAction));

actionBarManager.add(createContributionItem(addAction));

actionBarManager.add(createContributionItem(removeAction));

actionBarManager.update(true);// 更新工具欄,否則工具欄不顯示任何按鈕

}



// 將Action包裝成ActionContributionItem類的方法。實際上Action加入到

// ToolBarManager或MenuManager裏時,也做了ActionContributionItem的包裝,

// 大家看它ToolBarManager的add(IAction)的源代碼即知

private IContributionItem createContributionItem(IAction action) {

ActionContributionItem aci = new ActionContributionItem(action);

aci.setMode(ActionContributionItem.MODE_FORCE_TEXT);// 顯示圖像+文字

return aci;

}



// 得到一個圖像的ImageDescriptor對象

private ImageDescriptor getImageDesc(String fileName) {

try {

URL url = new URL("file:icons/" + fileName);

return ImageDescriptor.createFromURL(url);

} catch (MalformedURLException e) {

e.printStackTrace();

}

return null;

}

程序說明:在表格中,界面中顯示的數據和setInput(Objectinput)傳入的input對象是分離的。也就是說如果input對象中的記錄數據發生改變,要調用表格的tv.refresh()或tv.update(Object element, String[]properties)才能在界面上也顯示新的數據。refresh、update兩個更新界面的方法中:前者是全面更新;後者是隻更新某一條記錄(element)在界面上的顯示,後者的第二個參數甚至還可以指定更新哪幾個字段的界面顯示,顯然後者更新效率要高些。

對於新增記錄和刪除記錄則TableViewer有add和remove方法可用,不過由於前面所說的界面數據和input數據分離,在tv.add、tv.remove之後,勿忘input.add、input.remove。

14.6.2  用ViewForm做佈局調整

在上一步創建好ActionGroup中的Action後,接下來就是要在界面中加上工具欄。先要將佈局用ViewForm類來調整一下,ViewForm也是繼承自Composite的一個容器。原先表格是建立在Shell之上的,現在要在Shell上再插入一個ViewForm容器,以它爲基座將工具欄和表格創建於其中,如圖14.9所示。

將原主程序中的open()方法修改如下,其他代碼不變:

 

shell.setLayout(new FillLayout());

ViewForm viewForm = new ViewForm(shell, SWT.NONE); //佈局基座ViewForm

viewForm.setLayout(new FillLayout());

final TableViewer
 tv = new TableViewer
(viewForm, SW… //父容器由shell改爲viewForm

//……和上一節相同的代碼(省略)

//創建工具欄

ToolBar toolBar = new ToolBar(viewForm, SWT.FLAT); // 創建一個ToolBar容器

ToolBarManager toolBarManager = new ToolBarManager(toolBar); // 創建一個toolBar的管理器

actionGroup.fillActionToolBars(toolBarManager); //將Action通過toolBarManager注入ToolBar中

// 設置表格和工具欄在佈局中的位置

viewForm.setContent(tv.getControl()); // 主體:表格

viewForm.setTopLeft(toolBar); // 頂端邊緣:工具欄

shell.open();

 

圖14.9 佈局示意圖

14.7  帶複選框的表格(CheckboxTableViewer類)

帶複選框的表格如圖14.10所示,它具有如下功能:

單擊“全選”按鈕時,將表格中的所有記錄選中(選中複選框)。

單擊“全不選”按鈕時,取消所有選擇(取消選中複選框)。

單擊“刪除”按鈕時,將所有選中複選框的記錄刪除。

 

圖14.10 帶複選框的表格

本節實例在前幾節的代碼基礎上修改完成(完整代碼見配書光盤的TableViewer6.java文件)。要完成此實例需要如下幾個步驟。

14.7.1  使用表格的複選框式樣

(1)在創建TableViewer對象時多加一個SWT.CHECK式樣,表格變爲複選框式。複選框式的表格要取得選中的記錄,還需要增加一個CheckboxTableViewer對象來輔助表格的使用,因爲僅TableViewer對象無法取得選中的記錄。

 

final TableViewer
 tv = new TableViewer
(viewForm, SWT.CHECK | SWT.MULTI | SWT.……

final CheckboxTableViewer ctv = new CheckboxTableViewer(tv.getTable());

(2)修改創建MyActionGroup的語句,將CheckboxTableViewer的ctv對象作構造函數的第二個參數傳入,因爲MyActionGroup中的Action需要用到此對象。

 

MyActionGroup actionGroup = new MyActionGroup(tv, ctv);

這一步完成後,因爲還沒有對MyActionGroup類作相應改動,Eclipse會顯示錯誤。下面開始修改MyActionGroup類。

14.7.2  修改MyActionGroup類

在原有MyActionGroup類的代碼中作如下幾處修改:

新增一個用於接受CheckboxTableViewer對象的構造函數。

增加“全選”和“全不選”兩個Action類,並相應修改fillActionToolBars方法。

修改“刪除”的RemoveAction,改由CheckboxTableViewer來取得選中的記錄。因爲前幾節的程序也用到RemoveAction,爲了兼容,所以RemoveAction原有的處理代碼還不能廢棄掉。可以加一個表格是否爲複選框式樣的判斷,以決定使用哪種刪除處理代碼。

具體代碼如下所示:(和原代碼相同部分用省略號代替)

 

public class MyActionGroup extends ActionGroup {

    private TableViewer
 tv;

private CheckboxTableViewer ctv; //新增的語句



public MyActionGroup(TableViewer
 v) {

    this.tv = v;

}



    //新增一個構造函數

    public MyActionGroup(TableViewer
 v, CheckboxTableViewer ctv) {

        this.tv = v;

        this.ctv = ctv;

    }



…… 



    //修改原來的“刪除”Action的run方法

    private class RemoveAction extends Action {

        …… 

        public void run() {

  if (ctv != null) {

     Object[ ] checkObj = ctv.getCheckedElements(); // 取得打勾的記錄

if (checkObj.length == 0) {// 判斷是否有勾選複選框

MessageDialog.openInformation(null, "提示", "請先勾選記錄");

return;

}

for (int i = 0; i < checkObj.length; i++) {

PeopleEntity o = (PeopleEntity) checkObj[i];

ctv.remove(o);// 從表格界面上刪除

List list = (List) tv.getInput();

list.remove(o);// 從數據模型的List容器中刪除

// ....,從數據庫中刪除記錄(略)

}

         } else {

IStructuredSelection s = (IStructuredSelection) tv.getSelection();

if (s.isEmpty()) {// 判斷是否有選擇

MessageDialog.openInformation(null, "提示", "請先選擇");

return;

}

for (Iterator it = s.iterator(); it.hasNext();) {

PeopleEntity o = (PeopleEntity) it.next();

tv.remove(o);// 從表格界面上刪除

List list = (List) tv.getInput();

list.remove(o); // 從數據模型的List容器中刪除

// ....,從數據庫中刪除記錄(略)

}

}            

        }

}



……



//新增的“全選”Action

    private class SelectAllAction extends Action {

        public SelectAllAction() {

            setHoverImageDescriptor(getImageDesc("selectall.gif"));

            setText("全選");

        }

        public void run() {

            if (ctv != null) ctv.setAllChecked(true); //將所有複選框打勾

        }

}



//新增的“全不選”Action

    private class DeselectAction extends Action {

        public DeselectAction() {

            setHoverImageDescriptor(getImageDesc("deselect.gif"));

            setText("全不選");

        }

        public void run() {

            if (ctv != null) ctv.setAllChecked(false); //取消所有複選框打勾

        }

    }



    //修改此方法將“全選”、“全不選”加入

    public void fillActionToolBars(ToolBarManager actionBarManager) {

        …… 

        Action selAllAction = new SelectAllAction();

        Action deselAction = new DeselectAction();

        …… 

        actionBarManager.add(createContributionItem(selAllAction));

        actionBarManager.add(createContributionItem(deselAction));

        actionBarManager.update(true); // 更新工具欄,否則工具欄不顯示任何按鈕

    }

}

14.8  讓表格可直接編輯(CellEditor類、ICellModifier接口)

前面僅僅是顯示錶格數據,本節談談如何修改數據。本節實例效果如圖14.11所示。本節實例在前幾節的代碼基礎上修改完成(完整代碼見配書光盤的TableViewer7.java文件)。

 

圖14.11 CellEditor效果圖

 

14.8.1  設置編輯組件CellEditor

首先在TableViewer主程序前部的變量定義區中創建一個靜態公用的字符串數組,它們就是修改“姓名”列時出現在下拉框中的值。

 

public static String[] NAMES = { "老張", "小紅", "陳剛", "周閱", "陳常恩" };

接着給表格列添加編輯組件CellEditor,在tv.setInput(data)語句之後,加入如下程序塊:

 

// 定義每一列的別名

tv.setColumnProperties(new String[] { "id", "name", "sex", "age", "createdate" });

// 設置每一列的單元格編輯組件CellEditor

CellEditor[] cellEditor = new CellEditor[5];

cellEditor[0] = null;

cellEditor[1] = new ComboBoxCellEditor(tv.getTable(), NAMES, SWT.READ_ONLY);

cellEditor[2] = new CheckboxCellEditor(tv.getTable());

cellEditor[3] = new TextCellEditor(tv.getTable());

cellEditor[4] = null;

tv.setCellEditors(cellEditor);

tv.setCellModifier(new MyCellModifier(tv)); // 設置表格的修改器MyCellModifier

Text text = (Text) cellEditor[3].getControl();// 設置第4列只能輸入數值

text.addVerifyListener(new VerifyListener() { // 以下代碼說明參閱第8.4節“文本框”,完全一樣

public void verifyText(VerifyEvent e) {

String inStr = e.text;

if (inStr.length() > 0) {

e.doit = NumberUtils.isDigits(inStr);

}

}

});

程序說明:表格設置的列別名在修改器MyCellModifier類中要用到。和設置列別名一樣,設置列的CellEditor編輯組件也是用數組的方式,其數組序號和列序號一一對應。

14.8.2  創建修改器ICellModifier

修改器MyCellModifier是最重要的一個類,也是最複雜的一個類,編程時一不小心就容易出BUG。其代碼如下所示:

 

//------------- 文件名:MyCellModifier.java --------------

public class MyCellModifier implements ICellModifier {

private TableViewer
 tv;



public MyCellModifier(TableViewer
 tv) {

this.tv = tv;

}



// 判斷是否可以修改某條記錄的某一字段。這裏返回true表示都可以修改

// 參數element是表格記錄對象,也就是PeopleEntity對象

// 參數property是列別名。該值不會有CellEditor爲null的列,也就是說它不可能爲id,createdate

public boolean canModify(Object element, String property) {

return true;

}



// 此方法決定當單擊單元格出現CellEditor時應該顯示什麼值。參數說明參考canModify方法

// 每種CellEditor要求返回的數據類型都是各不相同的,類型不對應就會出錯

public Object getValue(Object element, String property) {

PeopleEntity o = (PeopleEntity) element;

if (property.equals("name")) {

// ComboBoxCellEditor要求返回姓名在下拉框中的索引值

return getNameIndex(o.getName());

} else if (property.equals("sex")) {

// CheckboxCellEditor要求返回當前值對應的布爾值

return o.isSex();

} else if (property.equals("age")) {

// TextCellEditor要求返回當前值對應的字符串

return String.valueOf(o.getAge());

}

throw new RuntimeException("錯誤的列別名:" + property);

}



private int getNameIndex(String name) {

for (int i = 0; i < TableViewer7.NAMES.length; i++) {

if (TableViewer7.NAMES[i].equals(name))

return i;

}

return -1;

}



// 從CellEditor取值得此單元格的值

// 參數element是表格行對象TableItem,其getData()方法可取得PeopleEntity

// 參數property是列別名

// 參數value是修改後的新值。每種CellEditor的value的數據類型各不相同

public void modify(Object element, String property, Object value) {

TableItem item = (TableItem) element;

PeopleEntity o = (PeopleEntity) item.getData();

// 根據新的修改值更新PeopleEntity對象的數據

if (property.equals("name")) {

// ComboBoxCellEditor的value是其索引值

Integer comboIndex = (Integer) value;

String newName = TableViewer7.NAMES[comboIndex];

o.setName(newName);

} else if (property.equals("sex")) {

// CheckboxCellEditor的value是布爾值

Boolean newValue = (Boolean) value;

o.setSex(newValue);

} else if (property.equals("age")) {

// TextCellEditor的value就是文本框裏的字符

String newValue = (String) value;

if (newValue.equals(""))// 如果不修改

return;

int newAge = Integer.parseInt(newValue);

o.setAge(newAge);

} else {

throw new RuntimeException("錯誤的列別名:" + property);

}

// 更新對象在表格上的界面顯示。也可以用tv.refresh()全面更新界面,但太浪費效率

tv.update(o, null);

}

}

程序說明:

當單擊一個可修改表格列時,首先執行canModify方法來決定是否編輯這條記錄,如果它返回true纔會接着去執行getValue方法,並通過getValue方法決定編輯組件的顯示值。接着用戶在表格上顯示的編輯組件裏進行值的修改,修改完成後,將修改值傳入到modify方法,在此方法中自己編程把新值更新到表格顯示。

在感觀上單元格編輯組件似乎是表格的一部分,但實際上它是作爲單獨組件疊加在表格上的,加上編輯組件種類複雜,所以纔要MyCellModifier這樣的類來作爲編輯組件和表格組件的中間人,進行數據處理和傳遞。

14.9  其他使用技巧

14.9.1  表格記錄的過濾

建立一個繼承自ViewerFilter的類,稱之爲過濾器類。下面的實例建立了一個過濾器,此過濾器的作用是在表格中只顯示姓名叫“陳剛”的記錄。

 

//------------- 文件名:MyFilter.java --------------

public class MyFilter extends ViewerFilter {

// 參數viewer在本例中就是TableViewer
對象

// 參數parentElement 在本例中是一個包含全部記錄的Object數組

// 參數element 當前傳入的記錄,需要判斷是否過濾它

// 返回值=false則此記錄(element)不顯示。true爲顯示

public boolean select(Viewer viewer, Object parentElement, Object element) {

PeopleEntity o = (PeopleEntity) element;

return o.getName().equals("陳剛");

}

}

表格使用過濾器的語句如下所示,也可以把它寫入某Action的run方法中:

 

tv.addFilter(new MyFilter());  //tv是TableViewer
對象

14.9.2  控制表格的當前選擇行

可以將以下語句寫在某個事件代碼中,例如寫在Action的run方法中。

(1)向下移動,到底後又回到第一行。

 

Table table = tv.getTable();

int i = table.getSelectionIndex(); //取得當前所選行的序號,如沒有則返回-1

table.setSelection(i + 1); //當前選擇行移下一行

(2)向上移動,到第一行後又回到最末尾一行。

 

Table table = tv.getTable();

int i = table.getSelectionIndex();

if (i > 0) //是否超過第一行

table.setSelection(i - 1); //向上移

else {

int count = table.getItemCount();  //總的行數

table.setSelection(count - 1);

}

14.9.3  給表格的單元格設置背景色

如下語句將使第1行第2列的單元格背景色變爲紅色(要加在tv.setInput()方法後面)。

 

Table table = tv.getTable(); //tv是一個TableViewer
對象

TableItem item = table.getItem(0); //得到第1行

Color color =Display.getDefault().getSystemColor(SWT.COLOR_RED);//紅色

item.setBackground(1, color); //設置此行的第2列爲紅色

table.redraw(); //重畫界面

14.9.4  加快TableItem和記錄之間的查找速度

用以下語句可以在TableViewer內部爲數據記錄和TableItem之間的映射創建一個哈希表,這樣可以加快TableItem和記錄之間的查找速度,這條語句必須加在setInput方法之前。

 

tv.setUseHashlookup(true);


原址:表格(TableViewer)類

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章