首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

2.5  开发枚举项编辑向导页

这个插件的向导目前只能设定文件保存在哪个包下、文件名是什么,我们还缺少一个指定这个枚举类有哪些项的向导界面。

因为本节的目的在于快速帮助我们掌握一个简单的实用插件的开发,因此将此向导页简化:整个向导页只有一个多行文本框,用户在多行文本框中每行输入一个枚举项的名字就可以;我们对输入文本框的内容的校验也做简化,只校验内容是否为空及各项是否重复,不校验枚举项的命名是否合法。

(1)   首先创建向导页类

所有的向导页类都直接或间接地从org.eclipse.jface.wizard.WizardPage继承,因此我们新建一个从WizardPage继承的EnumGenItemDefWizardPage类。

(2)   画界面

这个界面非常简单,只有一个多行文本框,并且此多行文本框充满整个页面。因此我们对此页面采用FormLayout页面布局管理器。FormLayout是SWT 2.0中新增加的布局管理器,在其中可以设定控件与容器以及容器与容器之间的“附着关系”,这样界面控件就会随着界面大小的变化自动伸展,从而不会造成控件之间的错位。

【代码2-10】枚举项编辑向导页界面初始化:

public void createControl(Composite parent)

{

    Composite container = new Composite(parent, SWT.NULL);

    FormLayout  layout = new FormLayout();

    container.setLayout(layout);

   

    FormData data = new FormData();

    data.top = new FormAttachment(0, 0);

     data.left = new FormAttachment(0, 0);

     data.right = new FormAttachment(100, 0);

     data.bottom = new FormAttachment(100, 0);       

    

     txtItem = new Text(container,SWT.MULTI|SWT.WRAP|SWT.V_SCROLL);

     txtItem.setLayoutData(data);

     txtItem.addModifyListener(new ModifyListener(){

        public void modifyText(ModifyEvent e)

        {

            dialogChanged();               

        }           

     });

    setControl(container);

}

代码解析:

l   FormData的实例data的作用是保证无论界面如何缩放,文本框控件到容器四周的距离都是0。

l   Swing中一个文本框本身是不带滚动条的,如果要它出现滚动条,那么必须把文本框放入JScrollPane控件中。而SWT中则无需如此,如果要其有垂直滚动条,只要指定其具有SWT.V_SCROLL风格就可以了,如果要使其有水平滚动条,只要指定其具有SWT.H_SCROLL风格就可以了。

l   文本框控件中SWT.MULTI、SWT.WRAP两种风格分别代表多行和自动换行。

(3)   dialogChanged方法

在createControl方法中,我们为txtItem控件增加了一个修改监听器,当用户在文本框中敲入内容的时候dialogChanged方法就会被调用。我们在这个方法中主要完成文本框中内容合法性的校验,代码如下。

【代码2-11】dialogChanged方法:

private void dialogChanged()

{

    String strItems = txtItem.getText();       

    if(strItems==null||strItems.trim().length()<=0)

    {

        updateStatus("请输入枚举项!");

        return;

    }      

    String[] itemArray = strItems.split(LINESEPRATOR);

    Set<String> set = new HashSet<String>();

    for (String item : itemArray)

    {

        if(item==null||item.trim().length()<=0)

        {

            continue;

        }

        if(set.contains(item))

        {

            updateStatus("项重复:"+item);

            return;

        }

        updateStatus(null);

        set.add(item);

    }              

}

需要注意的是在验证用户输入的枚举项是否重复的时候,运用了一个小技巧,首先把文本框中的字符串按照行分隔符分割(LINESEPRATOR是一个常量:static final String LINESEPRATOR = System.getProperty("line.separator")),这样就可以得到一个个的枚举项了;然后创建一个集合(Set),遍历这些枚举项,判断每个项是否已经在集合中存在,如果存在则证明有重复,否则将此项存入这个集合中。这样做是比较高效的做法,其时间复杂度为O(n)。

(4)   将此页面加入向导

