dialog之登录
在这篇文章里,我要和大家探讨的是对话框,不要小看这个对话框,虽然JFace里面提供了很好用的对话框基类,但是如果你不理解SWT中GUI线程和非GUI线程的概念,那么你依然难以达到你想要的效果。问什么突然想要研究对话框呢?这要从我最近接的一个项目说起。
有人委托我做一个药店管理系统,这种系统属于进销存管理系统的范畴,理论上讲没有什么难度,可供选择的开发工具有很多,最主流的当然要数Visual C++和VB、Delphi,如果偷一点懒,选择Office中的Access开发也很简单,但是为了挑战自己,我决定选择Eclipse RCP来写这个程序,Eclipse RCP开发的程序界面很漂亮,但是Eclipse RCP很复杂,稍有不慎就会陷入Bug的泥沼,严重延误工期。这不,一开始就碰到了对话框的难题。
我的本意是在打开工作台窗口前,先打开一个用户登录的对话框,如果用户登录成功,则关闭对话框,打开工作台窗口。使用Eclipse的向导创建了项目之后,很快我就决定在Application类中实现该功能。我的代码如下,只列出Application类中的start方法:
public Object start(IApplicationContext context) { Display display = PlatformUI.createDisplay(); // 下面是我的代码 LoginDialog loginDialog = new LoginDialog(); // 我的代码结束 try { int returnCode = PlatformUI.createAndRunWorkbench(display, new ApplicationWorkbenchAdvisor()); if (returnCode == PlatformUI.RETURN_RESTART) { return IApplication.EXIT_RESTART; } return IApplication.EXIT_OK; } finally { display.dispose(); } } |
我的本意是只弹出登录对话框,登录对话框关闭后才出现工作台窗口,可是事实证明,即使登录等话框不关闭,也不会影响工作台窗口的创建,效果如下图:
即使我自己加入阻塞代码也不行,我先加入的代码如下:
public
Object start(IApplicationContext context) { Display display = PlatformUI.createDisplay(); // 下面是我的代码 LoginDialog loginDialog = new LoginDialog(); while ( ! loginDialog.getSShell().isDisposed()){ try { Thread.sleep( 100 ); } catch (Exception e){ } } // 我的代码结束 try { int returnCode = PlatformUI.createAndRunWorkbench(display, new ApplicationWorkbenchAdvisor()); if (returnCode == PlatformUI.RETURN_RESTART) { return IApplication.EXIT_RESTART; } return IApplication.EXIT_OK; } finally { display.dispose(); } } |
这个时候虽然可以阻止工作台窗口的产生,但是程序会失去响应,Splash Screen也不消失,关闭程序时会出错,如下图:
其实我想要的效果,就是要一个模式对话框,在一般的Windows编程书籍中,都会有专门的篇幅来讲解模式对话框和非模式对话框的区别,以及实现方法。但是Eclipse RCP中没有这样的资料。这个问题当然是可以解决的,下面我们就来讨论其中隐藏的秘密。
在SWT程序中,一般都会存在两个线程,一个GUI线程,一个非GUI线程,GUI线程就是用来生成窗口和控件的,而非GUI线程里面一般都有一个事件循环,用来处理GUI中产生的事件。在我上面的例子中,我自己加入的阻塞代码不仅没有阻塞GUI线程,反而阻塞了非GUI线程,使得窗口事件得不到处理,所以出现了应用程序没有响应。那么哪一个是GUI线程,哪一个是非GUI线程呢?我刚才加代码的那个肯定是非GUI线程了,而GUI线程,我查了一下文档,文档说GUI线程是在创建Display对象的时候产生的。
有了上面的知识,就可以解决我们的这个问题了,我们不能用暴力的方法(即Thread的sleep方法)来阻塞非GUI线程,但是我们可以在非GUI线程里面构建一个事件循环,只要这个事件循环不退出,那后面的工作台窗口当然就不会创建了。构建事件循环,需要用到Display类的一些方法。
所以,正确的代码如下:
public
Object start(IApplicationContext context) { Display display = PlatformUI.createDisplay(); // 下面是我的代码 LoginDialog loginDialog = new LoginDialog(); while ( ! loginDialog.getSShell().isDisposed()){ if ( ! display.readAndDispatch ()) display.sleep (); } // 我的代码结束 try { int returnCode = PlatformUI.createAndRunWorkbench(display, new ApplicationWorkbenchAdvisor()); if (returnCode == PlatformUI.RETURN_RESTART) { return IApplication.EXIT_RESTART; } return IApplication.EXIT_OK; } finally { display.dispose(); } } |
只要把该事件循环的代码转移到对话框类中,我们就可以创建完美的模式对话窗了。
写到这里,大家肯定会以为Eclipse中没有提供比较好的模式对话框,那就是大家被我误导了。在Eclipse中,Dialog类根本就不是一个窗口,对话框的窗口靠的是Dialog类里面的Shell对象产生的。在Eclipse中大家一定要记住,Shell就等同于Window,所以,没有模式Dialog,却有模式Shell,只需要在创建Shell的时候指定SWT.APPLICATION_MODAL参数就行。而org.eclipse.jface.dialogs.Dialog类就对Shell做了比较好的封装,使用该类,我们可以很方便地构建一个模式对话框。
在上面的例子中,我的LoginDialog类没有继承自org.eclipse.jface.dialogs.Dialog,就是使用的一个普通Shell,其代码如下:
package opengsp; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.layout.GridData; public class LoginDialog { private Shell sShell = null ; private Label lbUserName = null ; private Text txtUserName = null ; private Label lbPassword = null ; private Text txtPassword = null ; private Button btnOK = null ; private Button btnCancel = null ; public LoginDialog() { // TODO Auto-generated constructor stub createSShell(); sShell.open(); } public Shell getSShell() { return sShell; } /** * This method initializes sShell */ private void createSShell() { sShell = new Shell(); GridData gridData = new GridData(); gridData.horizontalSpan = 2 ; GridLayout gridLayout = new GridLayout(); gridLayout.numColumns = 3 ; sShell.setText( " 用户登录 " ); sShell.setLayout(gridLayout); sShell.setSize( new Point( 300 , 200 )); sShell.addShellListener( new org.eclipse.swt.events.ShellAdapter() { public void shellClosed(org.eclipse.swt.events.ShellEvent e) { sShell.dispose(); // TODO Auto-generated Event stub shellClosed() } }); lbUserName = new Label(sShell, SWT.NONE); lbUserName.setText( " 用户名: " ); txtUserName = new Text(sShell, SWT.BORDER); txtUserName.setLayoutData(gridData); lbPassword = new Label(sShell, SWT.NONE); lbPassword.setText( " 密码: " ); txtPassword = new Text(sShell, SWT.BORDER); Label filler1 = new Label(getSShell(), SWT.NONE); btnOK = new Button(getSShell(), SWT.NONE); btnOK.setText( " 确定 " ); btnCancel = new Button(getSShell(), SWT.NONE); btnCancel.setText( " 取消 " ); } } |
在上面的代码中,我们需要自己设计OK按钮和Cancel按钮,需要自己处理事件,需要模式对话框的时候,还需要自己构建事件循环。
其实,我们可以使用JFace提供的Dialog基类来简化我们的开发,使用JFace的Dialog类时,只需要重写几个方法就行了,重写createDialogArea来添加控件,而且不需要我们自己设计OK按钮和Cancel按钮,重写okPressed来处理事件。所以,我写了了另外一个LoginDialog2类,其代码如下:
package opengsp; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.window.IShellProvider; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; public class LoginDialog2 extends Dialog { private Label lbUserName = null; private Text txtUserName = null; private Label lbPassword = null; private Text txtPassword = null; public LoginDialog2(IShellProvider parentShell) { super(parentShell); // TODO Auto-generated constructor stub } public LoginDialog2(Shell parentShell) { super(parentShell); // TODO Auto-generated constructor stub } @Override protected Control createDialogArea(Composite parent) { // TODO Auto-generated method stub Composite composite = (Composite) super.createDialogArea(parent); lbUserName = new Label(composite, SWT.NONE); lbUserName.setText("用户名:"); txtUserName = new Text(composite, SWT.BORDER); lbPassword = new Label(composite, SWT.NONE); lbPassword.setText("密码:"); txtPassword = new Text(composite, SWT.BORDER); return composite; } } |
而这个时候,在Application类中的代码如下:
public Object start(IApplicationContext context) { Display display = PlatformUI.createDisplay(); //下面是我的代码 LoginDialog2 loginDialog = new LoginDialog2(new Shell()); loginDialog.open(); //我的代码结束 try { int returnCode = PlatformUI.createAndRunWorkbench(display, new ApplicationWorkbenchAdvisor()); if (returnCode == PlatformUI.RETURN_RESTART) { return IApplication.EXIT_RESTART; } return IApplication.EXIT_OK; } finally { display.dispose(); } } |
最后说一点,关于可是化编辑环境的。VE是在是太不能让人满意了,根本没有办法对Dialog进行可视化编辑,而且用于Eclispe 3.4的VE 1.4非官方版也不能对ViewPart进行编辑。建议打击使用SWT Designer.
还有,我觉得不应该把这个对话框插入到Application类中,而是应该放到ApplicationWorkbenchWindowAdvisor类的preWindowOpen方法中。