分享
 
 
 

关于Eclipse插件开发之定制向导(图)

王朝other·作者佚名  2008-05-31
窄屏简体版  字體: |||超大  

Invokatron的历史

首先,我们具体说明一下Invokatron本身。在前面的文章中我们讨论过,Invokatron是一个生成java代码的的图形工具。你可以简单地通过拖放操作建立类的方法。拖入的方法被编辑的方法(也就是插件)"调用"。我们将让数据来驱动应用程序的设计。在后面一篇文章中,我们将开发这个GUI。现在我们需要做的是,找到插件将输入和存储的重要数据。它通常被称为应用程序的模型(model)。在设计这个系统的时候,我们需要考虑下面一些内容:

· 哪些细节数据需要保存?

· 这些数据在内存中用什么来表现?POJO、JavaBean还是EJB?

· 这些数据的存储格式是怎样的?数据库表、xml文件、属性文件还是串行二进制文件?

· 输入数据的方式有哪几种?用"新建文件"向导还是在文档属性页面上使用弹出对话框、用编辑器绘制、在文本编辑器中输入的其它向导?

在我们继续工作之前必须回答这些问题。不可能有适合所有项目的答案;它完全依靠于你的需求。在我们的例子中,我做出了一些随意的、可能有问题的决定,如下所示:

· 一个Java类,它包含类名、程序包、超类(superclass)和实现接口。我们以它为基础,在后面的文章中添加更多数据。

· 我将把数据表现为扩展PRoperties类的类。它建立了编辑器的"文档类"。

· 我将使用的格式是属性文件,很轻易使用Properties类来分析它。

· 在"新建文件"向导中,我将先寻找数据,接着让用户改变属性窗口或文本编辑器中的数据。这个步骤将在下一篇文章中完成。

Document(文档)类

下一步是编写文档类。建立一个新程序包(invokatron.model)和一个新类(InvokatronDocument)。下面是我们的文档类的开头:

public class InvokatronDocument

extends Properties

{

public static final String PACKAGE = "package";

public static final String SUPERCLASS = "superclass";

public static final String INTERFACES = "interfaces";

}

使用Properties类可以更简单地分析和保存我们的数据。Getter和 setter不是必须的,但是假如你想要,也可以加上它们。这个类还没有完成;我们将添加一个接口,在后面的部分中Eclipse需要使用它。

有了这个类之后,我们要获取一个属性就非常简单了:

String package =document.getProperty(InvokatronDocument.PACKAGE);

定制向导

请看一看前面的文章中所出现的向导。你应该记得,我们可以通过点击(我们自己添加的)工具条按钮或者菜单项来访问它。图1是它的界面:

图1:旧的向导

它只有一个页面,右上角没有图片。我们想输入更多的信息,并提供一个很好的图片。换句话说,我们希望定制这个向导。

我们来分析一下这个向导。请打开InvokatronWizard.java文件。请注重这个类是如何扩展Wizard并实现INewWizard接口的。你应该理解它里面的很多方法。为了定制向导,我们简单地调用或重载其中的某些方法。下面是一些重要的方法:

生命周期方法

我们应该重载这些方法,把初始化和析构(destrUCtion)代码插入向导中:

· Constructor(构造函数):向导实例化的时候、在Eclipse给它传递信息之前调用。向导的一般初始化实现。通常你希望调用"美化方法"(后面有描述)并设置对话框的默认值。

· init(IWorkbench workbench, IStructuredSelection editorSelection): Eclipse调用它为向导提供工作台的信息。请重载它,保存IWorkbench和对象的句柄供以后使用。假如它是一个编辑器向导而不是新向导,我们最好把当前的编辑器选项作为第二个参数。

· dispose():Eclipse调用它执行清理工作。重载它来清除向导使用的资源。

· finalize():清除代码,可能使用dispose()代替。

美化方法

这些方法都是用于装饰向导窗体的。

· setWindowTitle(String title):设置窗体的标题行字符串。

· setDefaultPageImageDescriptor(ImageDescriptor image):用于提供显示在向导的所有页面右上方的图片。

· setTitleBarColor(RGB color):指定标题栏用什么颜色。

按钮方法

这些方法控制着向导按钮的实用性和行为。

· boolean canFinish():重载它用于指定Finish(完成)按钮是否激活(根据向导的状态)。

· boolean performFinish():重载它来实现向导的根本的业务逻辑。假如向导没有完成(错误的条件),就返回false。

