FormLayout通過為小窗口部件創(chuàng)建四邊的Form附加值(attachment)來進(jìn)行工作,并且把這些Form附加值存儲在布局?jǐn)?shù)據(jù)中。一個(gè)附加值讓一個(gè)小窗口部件指定的一邊粘貼(attach)到父Composite的一個(gè)位置或者這個(gè)布局中的另一個(gè)小窗口部件上。這為布局提供了極大的便利,并且這也允許你指定布局中單個(gè)小窗口部件的放置。
FormLayout的可配置域
MarginWidth, MarginHeight
FormLayout中的margin域類似于GridLayout中的相同域。左邊和右邊邊距(margin)被定義成marginWidth,而頂部和底部的邊距被定義成marginHeight。邊距也能根據(jù)每個(gè)小窗口部件的附加值來定義。FormLayout邊距在默認(rèn)情況下為0。
為了設(shè)置這些邊距,我們創(chuàng)建一個(gè)FromLayout,然后設(shè)置它的邊距域。下面這段代碼會在父Composite的四個(gè)邊都設(shè)置5個(gè)象素的邊距。
Display display = new Display ();
Shell shell = new Shell (display);
FormLayout layout= new FormLayout ();
layout.marginHeight = 5;
layout.marginWidth = 5;
shell.setLayout (layout);
FormData對象用于指定FormLayout中的每一個(gè)小窗口部件怎么樣放置。每一個(gè)FormData對象定義了小窗口部件四個(gè)邊的附加值值。這些附加值決定在哪里定位小窗口部件的四個(gè)邊。你可以用setLayoutData方法來設(shè)定一個(gè)小窗口部件的FormData對象。例如:
Button button1 = new Button(shell, SWT.PUSH);
button1.setText("B1");
button1.setLayoutData(new FormData());
但是,這段代碼創(chuàng)建了一個(gè)沒有附加值的FormData對象。在這個(gè)例子中,定義了默認(rèn)的附加值值,這沒有體現(xiàn)出FormLayout的目的和效用。默認(rèn)的附加值值讓小窗口部件粘貼到父Composite的左邊和頂部。如果FormLayout中的每一個(gè)小窗口部件都使用了默認(rèn)的附加值值,它們就會全部被重疊的放置在父Composite的左上角。
Left, Right, Top和Bottom
FormLayout中的left, right, top和bottom域分別指定了與小窗口部件的左邊,右邊,頂部和底部相關(guān)聯(lián)的Form附加值對象。這些域被象下面的例中一樣設(shè)定:
FormData formData = new FormData();
formData.top = new FormAttachment (0,60);
formData.bottom = new FormAttachment (100,-5);
formData.left = new FormAttachment (20,0);
formData.right = new FormAttachment (100,-3);
button1.setLayoutData(formData);
一個(gè)Form附加值對象定義了一個(gè)小窗口部件指定的邊的附加值。有很多方法能定義小窗口部件的邊的粘貼:相對于父Composite的一個(gè)位置,相對于Composite的一個(gè)邊,相對于另一個(gè)小窗口部件的相鄰邊,相對于另一個(gè)小窗口部件的相對邊,或者和另一個(gè)小窗口部件居中。相對于父Composite的一個(gè)位置粘貼放置了小窗口部件的邊,因此通常用相對于父Composite的百分比來表示。如果貼粘到父Composite的邊緣,這個(gè)百分比就是0%或者100%。
相對于另一個(gè)小窗口部件的相鄰邊粘貼保證了小窗口部件的指定邊總是與另一個(gè)小窗口部件的最近邊相鄰。相對于另一個(gè)小窗口部件的相對邊粘貼保證了小窗口部件的指定邊總是與另一個(gè)小窗口部件的最遠(yuǎn)邊對齊。最后,相對于另一個(gè)小窗口部件居中讓小窗口部件在另一個(gè)小窗口部件中居中。這些方法都可以加或者不加偏移值來實(shí)現(xiàn)。

