随笔-67  评论-88  文章-0  trackbacks-2
  2006年10月5日
Platform: Eclipse 3.2

Dialog是SWT和JFace的一个重要的组成部分,我们在开发Plug-in或RCP的时候也经常会用到它们。这篇随笔不会介绍 SWT的Dialog,因为我想很多人都已经非常熟悉它了。在这里,我要讨论的是JFace的Dialog,或者更进一步说是JFace的 TitleAreaDialog。什么是TitleAreaDialog呢?想想我们常常用到的New XX Wizard就知道了。在我们创建一个Java Project或Class的时候,我们所使用的Wizard其实就是由TitleAreaDialog构成的。这种Dialog有如下所示的 TitleArea和一个标准的Button Bar:

      
                                    正常的TitleArea                                                                                                               带有错误信息的TitleArea


                                    标准的Button Bar

这种GUI的表现力要比SWT的Dialog强很多,而且JFace为该 Dialog封装了很多东西,这也使开发工作变得更加简单,所以我极力推荐使用TitleAreaDialog。那么让我们来看一个最基本的 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"));
    }

}

这段代码非常容易理解,从方法签名中可以看出每个方法做了什么事情。注意createButtonsForButtonBar方法,其中用createButton方法创建了OK和Cancel这两个Button,并且把Button的默认点击事件也写好了,就是关闭该 Dialog。ResourceManager.getPluginImage是我自己编写的获得图片的helper method,这里就不讨论其实现了。这段代码会产生如下的Dialog:



有趣的是,我在这里故意使用了一个128×128的大图标, TitleAreaDialog不会自动缩小或裁减Image,而是调整TitleArea的大小来适应Image。

接下来我们要为OK Button编写我们自己的事件,例如把用户在Dialog中的输入保存到某处。有人可能会想到为OK Button添加SelectionListener,但实际上这样做是不对的,因为OK Button是JFace为Dialog封装好了的,同时JFace也提供了响应的callback:

/*
 * (non-Javadoc)
 * 
 * @see org.eclipse.jface.dialogs.Dialog#okPressed()
 
*/
protected void okPressed() {
   
// implement your own function here
    super.okPressed();
}

我们可以在这里实现我们自己的事件,不过最后一定要调用super.okPressed方法,否则Dialog就不会关闭了。

OK,以上就是TitleAreaDialog的基本Framework,非常容易理解,下面我们就来在 TitleArea中动态设置一些信息。你可以把这个scenario想象成在用户输入的同时提示用户输入的合法性。TitleAreaDialog提供了好3个方法可以动态设置TitleArea信息,具体如下:
这样,我们就可以为一些文本框添加ModifyListener,然后在其中设置TitleArea的信息了。

接着,再让我们来看看Button Bar。有些时候,我们希望把OK和Cancel这种默认的Button放置在Button Bar的右侧,而把其他Button放置在Button Bar的左侧,如下图中的Customize... Button:



这又如何实现呢?有人可能想到在 createButtonsForButtonBar方法中做一些手脚,但是遗憾的是这行不通,我们真正要覆写的是createButtonBar方法,下面是一个简单的例子:

/*
 * (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;
}

正如你所见,我们实际上创建了自己的Button Bar,然后在上面添加了3个Button:Import、Export和Other,最后 super.createButtonsForButtonBar会创建OK和Cancel Button。filler是用来在两组Button见占位的。代码中用到的两个convert方法来自 org.eclipse.jface.dialogs.Dialog类,你还可以在这个类中找到一个getButton(int)方法,它可以根据传入的 ID返回用createButton创建的Button。这些都是非常实用的方法。

回头看一下上面那个完整的 TitleAreaDialog图片,你会看到在Dialog左下角有一个问号符号,这其实是一个Button,点击它可以显示帮助信息,当然帮助信息是由你来创建的。让我们看看Eclipse Search的TitleAreaDialog中的帮助信息吧:



如果我们也想实现这种帮助机制,那么就要实现如下方法:

/*
 * (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);
}

如果不想实现帮助机制,那么最好不要在Dialog中显示出那个问号符号,你可以覆写如下方法并永远返回false,这样就不会显示问号符号了。

/*
 * (non-Javadoc)
 * 
 * @see org.eclipse.jface.dialogs.TrayDialog#isHelpAvailable()
 
*/

public boolean isHelpAvailable() {
    return false;
}

那么这个酷酷的帮助机制到底是个什么东西呢?实际上,它的学名叫做DialogTray。TitleAreaDialog继承了
org.eclipse.jface.dialogs.TrayDialog类,而TrayDialog就可以显示这种 DialogTray,是不是有点儿拗口呢?实际上,我们不仅仅可以添加帮助信息这一种DialogTray,还可以添加任意的DialogTray,现在就让我们动手实现一个最简单的吧。代码很简单,最主要的就是要实现一个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, truefalse));

        
return container;
    }

}

我们只在其中创建了一个Label和一个Text,这就足够了。最后,我们为MyTitleAreaDialog添加两个Button,用来打开和关闭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);
    }
});

最后我们会得到如下对话框:



好了,就讲这么多吧。如果能把这些东东适当地用在你的Application中,那么效果一定非常棒。
posted @ 2006-10-05 21:37 Allen Young 阅读(2493) 评论(2) 编辑
Platform: Eclipse 3.2