· boolean performCancel():重载它,在用户点击Cancel(取消)按钮的时候进行清除操作。假如向导不能终止,则返回false。

· boolean isHelpAvailable():重载它用于指定Help(帮助)按钮是否可视。

· boolean needsPreviousAndNextButtons():重载它来指定Previous(前一步)和Next(后一步)按钮是否可视。

· boolean needsProgressMonitor():重载它来指定进度条部件是否可视。当点击Finish按钮调用performFinish()方法的时候,它就会出现。

页面方法

这些方法控制着页面的外观。

· addPages():向导显示的时候调用。重载它给向导插入新页面。

· createPageControls(Composite pageContainer):Eclipse调用它来实例化所有的向导页面(用前面的addPages()方法已经添加的页面)。重载它给向导添加持续可视的窗体小部件(除页面之外的部件)。

· IWizardPage getStartingPage():重载它来检测哪个页面是向导的第一个页面。

· IWizardPage getNextPage(IWizardPage nextPage):在默认情况下,点击Next按钮将进入addPages()所提供的数组中的下一个页面。你可能希望根据用户选择进入不同的页面。重载它来计算后一个页面。

· IWizardPage getPreviousPage(IWizardPage previousPage):与getNextPage()类似,用于计算前一个页面。

· int getPageCount():检索addPages()添加的页面的数量。在典型情况下,你不必重载它,除非你希望显示页面的数量和形式。

其它有用的方法

这些都是有用的辅助方法:

· setDialogSettings(IDialogSettings settings):你可以载入对话框的状态,并通过在init()中调用这个方法来设置这些值。在典型情况下,这些设置可以作为向导字段的默认值。请查看DialogSettings类了解更具体的信息。

· IDialogSettings getDialogSettings():当我们需要数据的时候,就调用这个方法来检索它。在performFinish()的对话框的末尾,你再次可以把数据保存到文件中。

· IWizardContainer getContainer():对于检索Shell、运行的后台线程、刷新窗口等非常有用。

向导页面方法

你已经看到了,向导是由一个或多个页面组成的。这些页面扩展了WizardPage类,并实现了IWizardPage接口。为了定制单独的页面,你必须了解很多方法。下面是一些重要的方法:

· Constructor:用于实例化页面。

· dispose():重载它用于实现清除代码。

· createControl(Composite parent):重载它来给页面添加控件。

· IWizard getWizard():用于获取父向导对象。对于调用getDialogSettings()是有用处的。

· setTitle(String title):调用它来设置显示在向导标题区域中的字符串。

· setDescription(String description):调用它来提供标题下面显示的文本内容。

· setImageDescriptor(ImageDescriptor image):调用它来提供页面右上方出现的图片(用于代替默认的图片)。

· setMessage(String message):调用它来显示描述字符串下方的消息文本。这些文本是用于警告或提示用户的。

· setErrorMessage(String error):调用它来高亮度显示描述字符串下方的消息文本。它一般意味着向导不能继续,除非错误被修正。

· setPageComplete(boolean complete):假如为true,Next按钮就可视。

· performHelp():重载它来提供内容敏感的帮助信息。当点击Help按钮的时候向导会调用它。

编写向导的代码

有了这些方法之后,我们就能够开发出具有极大的灵活性的向导了。我们现在修改以前建立的Invokatron向导,给它添加一个页面来请求用户输入初始的文档数据。我们还给向导添加了一个图片。新代码是粗体的:

public class InvokatronWizard extends Wizard

