第九天:超级对话框

作者:梁祺 (eclipsesbs@gmail.com)

来自:http://www.benisoft.net/day9/index.html


WizardDialog称为向导对话框,当用户需要输入大量信息时,向导对话框可以循序渐进地引导用户,使得用户在每一个向导对话框页面里只需要专注于输入少量信息。并且向导对话框可以根据用户的输入,判断用户是否完成了该页面,决定下一个向导对话框页面。由于向导对话框是用于帮助用户输入大量且复杂的信息,它本身就很复杂,所以它非常需要仔细的设计,经验表明,一个设计不合理的向导对话框是很难通过后期修改来完全满足需求,往往需要重新设计。

在Itinerary的例子里,我们需要一个向导对话框来创建行程活动Itinerary Item。行程活动有三种,交通(Transportation),住宿(Accommodation),和观光(Activity)。我们不希望用三个独立的对话框来分别创建它们,而是用一个向导对话框。这个向导对话框有三页,第一页选择活动类型,交通,住宿,还是观光;根据第一页中选择的内容,用户在第二页输入交通,住宿或观光的详细内容;第三页输入该行程活动的备注信息。

创建向导对话框,首先创建继承自Wizard的NewItineraryItemWizard类,Wizard主要提供向导对话框所要处理的数据对象,并且控制所有向导对话框页面。其次就是创建一系列继承自WizardPage的向导对话框页面类。

  • NewItineraryItemWizard:保存向导对话框所要处理的数据对象,并且控制向导对话框页面
  • NewItineraryItemTypeWizardPage:第一页,选择行程活动类型
  • NewItineraryItemTransportationWizardPage:第二页,输入交通信息
  • NewItineraryItemAccommodationWizardPage:第二页,输入住宿信息
  • NewItineraryItemActivityWizardPage:第二页,输入观光信息
  • NewItineraryItemDescriptionWizardPage:第三页,输入备注信息

用户选择主菜单的"Itinerary -> New Itinerary Item",会调用NewItineraryItemAction。在这个Action类的run()中,先创建一个NewItineraryItemWizard对象,然后将该对象交给WizardDialog的构造函数,最后调用WizardDialog.open()方法打开对话框。

        NewItineraryItemWizard wizard =  new NewItineraryItemWizard();
        WizardDialog dialog =  new WizardDialog(window.getShell(), wizard);
         if (Window.OK == dialog.open()) {
             // OK
        }

NewItineraryItemWizard负责实例化所有的WizardPage,并调用addPage()把它们加到Wizard维护的页面列表中。这里的WizardPage成员变量命名比较偷懒,但也一目了然,page2A,page2B,page2C就是第二页的三个可能的页面。

public class NewItineraryItemWizard  extends Wizard {
    
     private NewItineraryItemTypeWizardPage page1;
     private NewItineraryItemTransportationWizardPage page2A;
     private NewItineraryItemAccommodationWizardPage page2B;
     private NewItineraryItemActivityWizardPage page2C;
     private NewItineraryItemDescriptionWizardPage page3;
    
     public NewItineraryItemWizard() {
        page1 =  new NewItineraryItemTypeWizardPage( "page.type");
        addPage(page1);
        page2A =  new NewItineraryItemTransportationWizardPage( "page.transportation");
        addPage(page2A);
        page2B =  new NewItineraryItemAccommodationWizardPage( "page.accommodation");
        addPage(page2B);
        page2C =  new NewItineraryItemActivityWizardPage( "page.activity");
        addPage(page2C);
        page3 =  new NewItineraryItemDescriptionWizardPage( "page.description");
        addPage(page3);
    }

当用户在第一页中选择了交通,住宿或者观光后,Wizard就知道第二页该使用哪个页面了。getNextPage()和getPreviousPage()类似,都是根据当前页面以及行程活动类型来确定下一页或者上一页。