Eclipse Plug-in开发离不开与用户的交互,我们往往把用户的鼠标点击之类动作所调用的代码包装成一个Action。例如,如果用户点击了一个Menu Item,那么绑定到这个Menu Item上的Action就会运行,这一切都很直观。但是有的时候,用户触发的操作可能需要很长时间才能完成,比如连接数据库,在这种情况下,就必须把鼠标的状态改为忙碌,这样就可以用沙漏手势提示用户当前操作正在进行,也避免了用户在等待当前操作的时候又去触发其他的操作。那么如何实现这一点呢?Eclipse有一个High-level的实现,也有一个Low-level的实现。因为后者非常简单,而且在大多数情况下也能满足需求,所以这里会介绍后者。

我们可以在org.eclipse.swt.custom包中找到一个BusyIndicator类,它的Java Doc说得好:Support for showing a Busy Cursor during a long running process。该类只有一个static的方法,让我们来看看它的签名:

public static void showWhile(Display display, Runnable runnable)

其中,display参数指定了Busy Cursor应该显示在哪里,如果传入null,则默认使用当前线程的当前Display;runnable就是封装了我们要执行的操作的对象。这个方法会执行runnable中的操作,同时显示Busy Cursor,当runnable结束后,鼠标的状态就会恢复为正常。OK,让我们来看一个例子:

public class DoubleClickTreeNodeAction extends Action {

    
/*
     * (non-Javadoc)
     * 
     * @see org.eclipse.jface.action.Action#run()
     
*/
    
public void run() {
       
// get Display from your own plug-in
        BusyIndicator.showWhile(Activator.getDefault().getWorkbench()
                .getDisplay(), 
new Runnable() {
            
public void run() {
                performAction();
            }
        });
    }

    
private void performAction() {
        
// connecting to database...
    }

}

怎么样,是不是很简单呢?

posted @ 2006-10-05 15:55 Allen Young 阅读(568) 评论(0) 编辑
Platform: Eclipse 3.2

开发任何软件都不得不处理Exception和Log,Eclipse Plug-in也是如此。不过幸运的是,Eclipse PDE提供了记录及显示Exception和Log的机制:Error Log View。作为Eclipse SDK的一部分,PDE的普及率很高,所以除非你是要做RCP,不然的话用Error Log View处理Exception和Log应该是你的最佳选择。当然,这也带来了对PDE的依赖性。

使用Error Log View实际上非常简单,每个Plug-in的Activator类都有一个getLog()方法,返回一个ILog对象,这个对象就可以把Exception和Log记录到Error Log View中。ILog对象最主要的方法就是log了,顾名思义,它接收一个IStatus类型的对象,并把其代表的状态记录下来。Eclipse和许多常用的插件(如JDT)实现了很多的IStatus,最common的就是Status类,我们可以简单地使用它,或创建自己的IStatus实现。Status的构造函数有5个参数,具体如下:
  • int severity:日志的级别,可以是OK、ERROR、INFO、WARNING或CANCEL。这些常量都定义在Status类中。
  • String pluginId:当前Plug-in的ID。
  • int code:Plug-in指定的状态码,一般如果无需指定,则使用Status.OK。
  • String message:日志信息。
  • Throwable exception:记录的Exception,如果没有Exception,则传入null。
这样的话,我们就可以编写一个LogUtil类来负责日志工作,代码如下:

import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.Status;

public class LogUtil {

    
private static LogUtil instance = null;

    
private ILog logger = null;

    
private LogUtil() {
        logger = Activator.getDefault().getLog();
    }

    
public static LogUtil getInstance() {
        
if (instance == null) {
            instance = 
new LogUtil();
        }

        
return instance;
    }

    
public void log(int severity, String message, Throwable exception) {
        logger.log(
new Status(severity, Activator.getDefault().getPluginID(),
                Status.OK, message, exception));
    }

    
public void logCancel(String message, Throwable exception) {
        logger.log(
new Status(Status.CANCEL, Activator.getDefault()
                .getPluginID(), Status.OK, message, exception));
    }

    
public void logError(String message, Throwable exception) {
        logger.log(
new Status(Status.ERROR, Activator.getDefault()
                .getPluginID(), Status.OK, message, exception));
    }

    
public void logInfo(String message, Throwable exception) {
        logger.log(
new Status(Status.INFO,
                Activator.getDefault().getPluginID(), Status.OK, message,
                exception));
    }

    
public void logOk(String message, Throwable exception) {
        logger.log(
new Status(Status.OK, Activator.getDefault().getPluginID(),
                Status.OK, message, exception));
    }

    
public void logWarning(String message, Throwable exception) {
        logger.log(
new Status(Status.WARNING, Activator.getDefault()
                .getPluginID(), Status.OK, message, exception));
    }
}

除此之外,我们还可以通过ILog的addLogListener方法和removeLogListener方法为日志动作添加和删除事件监听器。这些Listener可以帮助我们在日志记录完成后做一些额外的事情。例如,如果记录的是ERROR级别的Log,那么我们可能要弹出一个Alert对话框告诉用户出现了错误,但如果是INFO级别,就没这个必要了。

posted @ 2006-10-05 15:36 Allen Young 阅读(909) 评论(0) 编辑