implements INewWizard {

private InvokatronWizardPage page;

private InvokatronWizardPage2 page2;

private ISelection selection;

public InvokatronWizard() {

super();

setNeedsProgressMonitor(true);

ImageDescriptor image =AbstractUIPlugin.imageDescriptorFromPlugin("Invokatron", "icons/InvokatronIcon32.GIF");

setDefaultPageImageDescriptor(image);

}

public void init(IWorkbench workbench,IStructuredSelection selection) {

this.selection = selection;

}

在构造函数中,我们打开了进度条,并设置了向导的图片。你可以下载并保存下面的图片:

请把这个图片保存在Invokatron/icons文件夹之下。为了更轻易载入这个图片,我们使用了便捷的AbstractUIPlugin.imageDescriptorFromPlugin()方法。

请注重:你应该知道,尽管这个向导是INewWizard类型的,但是并非所有的向导都是用于建立新文档的。你可以参考其它一些资料来了解如何建立"独立的"向导的信息。

下面是addPages()方法:

public void addPages() {

page=new InvokatronWizardPage(selection);

addPage(page);

page2 = new InvokatronWizardPage2(selection);

addPage(page2);

}

在这个方法中,我们添加了一个新页面(InvokatronWizardPage2),我们在后面编辑它。下面是用户点击向导的"完成"按钮的时候执行的一些方法:

public boolean performFinish() {

//首先把所有的页面数据保存在变量中

final String containerName = page.getContainerName();

final String fileName =page.getFileName();

final InvokatronDocument properties = new InvokatronDocument();

properties.setProperty(InvokatronDocument.PACKAGE,page2.getPackage());

properties.setProperty(InvokatronDocument.SUPERCLASS,page2.getSuperclass());

properties.setProperty(InvokatronDocument.INTERFACES,page2.getInterfaces());

//现在调用完成(finish)方法

IRunnableWithProgress op =new IRunnableWithProgress() {

public void run(IProgressMonitor monitor)

throws InvocationTargetException {

try {

doFinish(containerName, fileName,properties,monitor);

} 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;

}

return true;

}

为了保存数据,我们必须做一个后台事务。该事务是由向导的容器(Eclipse工作台)来执行的,并且必须实现IRunnableWithProgress接口,包含(唯一)一个run()方法。传递进来的IProgressMonitor答应我们报告事务的进度。实际的数据保存工作在一个辅助方法(doFinish())中进行:

private void doFinish(String containerName,String fileName, Properties properties,

IProgressMonitor monitor)

throws CoreException {

// 建立一个示例文件

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

IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();

IResource resource = root.findMember(new Path(containerName));

if (!resource.exists() !(resource instanceof IContainer)) {

throwCoreException("Container \"" + containerName + "\" does not exist.");

}

IContainer container =(IContainer)resource;

final IFile iFile = container.getFile(new Path(fileName));

final File file =iFile.getLocation().toFile();

try {

OutputStream os = new FileOutputStream(file, false);

properties.store(os, null);

os.close();

} catch (IOException e) {

e.printStackTrace();

throwCoreException("Error writing to file " + file.toString());

}

//确保项目已经刷新了,该文件在Eclipse API 之外建立

container.refreshLocal(IResource.DEPTH_INFINITE, monitor);

monitor.worked(1);

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

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

public void run() {

IWorkbenchPage page =PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();

try {

IDE.openEditor(page,iFile,true);

} catch (PartInitException e) {

}

}

});

monitor.worked(1);

}

我们还做了很多工作:

· 我们检索了自己希望保存文件的位置(用Eclipse的IFile类)。

· 我们还获取了该File。

· 我们把属性保存到了这个位置。

· 接着我们让Eclipse工作台刷新项目,这样就可以显示该文件了。

· 我们最后调度了一个事务,它在以后执行。这个事务包括在编辑器中打开那个新文件。

· 在整个过程中,我们通过调用IProgressMonitor对象(它是作为参数传递进来的)的方法来提示用户目前的进展情况。

最后一个方法是一个辅助的方法,当该文件保存失败的时候,它在向导中显示错误信息:

private void throwCoreException(String message) throws CoreException {

IStatus status =new Status(IStatus.ERROR,"Invokatron",IStatus.OK,message,null);

throw new CoreException(status);

}

}

向导可以捕捉CoreException异常,接着可以把它所包含的Status对象显示给用户看。向导不会被关闭。

编写新的向导页面的代码

下一步,我们编写InvokatronWizardPage2。它的整个类都是全新的:

public class InvokatronWizardPage2 extends WizardPage {

private Text packageText;

private Text superclassText;

private Text interfacesText;

private ISelection selection;

public InvokatronWizardPage2(ISelection selection) {

super("wizardPage2");

setTitle("Invokatron Wizard");

setDescription("This wizard creates a new"+" file with *.invokatron extension.");

this.selection = selection;

}

private void updateStatus(String message) {

setErrorMessage(message);

setPageComplete(message == null);

}

public String getPackage() {

return packageText.getText();

}

public String getSuperclass() {

return superclassText.getText();

}

public String getInterfaces() {

return interfacesText.getText();

}

上面的构造函数设置了页面的标题(在标题栏下方高亮度显示)和描述(在页面标题的下方显示)。我们还有一些辅助方法。 updateStatus处理页面特定的错误信息的显示。假如没有错误信息,就意味着页面完成了;因此,"下一步"按钮就可以使用了。还有数据字段内容的getter(获取)方法。下面是createControl()方法,它建立了页面的所有可视化组件:

public void createControl(Composite parent) {

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

GridLayout layout = new GridLayout();

controls.setLayout(layout);

layout.numColumns = 3;

layout.verticalSpacing = 9;

Label label =new Label(controls, SWT.NULL);

label.setText("&Package:");

packageText = new Text(controls,SWT.BORDER SWT.SINGLE);

GridData gd = new GridData(GridData.FILL_HORIZONTAL);

packageText.setLayoutData(gd);

packageText.addModifyListener(

new ModifyListener() {

public void modifyText(ModifyEvent e) {

dialogChanged();

}

});

label = new Label(controls, SWT.NULL);

label.setText("Blank = default package");

label = new Label(controls, SWT.NULL);

label.setText("&Superclass:");

superclassText = new Text(controls,SWT.BORDER SWT.SINGLE);

gd = new GridData(GridData.FILL_HORIZONTAL);

superclassText.setLayoutData(gd);

superclassText.addModifyListener(new ModifyListener() {

public void modifyText(ModifyEvent e) {

dialogChanged();

}

});

label = new Label(controls, SWT.NULL);

label.setText("Blank = Object");

label = new Label(controls, SWT.NULL);

label.setText("&Interfaces:");

interfacesText = new Text(controls,SWT.BORDER SWT.SINGLE);

gd = new GridData(GridData.FILL_HORIZONTAL);

interfacesText.setLayoutData(gd);

interfacesText.addModifyListener(

new ModifyListener() {

public void modifyText(ModifyEvent e) {

dialogChanged();

}

});

label = new Label(controls, SWT.NULL);

label.setText("Separated by ','");

dialogChanged();

setControl(controls);

}

为了编写这段代码,你必须了解SWT(请你自己查看一些这方面的资料)。基本上,这个方法建立了标签和字段,并把它们放置到网格布局上。字段发生改变的时候,就调用dialogChanged()来验证它的数据:

private void dialogChanged() {

String aPackage = getPackage();

String aSuperclass = getSuperclass();

String interfaces = getInterfaces();

String status = new PackageValidator().isValid(aPackage);

if(status != null) {updateStatus(status);

return;

}

status = new SuperclassValidator().isValid(aSuperclass);

if(status != null) {updateStatus(status);

return;

}

status = new InterfacesValidator().isValid(interfaces);

if(status != null) {updateStatus(status);

return;

}

updateStatus(null);

}

}

这个工作是在三个工具类--PackageValidator、SuperclassValidator和 InterfacesValidator的帮助下完成的。接下来我们编写这些类。

验证类

验证可以在插件的用户输入数据的任何部分中进行。因此,把验证代码放入可重复使用的类中是有意义的,这样就不用把它复制到多个位置。下面是一个验证类的例子。

public class InterfacesValidator implements ICellEditorValidator

{

public String isValid(Object value)

{

if( !( value instanceof String) )

return null;

String interfaces = ((String)value).trim();

if( interfaces.equals(""))

return null;

String[] interfaceArray = interfaces.split(",");

for (int i = 0; i < interfaceArray.length; i++)

{

IStatus status = JavaConventions.validateJavaTypeName(interfaceArray[i]);

if (status.getCode() != IStatus.OK)

return "Validation of interface " + interfaceArray[i] + ": " + status.getMessage();

}

return null;

}

}

其它的验证类与它非常类似。

Eclipse类库中的另外一个极好的类是JavaConventions,它为我们验证数据!它包含了很多验证方法,例如:

· validateJavaTypeName() 检查类和接口的名称。

· validatePackageName() 检查程序包的名称。

· validateFieldName() 检查数据成员的名称。

· validateMethodName() 检查方法的名称。

· validateIdentifierName() 检查变量的名称。

现在我们不需要ICellEditorValidator接口,但是在以后的文章中,我们是需要它的。

结果

到目前为止,我们拥有了一个可以工作的向导,它拥有一张图片和两个页面,第二个页面建立了原来的Invokatron文档。图2显示了结果:

2:定制的向导

闪亮的发明

我们可以看到,通常是数据驱动应用程序的。外表(Presentation)也是很重要的。丑陋的发明难以出售,但是闪亮的发明可能轻易出售。但是数据是我们这些程序员实现的非常本质的东西。

在本文中,我们首先决定了自己将处理的数据。然后,我们以定制向导的方式来获取这些数据。下一篇文章将继续讲解显示的问题,包括定制的编辑器和属性页面。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有