Dialog是SWT和JFace的一個(gè)重要的組成部分,我們?cè)陂_發(fā)Plug-in或RCP的時(shí)候也經(jīng)常會(huì)用到它們。這篇隨筆不會(huì)介紹 SWT的Dialog,因?yàn)槲蚁牒芏嗳硕家呀?jīng)非常熟悉它了。在這里,我要討論的是JFace的Dialog,或者更進(jìn)一步說(shuō)是JFace的 TitleAreaDialog。什么是TitleAreaDialog呢?想想我們常常用到的New XX Wizard就知道了。在我們創(chuàng)建一個(gè)Java Project或Class的時(shí)候,我們所使用的Wizard其實(shí)就是由TitleAreaDialog構(gòu)成的。這種Dialog有如下所示的 TitleArea和一個(gè)標(biāo)準(zhǔn)的Button Bar:

正常的TitleArea 帶有錯(cuò)誤信息的TitleArea

標(biāo)準(zhǔn)的Button Bar
這種GUI的表現(xiàn)力要比SWT的Dialog強(qiáng)很多,而且JFace為該 Dialog封裝了很多東西,這也使開發(fā)工作變得更加簡(jiǎn)單,所以我極力推薦使用TitleAreaDialog。那么讓我們來(lái)看一個(gè)最基本的 TitleAreaDialog:
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.jthin.jpssp.ide.configuration.Activator;
public class MyTitleAreaDialog extends TitleAreaDialog {
/**
* Create the dialog
*
* @param parentShell
*/
public MyTitleAreaDialog(Shell parentShell) {
super(parentShell);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.dialogs.TitleAreaDialog#createDialogArea(org.eclipse.swt.widgets.Composite)
*/
protected Control createDialogArea(Composite parent) {
Composite area = (Composite) super.createDialogArea(parent);
Composite container = new Composite(area, SWT.NONE);
container.setLayoutData(new GridData(GridData.FILL_BOTH));
// TitleArea中的Title
setTitle("My TitleAreaDialog");
// TitleArea中的Message
setMessage("This is a simple TitleAreaDialog example.");
// TitleArea中的Image
setTitleImage(ResourceManager.getPluginImage(Activator.getDefault(), "icons/Neptune.png"));
return area;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(org.eclipse.swt.widgets.Composite)
*/
protected void createButtonsForButtonBar(Composite parent) {
createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.dialogs.TitleAreaDialog#getInitialSize()
*/
protected Point getInitialSize() {
return new Point(500, 375);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
*/
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
// Dialog Title
newShell.setText("Test TitleAreaDialog Title");
// Dialog Icon
newShell.setImage(ResourceManager.getPluginImage(Activator.getDefault(), "icons/Neptune.png"));
}
}
這段代碼非常容易理解,從方法簽名中可以看出每個(gè)方法做了什么事情。注意createButtonsForButtonBar方法,其中用createButton方法創(chuàng)建了OK和Cancel這兩個(gè)Button,并且把Button的默認(rèn)點(diǎn)擊事件也寫好了,就是關(guān)閉該 Dialog。ResourceManager.getPluginImage是我自己編寫的獲得圖片的helper method,這里就不討論其實(shí)現(xiàn)了。這段代碼會(huì)產(chǎn)生如下的Dialog:

有趣的是,我在這里故意使用了一個(gè)128×128的大圖標(biāo), TitleAreaDialog不會(huì)自動(dòng)縮小或裁減Image,而是調(diào)整TitleArea的大小來(lái)適應(yīng)Image。
接下來(lái)我們要為OK Button編寫我們自己的事件,例如把用戶在Dialog中的輸入保存到某處。有人可能會(huì)想到為OK Button添加SelectionListener,但實(shí)際上這樣做是不對(duì)的,因?yàn)镺K Button是JFace為Dialog封裝好了的,同時(shí)JFace也提供了響應(yīng)的callback:
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.dialogs.Dialog#okPressed()
*/
protected void okPressed() {
// implement your own function here
super.okPressed();
}
我們可以在這里實(shí)現(xiàn)我們自己的事件,不過(guò)最后一定要調(diào)用super.okPressed方法,否則Dialog就不會(huì)關(guān)閉了。
OK,以上就是TitleAreaDialog的基本Framework,非常容易理解,下面我們就來(lái)在 TitleArea中動(dòng)態(tài)設(shè)置一些信息。你可以把這個(gè)scenario想象成在用戶輸入的同時(shí)提示用戶輸入的合法性。TitleAreaDialog提供了好3個(gè)方法可以動(dòng)態(tài)設(shè)置TitleArea信息,具體如下:
- public void setErrorMessage(String newErrorMessage):顯示傳入的錯(cuò)誤信息。(我們把用這個(gè)方法設(shè)置的信息叫做error message。)當(dāng)前顯示的信息會(huì)被保存起來(lái),等到error message清空之后會(huì)再次顯示,而清空error message要傳入null,而不是傳入空字符串。
- setMessage(String newMessage):顯示傳入的信息,等同于setMessage(String newMessage, IMessageProvider.NONE)。如果當(dāng)前顯示的是error message,那么newMessage會(huì)被保存起來(lái),等到error message清空后再顯示。
- setMessage(String newMessage, int newType):顯示傳入的信息,并顯示指定的信息類型。可用的類型有NONE、INFORMATION、WARNING和ERROR。需要注意的是, setMessage(String newMessage, int IMessageProvider.ERROR)和setErrorMessage(String newErrorMessage)并不相同。后者會(huì)覆蓋當(dāng)前的任何信息,而前者只會(huì)覆蓋當(dāng)前的非error message,不會(huì)影響到error message(也就是說(shuō)當(dāng)error message清空后才會(huì)顯示)。
這樣,我們就可以為一些文本框添加ModifyListener,然后在其中設(shè)置TitleArea的信息了。
接著,再讓我們來(lái)看看Button Bar。有些時(shí)候,我們希望把OK和Cancel這種默認(rèn)的Button放置在Button Bar的右側(cè),而把其他Button放置在Button Bar的左側(cè),如下圖中的Customize... Button:

這又如何實(shí)現(xiàn)呢?有人可能想到在 createButtonsForButtonBar方法中做一些手腳,但是遺憾的是這行不通,我們真正要覆寫的是createButtonBar方法,下面是一個(gè)簡(jiǎn)單的例子:
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.dialogs.TrayDialog#createButtonBar(org.eclipse.swt.widgets.Composite)
*/
protected Control createButtonBar(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.numColumns = 0;
layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
composite.setLayout(layout);
composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
if (isHelpAvailable()) {
createHelpControl(composite);
}
createButton(composite, MyConstants.IMPORT_BUTTON_ID, "Import", false).addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
MessageDialog.openInformation(MaintainModuleDialog.this.getShell(), "Information",
"\"Import\" button has not been implemented.");
}
});
createButton(composite, MyConstants.EXPORT_BUTTON_ID, "Export", false).addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
MessageDialog.openInformation(MaintainModuleDialog.this.getShell(), "Information",
"\"Export\" button has not been implemented.");
}
});
createButton(composite, MyConstants.OTHER_BUTTON_ID, "Other", false).addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
MessageDialog.openInformation(MaintainModuleDialog.this.getShell(), "Information",
"\"Other\" button has not been implemented.");
}
});
Label filler = new Label(composite, SWT.NONE);
filler.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL));
layout.numColumns++;
super.createButtonsForButtonBar(composite);
return composite;
}
正如你所見,我們實(shí)際上創(chuàng)建了自己的Button Bar,然后在上面添加了3個(gè)Button:Import、Export和Other,最后 super.createButtonsForButtonBar會(huì)創(chuàng)建OK和Cancel Button。filler是用來(lái)在兩組Button見占位的。代碼中用到的兩個(gè)convert方法來(lái)自 org.eclipse.jface.dialogs.Dialog類,你還可以在這個(gè)類中找到一個(gè)getButton(int)方法,它可以根據(jù)傳入的 ID返回用createButton創(chuàng)建的Button。這些都是非常實(shí)用的方法。
回頭看一下上面那個(gè)完整的 TitleAreaDialog圖片,你會(huì)看到在Dialog左下角有一個(gè)問(wèn)號(hào)符號(hào),這其實(shí)是一個(gè)Button,點(diǎn)擊它可以顯示幫助信息,當(dāng)然幫助信息是由你來(lái)創(chuàng)建的。讓我們看看Eclipse Search的TitleAreaDialog中的幫助信息吧:

如果我們也想實(shí)現(xiàn)這種幫助機(jī)制,那么就要實(shí)現(xiàn)如下方法:
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.dialogs.TrayDialog#createHelpControl(org.eclipse.swt.widgets.Composite)
*/
protected Control createHelpControl(Composite parent) {
// TODO Auto-generated method stub
return super.createHelpControl(parent);
}
如果不想實(shí)現(xiàn)幫助機(jī)制,那么最好不要在Dialog中顯示出那個(gè)問(wèn)號(hào)符號(hào),你可以覆寫如下方法并永遠(yuǎn)返回false,這樣就不會(huì)顯示問(wèn)號(hào)符號(hào)了。
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.dialogs.TrayDialog#isHelpAvailable()
*/
public boolean isHelpAvailable() {
return false;
}
那么這個(gè)酷酷的幫助機(jī)制到底是個(gè)什么東西呢?實(shí)際上,它的學(xué)名叫做DialogTray。TitleAreaDialog繼承了 org.eclipse.jface.dialogs.TrayDialog類,而TrayDialog就可以顯示這種 DialogTray,是不是有點(diǎn)兒拗口呢?實(shí)際上,我們不僅僅可以添加幫助信息這一種DialogTray,還可以添加任意的DialogTray,現(xiàn)在就讓我們動(dòng)手實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的吧。代碼很簡(jiǎn)單,最主要的就是要實(shí)現(xiàn)一個(gè)DialogTray,代碼如下:
import org.eclipse.jface.dialogs.DialogTray;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
public class MyDialogTray extends DialogTray {
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.dialogs.DialogTray#createContents(org.eclipse.swt.widgets.Composite)
*/
protected Control createContents(Composite parent) {
Composite container = new Composite(parent, SWT.NONE);
final GridLayout gridLayout = new GridLayout();
gridLayout.numColumns = 2;
container.setLayout(gridLayout);
final Label label = new Label(container, SWT.NONE);
label.setText("Name:");
final Text text = new Text(container, SWT.BORDER);
text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
return container;
}
}
我們只在其中創(chuàng)建了一個(gè)Label和一個(gè)Text,這就足夠了。最后,我們?yōu)镸yTitleAreaDialog添加兩個(gè)Button,用來(lái)打開和關(guān)閉MyDialogTray,代碼如下:
final Button openTrayButton = new Button(container, SWT.NONE);
openTrayButton.setText("Open Tray");
final Button closeTrayButton = new Button(container, SWT.NONE);
closeTrayButton.setText("Close Tray");
closeTrayButton.setEnabled(false);
openTrayButton.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(final SelectionEvent e) {
// this method is from TrayDialog
openTray(new MyDialogTray());
openTrayButton.setEnabled(false);
closeTrayButton.setEnabled(true);
}
});
closeTrayButton.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(final SelectionEvent e) {
// this method is from TrayDialog
closeTray();
openTrayButton.setEnabled(true);
closeTrayButton.setEnabled(false);
}
});
最后我們會(huì)得到如下對(duì)話框:

好了,就講這么多吧。如果能把這些東東適當(dāng)?shù)赜迷谀愕腁pplication中,那么效果一定非常棒。