button1.setLayoutData(new FormData());
注意到如果多于一個(gè)小窗口部件被定義為沒有任何附加值的時(shí)候,這些小窗口部件都會被放置到窗口中相同的默認(rèn)位置上,并且會發(fā)生重疊。Form附加值在Form附加值對象這一節(jié)中將進(jìn)行更仔細(xì)的敘述。
Width和Height
FormData中的width和height域用來指定小窗口部件所需的寬度和高度。如果所請求的寬度和高度與附加值設(shè)置的約束發(fā)生沖突,那么這些請求的高度和寬度就不能遵從了。雖然通過設(shè)置附加值也以決定寬度和高度值,但有些情況下你不希望為小窗口部件的所有邊都定義附加值值。在這種情況下,它們就可以很有用的象下面這樣設(shè)定小窗口部件的寬度和高度值:
FormData formData = new FormData(20,30);
formData.top = new FormAttachment (0,60);
formData.left = new FormAttachment (20,0);
button1.setLayoutData(formData);
如果你希望只設(shè)定寬度和高度值,你可以直接設(shè)置FromData對象的寬度和高度值:
FormData formData = new FormData ();
formData.width = 30;
formData.top = new FormAttachment (0,60);
formData.bottom = new FormAttachment (100,-5);
formData.left = new FormAttachment (20,0);
button1.setLayoutData(formData);
注意,如果一個(gè)按鈕粘貼到父Composite的兩邊,當(dāng)這父Composite進(jìn)行縮放時(shí),這個(gè)按鈕會隨著父Composite增大和縮小。
FormAttachment對象
From附加值是一個(gè)用來定義小窗口部件的指定邊的附加值的對象。不需要總是為小窗口部件的四個(gè)邊都設(shè)置附加值。通常只指定一個(gè)或者多個(gè)小窗口部件的邊就能完全指定它的放置。為了更適當(dāng)?shù)姆胖媚愕男〈翱诓考?,你必須至少為FormData中left或者right之一,以及top或者bottom之一定義附加值。如果你只希望粘貼小窗口部件的左邊而不是右邊,那么這個(gè)小窗口部件將用它的邊來進(jìn)行定位,并且這個(gè)小窗口部件會采用它的正常大小(或者當(dāng)設(shè)置了請求大小時(shí),采用它的請求大?。?。如果你沒有貼粘左邊或者右邊,默認(rèn)的定義會把你的小窗口部件粘貼到窗體的左邊。對于頂部和底部也是采用了相同的邏輯。
相對一個(gè)位置進(jìn)行粘貼
存在很多種類型的附加值。第一種就是粘貼到相對于父Composite的一個(gè)位置。通過定義一個(gè)100以內(nèi)的百分值就能實(shí)現(xiàn),例如:

FormData formData = new FormData();
formData.top = new FormAttachment (50,0);
button1.setLayoutData(formData);
這里設(shè)置Button的頂部到相對于父Composite(一個(gè)Shell)高度的50%的位置,偏移量為0。當(dāng)這個(gè)Shell進(jìn)行縮放的時(shí)候,Button的頂部會始終在50%的位置,像這樣:

如果我們選擇設(shè)定一個(gè)偏移值,Button的上部就會被設(shè)置為父Composite的50%加上或者減去為偏移量設(shè)置的象素?cái)?shù)。
我們也能定義不使用百分比定義按鈕的位置,例如:
formData.top = new FormAttachment (30,70,10);
button1.setLayoutData(formData);
如果父Composite的高度被定義為70個(gè)單位,這里設(shè)置了Button的頂部在父Composite頂部以下30個(gè)單位,再加上10個(gè)象素的地方。
相對于父Composite進(jìn)行粘貼
第二種類型的附加值是相對于父Composite的邊緣進(jìn)行粘貼。這與相對于一個(gè)位置進(jìn)行粘貼的實(shí)現(xiàn)方法差不多,但是這個(gè)位置值只能是0%或者100%。當(dāng)處于垂直樣式的時(shí)候,0這個(gè)位置被定義為父Composite的頂部,但處于水平樣式的時(shí)候被定義為左邊。右邊和底部邊緣被定義為100。因此,如果我們要粘貼一個(gè)小窗口部件到父Composite的右邊,我們只需要簡單的創(chuàng)建一個(gè)附加值,然后設(shè)置它的位置值為100:

FormData formData = new FormData();
formData.right = new FormAttachment (100,-5);
button1.setLayoutData(formData);
這里把Button的右邊粘貼到了父Composite(一個(gè)Shell)的右邊緣,并有一個(gè)5個(gè)象素的偏移值。注意,附加值是往一個(gè)方向增加的。如果你要讓一個(gè)小窗口部件向下或者所右偏移,偏移值必須是正的。如果要小窗口部件向上或者向左偏移,這個(gè)偏移值必須是負(fù)的。現(xiàn)在當(dāng)這個(gè)Shell被縮放時(shí),這個(gè)Button就總是偏離右邊緣5個(gè)象素:

相對于另一個(gè)小窗口部件進(jìn)行粘貼
第三種類型的附加值是相對于父Composite中的另一個(gè)控件進(jìn)行粘貼。小窗口部件的邊可以被粘貼到另一個(gè)控件的相鄰邊(默認(rèn)),粘貼到另一個(gè)控件的相對邊,或者小窗口部件可以相對于另一個(gè)控件居中,這些都認(rèn)使用或者不使用偏移值。
相對于另一個(gè)控件進(jìn)行粘貼最常用的方法是粘貼到它的相鄰邊。例如下面的代碼:
FormData formData = new FormData();
formData.top = new FormAttachment (20,0);
button1.setLayoutData(formData);
FormData formData2 = new FormData();
formData2.top = new FormAttachment (button1,10);
button2.setLayoutData(formData2);
把按鈕2的頂部粘貼到了按鈕1的底部,這是因?yàn)榘粹o1的底部和按鈕2的頂部是相鄰的。

注意到當(dāng)窗口被縮放時(shí),按鈕1會移動(dòng)讓它的頂部總是定位在Shell的20%的位置,而按鈕2也是會移動(dòng)讓它的頂部總是在按鈕1的相鄰邊(底部)以下10個(gè)象素的位置。

雖然默認(rèn)的情況下是相對于一個(gè)控件的相鄰邊進(jìn)行粘貼,F(xiàn)romAttachment也能被創(chuàng)建用來相對一個(gè)控件的相對邊進(jìn)行粘貼。當(dāng)要排列小窗口部件的時(shí)候這就非常的有用了。在這種情況下,你可以用TOP, BOTTOM, LEFT或者RIGHT排列(alignment)創(chuàng)建相對于另一個(gè)控件的附加值,例如:
formData2.top = new FormAttachment (button1,0,SWT.TOP);
在接下來的例子中,按鈕1的頂部邊被定位在Shell的20%的地方。按鈕2使用了TOP排列,它的頂部邊和按鈕1的頂部邊對齊。這意味著按鈕2的頂部也被定位在這個(gè)Shell的20%的位置。注意到當(dāng)指定了頂部附加值,只有小窗口部件的垂直放置會被定義。仍然需要為按鈕2設(shè)置左邊附加值以得Button不會發(fā)生重疊。
FormData formData = new FormData(50,50);
formData.top = new FormAttachment (20,0);
button1.setLayoutData(formData);
FormData formData2 = new FormData();
FormData2.left = new FormAttachment (button1,5);
formData2.top = new FormAttachment (button1,0,SWT.TOP);
button2.setLayoutData(formData2);
結(jié)果如下:

最后一個(gè)相對于另一個(gè)控件進(jìn)行粘貼的方法是相對于另一個(gè)控件居中。當(dāng)兩個(gè)控件有不同的大小的時(shí)候,這就顯得很有用了。在這種情況下,你用CENTER排列(aligmnet)創(chuàng)建相對于另一個(gè)控件的附加值,例如:
formData.top = new FormAttachment (button1,0,SWT.CENTER);
這會放置這個(gè)小窗口部件的頂部在一個(gè)允許這個(gè)小窗口部件相對于另一個(gè)控件居中的位置上,并且有一個(gè)值為0的偏移值。只設(shè)定頂部,或者底部,或者它們兩者為居中附加值會導(dǎo)致相同的結(jié)果。這個(gè)小窗口部件的頂部邊不會居中,但是整個(gè)小窗口部件會居中,所以這只需要指定一次。這里有一個(gè)例子:

FormData formData1 = new FormData (50,50);
button1.setLayoutData(formData1)
FormData formData2 = new FormData ();
formData2.left = new FormAttachment (button1,5);
formData2.top = new FormAttachment (button1,0,SWT.CENTER);
button2.setLayoutData (formData2);
使用不同類型的附加值允許布局以不同的方法來定義。FormLayout解決了一些FillLayout,RowLayout或者GridLayout不能解決的問題,讓它成為定義布局很有用的類。
重要的:不要定義循環(huán)的附加值。例如,不要把按鈕1的右邊粘貼到按鈕2的左邊,然后又把按鈕2的左邊粘貼到按鈕1的右邊。這會過度約束布局,導(dǎo)致不可預(yù)知的行為。雖然運(yùn)算會被中斷,但結(jié)果是不可預(yù)知的。因此,確實(shí)保證你沒有過度約束你的小窗口部件。僅提供所需要的附加值來適當(dāng)?shù)姆胖玫男〈翱诓考?/div>
到目前為止,所有使用FormLayout的例子都只是圍繞著一兩個(gè)Button,為了展示FormAttachment是如何工作 的。下面,我們會舉一個(gè)有更多的Button的例子來展示使用附加值如何安排布局。我們會從畫一張描繪我們要?jiǎng)?chuàng)建的附加值的基本草圖開始。

FormData data1 = new FormData();
data1.left = new FormAttachment (0,5);
data1.right = new FormAttachment (25,0);
button1.setLayoutData(data1);
FormData data2 = new FormData();
data2.left = new FormAttachment (button1,5);
data2.right = new FormAttachment (100,-5);
button2.setLayoutData(data2);
FormData data3 = new FormData(60,60);
data3.top = new FormAttachment (button1,5);
data3.left = new FormAttachment (50,-30);
data3.right = new FormAttachment (50,30);
button3.setLayoutData(data3);
FormData data4 = new FormData();
data4.top = new FormAttachment (button3,5);
data4.bottom = new FormAttachment (100,-5);
data4.left = new FormAttachment (25,0);
button4.setLayoutData(data4);
FormData data5 = new FormData();
data5.bottom = new FormAttachment (100,-5);
data5.left = new FormAttachment (button4,5);
button5.setLayoutData(data5);
在這個(gè)例子中,因?yàn)闆]有為按鈕1和按鈕2定義頂部附加值,它們粘貼到了布局的頂部上。鈕3在左邊和右邊使用了百分比和偏移值而在布局中居中。按鈕4和按鈕5粘貼到了布局的底部,并有5個(gè)象素的偏移量。

當(dāng)我們進(jìn)行縮放時(shí),附加值就更顯得明顯了。按鈕1的左邊和右邊都相對進(jìn)行粘貼,所以當(dāng)窗口被縮放時(shí)它也增大了。注意到它的右邊總是在窗口的25%的位置。相同的縮放效果也被應(yīng)用到按鈕2上,因?yàn)樗膬蛇呉捕枷鄬M(jìn)行了粘貼。左邊相對按鈕1進(jìn)行了粘貼,所以它總是會在窗口的25%加上5個(gè)象素的位置。按鈕3水平地保持在窗口的中間。按鈕4的頂部和底部都相對進(jìn)行粘貼,因此當(dāng)窗口被縮放的時(shí)候它垂直地增大了,但是它只相對于左邊進(jìn)行了粘貼而對右邊則沒有,因此水平方向上它沒有增大。按鈕5不會增大或者縮小,但它的左邊會保持在離按鈕4有5個(gè)象素遠(yuǎn),離窗口底部5個(gè)象素遠(yuǎn)的地方。

一個(gè)復(fù)雜的FormLayout的例子
為了舉例說明FormLayout如何能夠用于更復(fù)雜的布局,我們用FormLayout來重做上面為GridLayout舉例的“狗狗展示條目”的例子。這段代碼創(chuàng)建一個(gè)一樣的布局,但是用不同的觀念來實(shí)現(xiàn)它。
我們會從修改為網(wǎng)格布局所作的設(shè)計(jì)草圖開始。我們會畫出如何做,而不僅僅畫出我們希望做成什么樣。這人例子會用到所有的附加值類型。

