設計
增加一些內容
現在我們已經有一個view能夠成功運行了.我們可以往裡面增加一些內容.Eclipse forms有一個body,我們可以這樣建立內容.
public void createPartControl(Composite parent) { toolkit = new FormToolkit(parent.getDisplay()); form = toolkit.createForm(parent); form.setText("Hello, Eclipse Forms"); GridLayout layout = new GridLayout(); form.getBody().setLayout(layout); Hyperlink link = toolkit.createHyperlink(form.getBody(), "Click here.", SWT.WRAP); link.addHyperlinkListener(new HyperlinkAdapter() { public void linkActivated(HyperlinkEvent e) { System.out.println("Link activated!"); } }); } form的body是標題下面的可用空間,因為這個空間是一個SWT Composite,它能做為其它組件的parent。在上面的代碼裡,我們為body設定了layout,然後建立了一個超連結。超連結是由Eclipse Forms提供的為數不多的組件之一,我們可以為超連結增加監聽器,這樣能夠在使用者點擊它時做出反應。
升級後的視圖應該看起來象這樣:
圖3:一個有超連結的簡單form.
超連結組(Hyperlink Groups)
Form tookit有一個"超連結組"對象.每個建立出的超連結都加入這個組對象中.超連結為多個角色服務.它們定義了這個組中所有超連結在正常、hover、啟用不同狀態下的顏色.它們根據小組中連結不同的狀態來改變顏色.它們根據小組中連結不同的狀態來改變底線風格.
當你要改變超連結組對象的預設設定時,可以通過toolkit的getHyperlinkGroup()方法來獲得超連結組對象.
建立普通組件
Eclipse Forms的一個設計目標就是讓能夠在編輯器/視圖中建立普通SWT組件.因為form的body是一個普通composite,你能夠在它裡面使用任何layout和組件.但是,記住"原生的"SWT組件有一個組件背景.我們現在通過它們的構造方法建立一些組件.
layout.numColumns = 2; GridData gd = new GridData(); gd.horizontalSpan = 2; link.setLayoutData(gd); Label label = new Label(form.getBody(), SWT.NULL); label.setText("Text field label:"); Text text = new Text(form.getBody(), SWT.BORDER); text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); Button button = new Button(form.getBody(), SWT.CHECK); button.setText("An example of a checkbox in a form"); gd = new GridData(); gd.horizontalSpan = 2; button.setLayoutData(gd); 現在我們使用了兩列,並且建立了一個標籤(label),一個文字框(text field)和一個複選框(checkbox).結果如下:
圖4:一個擁有直接用它們的構造器建立出的SWT組件的form
這張圖片怎麼回事?我們建立的組件的背景直接和系統視窗背景相匹配,而不是和form的背景匹配.另外,文字框看起來還好是因為這張截圖是在Windows XP下截的.在其它作業系統上,它會看起來是有一個3D邊框的空白條.為瞭解決這個問題,我們會用toolkit的Factory 方法來建立這些組件:
Label label = toolkit.createLabel(form.getBody(), "Text field label:"); Text text = toolkit.createText(form.getBody(), ""); text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); Button button = toolkit.createButton(form.getBody(), "A checkbox in a form", SWT.CHECK); gd = new GridData(); gd.horizontalSpan = 2; button.setLayoutData(gd);
這個視圖現在會看來更好些了:
圖5:一個擁有用form toolkit的Factory 方法建立出的SWT組件的form
由form toolkit提供的Factory 方法是為了方便。Toolkit沒有函蓋所有情況,甚至是SWT組件集合,而且明顯沒有為你可能有的自己定製的組件提供這一便利.當你需要使一個SWT組件與form統一時,你應該使用一個方法:FormToolkit.adapt(Control control, boolean trackFocus,boolean trackKeyboard).所有的Factory 方法各自分別調用了這個適配方法。
達到"平滑"的視覺效果
一個在PDE編輯器中Eclipse Forms的可看出的屬性是組件的"平滑"視覺效果.以前所有沒有3D邊框的組件在視窗中看起來不錯,但是在編輯器或視圖中就不行.這個支援寫在FormToolkit類中.但是,在一些系統上它是通過一些定製達到的.舉個例子,看一下這張從PDE編輯器(2.1版本)中的截圖:
圖片6:Eclipse 2.1中Eclipse Forms的平滑視覺效果.
象表格,文本類,複選框等,是加上一個平滑的1個象素寬的邊框.這些邊框不是來自組件自己(SWT.BORDER風格沒有用到).另外,如果設定一下,tookit會為每個組件的parent增加一個paint監聽器,在paint事件發生時為組件加上邊框.要想這樣的話,你需要為你建立的象文本,表格,樹等組件的每個composite調用paintBordersFor(parent).每個parent只需要調用一次方法就足夠了:不需要為每個組件這樣調用.
Form toolkit知道哪個組件需要一個定製的邊框.但是,你可能新建立了一個不在原來名單中的組件,它也需要一個邊框.你可以通過象下面這樣的代碼給toolkit一個提示:
Control myControl = new MyControl(parent); myControl.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER); // or myControl.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TREE_BORDER); toolkit.paintBordersFor(parent);
你可以在上面這張圖中看出,象樹和表格這樣的"結構化(structural)"的組件有和文本地區不同的邊框風格並且你可以選擇和你的組件相符合的那個.注意用toolkit的Factory 方法建立出的組件不需要這樣做.
因為Eclipse 3.0和在Windows XP上,當javaw.exe.manifest檔案在Java虛擬機器bin檔案夾中時,沒有邊框產生出來.(這篇文章裡我所有截圖都是在Windows XP下截的).這個檔案--你可以在
SWT home page上下載--告訴toolkit為本機群組件使用XP皮膚.用XP皮膚,象文本,表格和樹是已經平滑的並且不需要再去調整.為了讓你的代碼擁有可移植性,你可以總是調用paintBordersFor(Composite)方法,讓toolkit去根據不同作業系統來決定該怎麼做。
定製布局(Custom layouts)
Eclipse Forms在SWT layout的基礎上增加了兩個新的layout,這些layout繼承了SWT Layout基類並能夠在任何SWT composite上使用,但是一般都是和Eclipse Forms聯合使用的。
TableWrapLayout
現在我們知道如何來組合一個form,讓我們先給一個懸念.我們會改變那個超連結文本使它加長一些:
link.setText("This is an example of a form that is much longer "+
"and will need to wrap.");
讓我們看看結果:
圖片7:一個使用GridLayout的form
發生什麼事了?記住我們使用的是GridLayout,當它問這個超連結組件來計算它大小時,超連結告訴它文字在單獨行裡需要的長度,雖然我們告訴組件去包裹(wrap),它並沒有這樣做因為GridLayout需要組件返回它的長度。超串連組件--和其它象Label一樣的SWT組件,可以通過你傳遞它數值來決定長和寬,但是GridLayout不會向組件傳遞數值參數。
我們需要的是一個象HTML表格一樣的layout.我們希望內容去試圖配合提供的客戶空間,並且一行行地疊加.Eclipse Forms提供了一個這樣的layout叫TableWrapLayout.GridLayout和TableWrapLayout之間有許多共同點.都是用表格來組織parent的children.都有layout data來告訴layout如何對待每個組件.都能夠在需要佔據所有空間時等接受組件的提示.
但是,它們在布局時完全不同.TableWrapLayout從列開始.它計算每列的最小的,合適的,最大的寬度並用這個資訊來佔據空間.它也試圖儘可能地公平地在各列間分隔,這樣有些組件就沒有多餘的空間.
讓我們使用TableWrapLayout來重新更改例子(更改處高亮顯示):
public void createPartControl(Composite parent) { toolkit = new FormToolkit(parent.getDisplay()); form = toolkit.createForm(parent); form.setText("Hello, Eclipse Forms"); TableWrapLayout layout = new TableWrapLayout(); form.getBody().setLayout(layout); Hyperlink link = toolkit.createHyperlink(form.getBody(),"Click here.", SWT.WRAP); link.addHyperlinkListener(new HyperlinkAdapter() { public void linkActivated(HyperlinkEvent e) { System.out.println("Link activated!"); } }); link.setText("This is an example of a form that is much longer and will need to wrap."); layout.numColumns = 2; TableWrapData td = new TableWrapData(); td.colspan = 2; link.setLayoutData(td); Label label = toolkit.createLabel(form.getBody(), "Text field label:"); Text text = toolkit.createText(form.getBody(), ""); td = new TableWrapData(TableWrapData.FILL_GRAB); text.setLayoutData(td); Button button = toolkit.createButton(form.getBody(), "A checkbox in a form", SWT.CHECK); td = new TableWrapData(); td.colspan = 2; button.setLayoutData(td); }
我們用了GridData相同的概念.一些變數擁有不同的名字(舉個例子,colspan和rowspan,align和valign取自HTML TABLE的屬性),但是你可以做相同的事--建立一個超連結和按鈕佔據兩列的格子.因為空白(margins)和GridLayout是相同的,結果會看起來一樣,除了超連結現在會包裹起來:
圖片8:一個使用TableWrapLayout的form
TableWrapLayout和GridLayout的一個主要的不同點是你應該停止去計算垂直的空間.在GridLayout裡,你一般會讓"不易變形的(rigid)"組件用自然的位置和大小並讓"可伸縮的(flexible)"組件去佔據水平或垂直的空間.相反,TableWrapLayout是從上往下工作的,並且它容下所有的組件,它的工作是完全的.佔據水平空間的概念還存在(象上面展示一樣).但是,垂直方向上,你只能在單元(cell)比組件高時選擇FILL單元,或選擇TOP,MIDDLE或BOTTOM垂直對齊.
你也許會注意到一個地方和剛剛說的不符:TableWrapLayout仍然有一個grabVertical變數.但是,這裡這個變數在這裡有明確的目的:當一個設定了高度的組件佔多行時,它的高度會給出一個條件就是垂直dimension已經知道了,組件需要去除它所在的多個單元之間的多餘空間.
為了用TableWrapLayout有好的結果,確定組件可以接近風格(SWT.WRAP).Eclipse Froms提供的組合定製組件能夠在box外麵包裹.這是通過實現ILayoutExtension介面實現的:
public interface ILayoutExtension {
/**
* Computes the minimum width of the parent. All widgets capable of word
* wrapping should return the width of the longest word that cannot be
* broken any further.
*
* @param parent the parent composite
* @param changed <code>true</code> if the cached information should be
* flushed, <code>false</code> otherwise.
* @return the minimum width of the parent composite
*/
public int computeMinimumWidth(Composite parent, boolean changed);
/**
* Computes the maximum width of the parent. All widgets capable of word
* wrapping should return the length of the entire text with wrapping
* turned off.
*
* @param parent the parent composite
* @param changed <code>true</code> if the cached information
* should be flushed, <code>false</code> otherwise.
* @return the maximum width of the parent composite
*/
public int computeMaximumWidth(Composite parent, boolean changed);
}
TableWrapLayout本身實現了這個介面,這樣讓它處理當composites的layout是這個composite的parent的children的情況.另外的兩個方法能夠計算兩種極端情況--當所有組件儘可能寬地布滿時的顯然的最小寬度和最大寬度.兩者的不同是使列之間的多餘空間儘可能小時貢獻出公平的空間.
讓我們看清楚貢獻空間是怎樣的.我們會推薦我們到現在為止寫的代碼並象下面這樣做出修改:
layout.numColumns = 3; Label label; TableWrapData td; label = toolkit.createLabel(form.getBody(), "Some text to put in the first column", SWT.WRAP); label = toolkit.createLabel(form.getBody(), "Some text to put in the second column and make it a bit "+ "longer so that we can see what happens with column "+ distribution. This text must be the longest so that it can "+ "get more space allocated to the columns it belongs to.", SWT.WRAP); td = new TableWrapData(); td.colspan = 2; label.setLayoutData(td); label = toolkit.createLabel(form.getBody(), "This text will span two rows and should not grow the column.", SWT.WRAP); td = new TableWrapData(); td.rowspan = 2; label.setLayoutData(td); label = toolkit.createLabel(form.getBody(), "This text goes into column 2 and consumes only one cell", SWT.WRAP); label.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); label = toolkit.createLabel(form.getBody(), "This text goes into column 3 and consumes only one cell too", SWT.WRAP); label.setLayoutData(new TableWrapData(TableWrapData.FILL)); label = toolkit.createLabel(form.getBody(), "This text goes into column 2 and consumes only one cell", SWT.WRAP); label.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); label = toolkit.createLabel(form.getBody(), "This text goes into column 3 and consumes only one cell too", SWT.WRAP); label.setLayoutData(new TableWrapData(TableWrapData.FILL)); form.getBody().setBackground(form.getBody().getDisplay(). getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
這個建立了一些有不同長度文本的label.有些labels佔據多列,有些佔據多行.為了讓測試更加簡單,我們把form的背景設定為組件背景,這樣單元能夠更簡單看出來.當我們運行例子,我們會得到下面的樣子:
圖片9:TableWrapLayout留下的多餘空間
關鍵之處在於組件最小寬度和最大寬度相比較時.相差越多,結果越明顯,會看到列中有很大的縫隙.佔據的寬度是組件所需要的最小的寬度.注意第3列比第2列稍微寬一點點,這是因為第3列中的文字長度比第2列中文字要長點.如果需要閱讀布局的相關理論,可以去這裡W3C recommendations for HTML table auto-layout.
ColumnLayout
Eclipse Forms另外一個定製的layout是SWT RowLayout的變種.如果我們把RowLayout上的children垂直放置時--按列-並且使同列中的所有組件擁有相同的寬度,我們會由組件的寬度得到多個列.但是,最後一列顯然不是被填滿的--這由組件的個數決定.如果在form中,我們會仍然看到所有組件在一列中,因為RowLayout不能夠"垂直" 包裹.如果我們用GridLayout相替代的話,列的數量由我們自己決定.
在更加複雜的forms中我們需要列的數量按情況變化.換句話來說,我們希望數字按照form的寬度來改變--有可能需要更多的列,當寬度減小時則把數字減小.而且我們希望所有列的長度象報紙布局一樣相同.這些要求都能夠通過ColumnLayout來達到.
與TableWrapLayout相比,ColumnLayout更加簡單.不需要複雜的設定.唯一需要你設定的就是列數的範圍,預設是1到3.下面的例子示範了一個使用ColumnLayout的有許多段落(sections)的form.初始時,只需要兩列就可以放置所有段落了.如果我們把編輯器變窄一點,layout會使它們在同一列中.
圖片10:使用ColumnLayout的所有按列排列的段落.layout開始是兩列,但是按照空間的改變變成了一列.