前面所做的工作都是在定义页面,而没有将页面加入向导的代码。我们只要为EnumGeneratorNewWizard加入addPages方法,在最后的位置加入“addPage(new EnumGen- ItemDefWizardPage());”即可。

EnumGeneratorNewWizard从Wizard类继承,并实现了INewWizard接口。它是插件的“管家”,负责向导页的初始化、相关环境数据的处理以及向导单击“完成”之后的业务处理。

2.5.1  初始化

初始化部分主要完成两个工作:添加向导页、将环境输入保存起来备用。在向导页中添加页面必须通过在addPages方法中调用addPage方法进行。

【代码2-12】添加向导页面:

public void addPages()

{

    genPage = new EnumGeneratorNewWizardPage(selection);

    addPage(genPage);

    itemDefPage = new EnumGenItemDefWizardPage();

    addPage(itemDefPage);

}

2.5.2  相关环境数据的处理

在一个向导页启动的时候会调用public void init(IWorkbench workbench, IStructured- Selection selection)方法将工作台、当前选择对象传送给向导,由于我们只用到当前selection,所以只要保存selection就可以了。

2.5.3  代码生成

单击向导的【完成】按钮以后,performFinish方法就会被调用,我们可以在performFinish方法中进行代码生成和保存到磁盘上的操作。

(1)   首先读取两个向导界面中的配置参数:

final String packageName = genPage.getPackageName();

final String fileName = genPage.getFileName();

final Set<String> itemDefSet = itemDefPage.getEnumItems();

final IPackageFragmentRoot srcFolderPck = genPage

        .getPackageFragmentRoot();

final IPackageFragment pckFragment = srcFolderPck

        .getPackageFragment(packageName);

EnumGeneratorNewWizardPage从NewContainerWizardPage继承,NewContainerWizardPage中的方法getPackageFragmentRoot用来取得用户选择的源文件夹,它返回的类型是IPackageFragmentRoot。

IPackageFragmentRoot并不仅仅代表源文件夹,它是一组IPackageFragment的根,所以它既可以是文件夹,也可以是jar包或者zip包。那么什么又是IPackageFragment呢?通俗地说,IPackageFragment代表Java中的“包”,但是和“包”又有区别,比如源文件夹src中的包com.cownew.demo和Jar包中的com.cownew.demo的名称相同,因此它们是同一个“包”,但是由于这两个包在不同的IPackageFragmentRoot下,所以它们是不同的IPackageFragment。通过IPackageFragmentRoot得到其下某个IPackageFragment的方法非常简单,只要调用IPackageFragmentRoot的getPackageFragment,并把包名作为参数传递进去就可以了。

(2)   由于各种原因,保存代码文件的时间可能会比较长,因此我们为代码生成添加了进度对话框。JFace对进度对话框提供了很好的支持,我们只要创建一个实现了IRunnableWithProgress接口的类的实例,把要运行的任务放到IRunnableWithProgress的run方法中即可,代码如下:

IRunnableWithProgress op = new IRunnableWithProgress() {

    public void run(IProgressMonitor monitor)

            throws InvocationTargetException

    {

        try

        {

            doFinish(pckFragment, packageName,fileName, monitor, itemDefSet);

        } catch (CoreException e)

        {

            throw new InvocationTargetException(e);

        } finally

        {

            monitor.done();

        }

    }

};

try

{

    getContainer().run(true, false, op);

} catch (InterruptedException e)

{

    return false;

} catch (InvocationTargetException e)

{

    Throwable realException = e.getTargetException();

    MessageDialog.openError(getShell(), "Error", realException

            .getMessage());

    return false;

}

首先创建一个实现了IRunnableWithProgress接口的匿名类,把生成代码的操作放到run方法中,此处为了便于维护,将实际运行的耗时代码放到了自定义的doFinish方法中。代码生成的过程中有可能发生异常,因此进行异常处理,无论任务是否正常完成进度条都要在任务执行完成之后关闭,因此在finally中调用monitor.done()表示任务完成。