import org.eclipse.swt.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.layout.*;
public class ComplexFormLayoutExample {
static Display display;
static Shell shell;
static Image dogImage;
static Text dogNameText;
static Combo dogBreedCombo;
static Canvas dogPhoto;
static List categories;
static Text nameText;
static Text phoneText;
public static void main(String[] args){
display = new Display();
shell = new Shell(display);
FormLayout layout= new FormLayout();
layout.marginWidth = 5;
layout.marginHeight = 5;
shell.setLayout(layout);
shell.setText("Dog Show Entry");
Group ownerInfo = new Group(shell, SWT.NONE);
ownerInfo.setText("Owner Info");
FormLayout ownerLayout = new FormLayout();
ownerLayout.marginWidth = 5;
ownerLayout.marginHeight = 5;
ownerInfo.setLayout(ownerLayout);
Label dogName = new Label(shell, SWT.NONE);
dogName.setText("Dog's Name:");
dogNameText = new Text(shell, SWT.SINGLE | SWT.BORDER);
Label dogBreed = new Label(shell, SWT.NONE);
dogBreed.setText("Breed:");
dogBreedCombo = new Combo(shell, SWT.NONE);
dogBreedCombo.setItems(new String [] {"Collie", "Pitbull", "Poodle", "Scottie"});
Label photo = new Label(shell, SWT.NONE);
photo.setText("Photo:");
dogPhoto = new Canvas(shell, SWT.BORDER);
Button browse = new Button(shell, SWT.PUSH);
browse.setText("Browse...");
Button delete = new Button(shell, SWT.PUSH);
delete.setText("Delete");
Label cats = new Label (shell, SWT.NONE);
cats.setText("Categories");
categories = new List(shell, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
categories.setItems(new String [] {
"Best of Breed", "Prettiest Female", "Handsomest Male",
"Best Dressed", "Fluffiest Ears", "Most Colors",
"Best Performer", "Loudest Bark", "Best Behaved",
"Prettiest Eyes", "Most Hair", "Longest Tail",
"Cutest Trick"});
Button enter = new Button(shell, SWT.PUSH);
enter.setText("Enter");
FormData data = new FormData();
data.top = new FormAttachment (dogNameText,0,SWT.CENTER);
dogName.setLayoutData(data);
data = new FormData();
data.left = new FormAttachment (dogName,5);
data.right = new FormAttachment (100,0);
dogNameText.setLayoutData(data);
data = new FormData();
data.top = new FormAttachment (dogBreedCombo,0,SWT.CENTER);
dogBreed.setLayoutData(data);
data = new FormData();
data.top = new FormAttachment (dogNameText,5);
data.left = new FormAttachment (dogNameText,0,SWT.LEFT);
data.right = new FormAttachment (categories,-5);
dogBreedCombo.setLayoutData(data);
data = new FormData(80,80);
data.top = new FormAttachment (dogBreedCombo,5);
data.left = new FormAttachment (dogNameText,0,SWT.LEFT);
data.right = new FormAttachment (categories,-5);
data.bottom = new FormAttachment (ownerInfo,-5);
dogPhoto.setLayoutData(data);
dogPhoto.addPaintListener(new PaintListener() {
public void paintControl(final PaintEvent event) {
if (dogImage != null) {
event.gc.drawImage(dogImage, 0, 0);
}
}
});
data = new FormData();
data.top = new FormAttachment (dogPhoto,0,SWT.TOP);
photo.setLayoutData(data);
data = new FormData();
data.top = new FormAttachment (photo,5);
data.right = new FormAttachment (dogPhoto, -5);
browse.setLayoutData(data);
browse.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
String fileName = new FileDialog(shell).open();
if (fileName != null) {
dogImage = new Image(display, fileName);
}
}
});
data = new FormData();
data.left = new FormAttachment (browse,0,SWT.LEFT);
data.top = new FormAttachment (browse,5);
data.right = new FormAttachment (dogPhoto, -5);
delete.setLayoutData(data);
delete.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
if (dogImage != null) {
dogImage.dispose();
dogImage = null;
dogPhoto.redraw();
}
}
});
data = new FormData(90,140);
data.top = new FormAttachment (dogPhoto,0,SWT.TOP);
data.right = new FormAttachment (100,0);
data.bottom = new FormAttachment (enter,-5);
categories.setLayoutData(data);
data = new FormData();
data.bottom = new FormAttachment (categories,-5);
data.left = new FormAttachment (categories,0,SWT.CENTER);
cats.setLayoutData(data);
data = new FormData();
data.right = new FormAttachment (100,0);
data.bottom = new FormAttachment (100,0);
enter.setLayoutData(data);
enter.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
System.out.println("\nDog Name: " + dogNameText.getText());
System.out.println("Dog Breed: " + dogBreedCombo.getText());
System.out.println("Owner Name: " + nameText.getText());
System.out.println("Owner Phone: " + phoneText.getText());
System.out.println("Categories:");
String cats[] = categories.getSelection();
for (int i = 0; i < cats.length; i++) {
System.out.println("\t" + cats[i]);
}
}
});
data = new FormData();
data.bottom = new FormAttachment (enter,-5);
data.left = new FormAttachment (0,0);
data.right = new FormAttachment (categories,-5);
ownerInfo.setLayoutData(data);
Label name = new Label(ownerInfo, SWT.NULL);
name.setText("Name:");
Label phone = new Label(ownerInfo, SWT.PUSH);
phone.setText("Phone:");
nameText = new Text(ownerInfo, SWT.SINGLE | SWT.BORDER);
phoneText = new Text(ownerInfo, SWT.SINGLE | SWT.BORDER);
data = new FormData();
data.top = new FormAttachment (nameText,0,SWT.CENTER);
name.setLayoutData(data);
data = new FormData();
data.top = new FormAttachment (phoneText,0,SWT.CENTER);
phone.setLayoutData(data);
data = new FormData();
data.left = new FormAttachment (phone,5);
data.right = new FormAttachment (100,0);
nameText.setLayoutData(data);
data = new FormData();
data.left = new FormAttachment (nameText,0,SWT.LEFT);
data.right = new FormAttachment (100,0);
data.top = new FormAttachment (55,0);
phoneText.setLayoutData(data);
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}