     public IWizardPage getNextPage(IWizardPage page) {
         if (page == page1) {
            String type = page1.getType();
             if (type.equals( "transportation")) {
                 return page2A;
            }  else if (type.equals( "accommodation")) {
                 return page2B;
            }  else {
                 return page2C;
            }
        }  else if (page == page2A || page == page2B || page == page2C) {
             return page3;
        }
         return null;
    }

接下来是WizardPage,也是代码量最多的地方。WizardPage主要负责提供界面与用户交互,检查用户的输入,获取用户输入的数据。这个和普通的对话框没有什么区别。第一个NewItineraryItemTypeWizardPage非常简单,只有一个Combo控件,用来选择行程活动的类型,也不需要检查输入。

第二页我们以NewItineraryItemTransportationWizardPage为例。这里我们需要检查一些必须输入的域,其中我们还需要检查航班出发和到达时间的格式,如果没有输入或者输入格式不正确,WizardPage的标题区域会先显示红色的错误提示。

为了实时检查用户输入,我们需要为每个控件添加事件接收器,以便在控件内容改变时能接收到事件,实时检查输入的合法性。一般文本控件可以使用ModifyListener,Combo控件可以使用SelectionListener。当收到事件时,我们调用isInputValid()来检查控件里的用户输入,如果输入合法,isInputValid()返回true,并调用setPageComplete()来告诉WizardPage,当前页已完成。

        ModifyListener listener =  new ModifyListener() {

             @Override
             public void modifyText(ModifyEvent e) {
                setPageComplete(isInputValid());
            }};
        textNumber.addModifyListener(listener);
        textDepartureTime.addModifyListener(listener);
        textDepartureStation.addModifyListener(listener);
        textArrivalTime.addModifyListener(listener);
        textArrivalStation.addModifyListener(listener);

这里简单看一下isInputValid(),每个输入项都有自己的合法性要求,如果不合法,调用setErrorMessage(),设置错误消息提示用户,并返回false。如果所以输入项都合法,调用setErrorMessage(null)清除错误消息,返回true。

     private boolean isInputValid() {
         if (comboType.getText().isEmpty()) {
             this.setErrorMessage( "Input transportation type.");
             return false;
        }
        
         if (textNumber.getText().isEmpty()) {
             this.setErrorMessage( "Input " + comboType.getText().toLowerCase() +  " number.");
             return false;
        }
        
         if (! textDepartureTime.getText().isEmpty()) {
            Date date = getValidDate(textDepartureTime.getText());
             if (date ==  null) {
                 this.setErrorMessage( "Input departure time in format yyyy-MM-dd HH:mm.");
                 return false;
            }
        }
        
        ...
        
         this.setErrorMessage( null);
        
        transportation =  new Transportation();
        transportation.setTransportationType(getType());
        transportation.setNumber(textNumber.getText());
        transportation.setDepartureTime(getValidDate(textDepartureTime.getText()));
        transportation.setDeparturePlace(textDepartureStation.getText());
        transportation.setArrivalTime(getValidDate(textArrivalTime.getText()));
        transportation.setArrivalPlace(textArrivalStation.getText());
        transportation.setDescription(textDescription.getText());

         return true;

只有当调用setPageComplete(true)后,用户才可以点击Next进入下一页,或者点击Finish按钮完成整个向导对话框。在上面这个例子里,第一页和第二页是必须的,第三页是可选的,所以第一页的Finish按钮非活动状态,不能点,第二页和第三页Finish按钮处于活动状态。为了定制Finish按钮的行为,我们需要重载Wizard的canFinish()方法,如果必选页都处于完成状态,向导对话框就可以Finish。

     public boolean canFinish() {
        String type = page1.getType();
         if (type.equals( "transportation")) {
             return page1.isPageComplete() && page2A.isPageComplete();
        }  else if (type.equals( "accommodation")) {
             return page1.isPageComplete() && page2B.isPageComplete();
        }  else {
             return page1.isPageComplete() && page2C.isPageComplete();
        }
    }

第三页是可选页,比较简单,就不纍述了。

这里小结一下,为了开发一个逻辑清晰并具有良好用户体验的向导对话框:

  • 仔细设计每个向导页面,包括页面的个数,每个页面是可选还是必选,每个页面的布局等;
  • 在createControl()方法里布局页面控件
  • 为需要输入检查的控件添加事件接收器以检查输入合法性,并调用isPageComplete()设置页面完成与否;
  • 继承Wizard.canFinish()来定制Finish按钮活动与否,以便用户可以提前结束向导对话框。
应该讲向导对话框的工作量是非常巨大的,测试工作也很耗时,开发过程中遇到很多Bug也很常见。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值