现在我们只是创建了一个IRunnableWithProgress的实例,还必须启动它,Eclipse的向导中提供了一个很简洁的方式运行此任务,那就是getContainer().run(boolean fork, boolean cancelable, IRunnableWithProgress runnable),其中fork代表任务是否要在一个独立的线程中运行,cancelable表示这个进度对话框是否能被取消,runnable就代表要运行的任务。如果能被取消的话,当用户在任务运行的时候单击【取消】按钮,就会抛出InterruptedException异常,因此我们捕捉此异常并返回false,表明此次向导操作没有完成。

下面看一看doFinish方法的代码。

【代码2-13】向导完成以后的操作:

private void doFinish(IPackageFragment pckFragment, String packageName,

String fileName,IProgressMonitor monitor, Set<String> itemDefSet) throws

CoreException

{

    monitor.beginTask("Creating " + fileName, 2);

   

    final ICompilationUnit cu = pckFragment.createCompilationUnit(

fileName,EnumCodeGenUtils.getEnumSourceCode(packageName,

fileName,itemDefSet), true, monitor);

    monitor.worked(1);

    monitor.setTaskName("Opening file for editing...");

    getShell().getDisplay().asyncExec(new Runnable() {

        public void run()

        {

            try

            {

                JavaUI.openInEditor(cu);

            } catch (PartInitException e)

            {

                Activator.logException(e);

            }

            catch (JavaModelException e)

            {

                Activator.logException(e);

            }

        }

    });

    monitor.worked(1);

}

首先调用monitor. beginTask ("Creating " + fileName, 2)启动任务,beginTask的第一参数代表此任务的名称,会显示在进度对话框上,此名称可以通过setTaskName方法动态设置,第二个参数表示此工作有几步。以后随着任务的一步步完成,我们就同步地调用monitor.worked来推荐滚动条的前进。

接下来就是最重要的一步:生成代码并保存到磁盘。由于生成代码相对复杂,我们把生成代码的逻辑封装到EnumCodeGenUtils.getEnumSourceCode方法中,这里假定调用EnumCodeGenUtils.getEnumSourceCode方法就可得到枚举的Java源码。创建Java文件有两种方式:①用IO把Java代码当成普通文本文件一样创建;②把Java代码当成编辑单元创建。建议使用第二种方法,第一种方法适用于生成普通的文本类文件。本节使用第二种方式,我们在后面的章节中将会讲解第一种方式的应用。

在得到了其父包的IPackageFragment以后,我们创建Java文件会很方便,只需调用IPackageFragment的ICompilationUnit createCompilationUnit(String name, String contents, boolean force, IProgressMonitor monitor)方法即可。第1个参数是生成的Java文件名(比如Test.Java),第2个参数为Java文件的内容,第3个参数force代表当要生成的文件已经存在的时候是否覆盖原有文件。

最后一步就是用Eclipse的Java文件编辑器打开生成的文件。由于打开文件编辑器的操作在另一个线程中,所以此处要采用asyncExec进行同步。

在Eclipse中用代码打开一个文件有两种方式:

l   使用org.eclipse.ui.ide.IDE类的静态方法openEditor,这个方法有9个重载方法,以其中一个为例:

public static IEditorPart openEditor(

IWorkbenchPage page, IFile input);

第1个参数代表要在哪个工作台页中打开,第2个参数是要打开的文件对象IFile。这个方法可以用来打开各种类型的文件。

l   使用org.eclipse.jdt.ui. JavaUI的openInEditor方法,其方法声明为:

public static IEditorPart openInEditor(IJavaElement element)

这种方法只能打开Java文件。

如果要使用第一种方法,那么首先需要将ICompilationUnit转换成IFile,这个转换过程有一点烦琐。ICompilationUnit接口是继承了IJavaElement 接口的,因此我们使用JavaUI的openInEditor方法,将ICompilationUnit传递过去就可以了。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论