當(dāng)這個(gè)窗口被縮放時(shí),象GridLayout的例子中一樣,相同的控件會被縮放。

編寫你自己的布局類
有時(shí)候,你可能需要編寫你自己的布局類。有可能你的布局的需求很復(fù)雜?;蛘吣阌行┑胤侥憧赡苄枰嗤耐庥^,而你希望改進(jìn)代碼的重用率。或者你希望擴(kuò)大領(lǐng)域知識來創(chuàng)建一個(gè)很有效率的布局類。不管原因是什么,在編寫一個(gè)新的類之前,有幾件事情是你要考慮的:
這個(gè)布局能夠用GridLayout或者FormLayout,以及可能是一些嵌套布局來實(shí)現(xiàn)嗎?
希望達(dá)到的效果可以更容易的用一個(gè)縮放監(jiān)聽器來實(shí)現(xiàn)嗎?
你定義了一個(gè)通用的布局算法還是只是布置了小窗口部件?
首先,我們會先來看看布局是怎么樣工作的,然后我們再創(chuàng)建一個(gè)新的布局類。另一個(gè)編寫你自己的而已的例子可以在“用SWT創(chuàng)建你自己的小窗口部件”(Creating Your Own Widgets Using SWT)中的“復(fù)合小窗口部件示例”(Compound Widget Example)一節(jié)中找到,它顯示了如何用一個(gè)縮放監(jiān)聽器或者一個(gè)新布局類達(dá)到相同的外觀。
布局是如何工作的
Layout是所有布局的超類。它只有兩個(gè)方法computeSize和layout。這個(gè)類的定義如下:
public abstract class Layout {
protected abstract Point computeSize(Composite composite, int widthHint, int heightHint, boolean flushCache);
protected abstract void layout(Composite composite, boolean flushCache);
}
一旦這個(gè)Composite的子組件被確定大小并根據(jù)布局類中的編寫的布局算法來放置的時(shí)候,computeSize方法計(jì)算包括所有這些子組件的矩形區(qū)域的寬度和高度。hint參數(shù)允許寬度和/或者高度被約束。例如,如果一個(gè)維被約束,一個(gè)布局可能選擇在另一個(gè)維上增長。一個(gè)SWT.DEFAULT的hint意思是使用合適的(preffed)大小。
既然一個(gè)布局控制著小窗口部件在一個(gè)Composite中的大小和放置,Composite中也有一些方法可以和布局一起使用。
public void setLayout(Layout layout);
public Layout getLayout();
一個(gè)應(yīng)用程序可以強(qiáng)制一個(gè)布局重新計(jì)算子組件的大小,并通過調(diào)用父Composite的layout()方法重新定位這些子組件。
public void layout(boolean changed);
public void layout();
// calls layout(true);
你需要在你改變了任何子組件,可能導(dǎo)致子組件的大小和位置發(fā)生改變的時(shí)候做這件事情,比如改變了子組件的字體,改變了子組件的文本或者圖片,增加了新的子組件,或者為子組件增加了子組件。(如果子組件可以適應(yīng)這個(gè)變化,布局可能不會成功—例如,改變一個(gè)可滾動(dòng)的多行的Text的字體或者文本)。既然這些改變都是通過編程來實(shí)現(xiàn)的,它們不會觸發(fā)事件。從而,父構(gòu)件不會知道這些變化,必須通過layout方法來告許它。這個(gè)策略導(dǎo)致了閃動(dòng),這是因?yàn)閼?yīng)用程序可能作了好幾個(gè)改變?nèi)缓蟛鸥嬖V父構(gòu)件重新布局,并且子組件只被重畫一次而不是每次改變都重畫一次。如果沒有調(diào)用layout()并改變是在shell打開后發(fā)生的,那么子組件可能不會被正確的放置直到shell由于某種原因進(jìn)行了縮放。注意shell.open()導(dǎo)致一個(gè)布局過程的發(fā)生。
Composite的computeSize方法計(jì)算Composite的合適大小,這是它由這個(gè)布局決定的客戶區(qū)(client)的大小加上它的修剪(trim)。
public Point computeSize(int widthHint, int heightHint, boolean changed);
public Point computeSize(int widthHint, int heightHint);
// calls computeSize(widthHint, heightHint, true);
Composite的clientArea是包含所有的子組件的矩形區(qū)。一個(gè)布局在客戶區(qū)中放置子組件。
public Rectangle getClientArea ();
Composite的trim(修剪)是客戶區(qū)外的區(qū)域。對于一些Composite,修剪的大小是零。修剪可以通過傳遞客戶區(qū)的尺寸給computeTrim計(jì)算出來。
public Rectangle computeTrim (int x, int y, int width, int height);
調(diào)用Composite的pack方法將把它縮放到它的合適大小。
public void pack(boolean changed);
// calls setSize(computeSize(SWT.DEFAULT, SWT.DEFAULT, changed));
public void pack();
// calls pack(true);
layout,computeSize和pack方法的布爾型參數(shù)是changed標(biāo)志。如果為true,它表明Composite的內(nèi)容已經(jīng)被通過某些方法改變而影響了它的合適大小,所以布局所持有的任何緩存都需要被沖洗。當(dāng)一個(gè)Composite被縮放時(shí),它調(diào)用layout(false)來讓自己的布局安排它的子組件;因此小窗口部件的內(nèi)容緩存沒有被沖洗掉。這讓布局只在必要的進(jìn)要的時(shí)候進(jìn)行代價(jià)高昂的計(jì)算。
緩存可以改進(jìn)性能,但它也可能是有欺騙性的。你可以選擇完全不緩存 — 事實(shí)上,最好不要試力用緩存,直到你的代碼已經(jīng)穩(wěn)定。當(dāng)你考慮緩存什么時(shí),確保你沒有緩存任何小窗口部件狀態(tài),例如一個(gè)標(biāo)簽的文本,或者一個(gè)列表的項(xiàng)的數(shù)目。
如果在你的應(yīng)用程序中你有一些垂直導(dǎo)向的Composite小窗口部件,你可能會選擇編寫ColumnLayout。我們將會展示一個(gè)簡單的布局類版本,它把Composite子組件放置到一個(gè)單列中。這個(gè)類有固定的修剪和間距。子組件被賦予相同的寬度,但它們能有自己自然的高度。(注意,在*2.0*中,RowLayout已經(jīng)被擴(kuò)展為有ColumnLayout的功能,如果它的類型被設(shè)為SWT.VERTICAL的話。正因?yàn)槿绱?,這個(gè)例子只是作為一個(gè)例子。事實(shí)上,如果你現(xiàn)在需要把小窗口部件放在一列中,你可以用RowLayout來實(shí)現(xiàn)。)
這段ColumnLayout類的例子就在下面。注意,我們緩存了小窗口部件子組件的寬度,子組件的高度和(加上了間距),而且這些值被用來計(jì)算大小和放置子組件。如果flushCache是true,它們會被重新計(jì)算。
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.layout.*;
public class ColumnLayout extends Layout {
// fixed margin and spacing
public static final int MARGIN = 4;
public static final int SPACING = 2;
// cache
Point [] sizes;
int maxWidth, totalHeight;
protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
Control children[] = composite.getChildren();
if (flushCache || sizes == null || sizes.length != children.length) {
initialize(children);
}
int width = wHint, height = hHint;
if (wHint == SWT.DEFAULT) width = maxWidth;
if (hHint == SWT.DEFAULT) height = totalHeight;
return new Point(width + 2 * MARGIN, height + 2 * MARGIN);
}
protected void layout(Composite composite, boolean flushCache) {
Control children[] = composite.getChildren();
if (flushCache || sizes == null || sizes.length != children.length) {
initialize(children);
}
Rectangle rect = composite.getClientArea();
int x = MARGIN, y = MARGIN;
int width = Math.max(rect.width - 2 * MARGIN, maxWidth);
for (int i = 0; i < children.length; i++) {
int height = sizes[i].y;
children[i].setBounds(x, y, width, height);
y += height + SPACING;
}
void initialize(Control children[]) {
maxWidth = 0;
totalHeight = 0;
sizes = new Point [children.length];
for (int i = 0; i < children.length; i++) {
sizes[i] = children[i].computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
maxWidth = Math.max(maxWidth, sizes[i].x);
totalHeight += sizes[i].y;
}
totalHeight += (children.length - 1) * SPACING;
}
}
這里有一些簡單的測試代碼來測試ColumnLayout。Grow和shrink按鈕展示了在改變了其中一個(gè)子組件的寬度后調(diào)用Shell的layout()方法強(qiáng)制一個(gè)重新布局的過程。調(diào)用layout()和調(diào)用layout(true)是一樣的,它告訴ColumnLayout在設(shè)定子組件的邊界的時(shí)候沖掉它的緩存。Shell在放置好子組件后也調(diào)用pack()方法。這強(qiáng)制讓Shell采用新的大小。
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.events.*;
public class ColumnLayoutTest {
static Shell shell;
static Button button3;
public static void main(String[] args) {
Display display = new Display();
shell = new Shell(display);
shell.setLayout(new ColumnLayout());
new Button(shell, SWT.PUSH).setText("B1");
new Button(shell, SWT.PUSH).setText("Very Wide Button 2");
(button3 = new Button(shell, SWT.PUSH)).setText("Button 3");
Button grow = new Button(shell, SWT.PUSH);
grow.setText("Grow Button 3");
grow.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
button3.setText("Extreemely Wide Button 3");
shell.layout();
shell.pack();
}
});
Button shrink = new Button(shell, SWT.PUSH);
shrink.setText("Shrink Button 3");
shrink.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
button3.setText("Button 3");
shell.layout();
shell.pack();
}
});
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) display.sleep();
}
}
}
如果我們運(yùn)行這段測試代碼,窗口會像左圖看起來那樣。按下Grow Button 3按鈕會導(dǎo)致窗口如右圖所示。用鼠標(biāo)縮放窗口的大小也能使按鈕變寬(或者變窄)但是它們不會變高。


重載Composite
如果你正在編寫自己的小窗口部件,就像“用SWT創(chuàng)建你自己的小窗口部件”(Creating Your Own Widgets Using SWT)中描述的那樣,并且你子類化了Composite,那么你的實(shí)現(xiàn)要幾點(diǎn)要考慮:
如果在你的新Composite中提供了修剪(trimming),確保你重載了computeTrim和getClientArea。
絕對不要重載layout(),但是你可以重載layout(boolean)。
重載setLayout讓它不做任何事。
重載layout(boolean)讓它調(diào)用你的布局代碼。
重載computeSize讓它正確的計(jì)算出你的Composite的大小。
概要
SWT提供了很多不同的方法來放置小窗口部件。最簡單的方法并且是你會最常使用到的方法,就是用這些標(biāo)準(zhǔn)布局類的其中一個(gè):FillLayout,RowLayout,GridLayout或者FormLayout。