Invokatron的历史
首先我们详细说明一下Invokatron本身在前面的文章中我们讨论过Invokatron是一个生成Java代码的的图形工具你可以简单地通过拖放操作建立类的方法拖入的方法被编辑的方法(也就是插件)调用我们将让数据来驱动应用程序的设计在后面一篇文章中我们将开发这个GUI现在我们需要做的是找到插件将输入和存储的重要数据它通常被称为应用程序的模型(model)在设计这个系统的时候我们需要考虑下面一些内容
· 哪些细节数据需要保存?
· 这些数据在内存中用什么来表现?POJOJavaBean还是EJB?
· 这些数据的存储格式是怎样的?数据库表XML文件属性文件还是串行二进制文件?
· 输入数据的方式有哪几种?用新建文件向导还是在文档属性页面上使用弹出对话框用编辑器绘制在文本编辑器中输入的其它向导?
在我们继续工作之前必须回答这些问题不可能有适合所有项目的答案它完全依赖于你的需求在我们的例子中我做出了一些随意的可能有问题的决定如下所示
· 一个Java类它包含类名程序包超类(superclass)和实现接口我们以它为基础在后面的文章中添加更多数据
· 我将把数据表现为扩展Properties类的类它建立了编辑器的文档类
· 我将使用的格式是属性文件很容易使用Properties类来分析它
· 在新建文件向导中我将先寻找数据接着让用户改变属性窗口或文本编辑器中的数据这个步骤将在下一篇文章中完成
Document(文档)类
下一步是编写文档类建立一个新程序包(invokatronmodel)和一个新类(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 =documentgetProperty(InvokatronDocumentPACKAGE);
定制向导
请看一看前面的文章中所出现的向导你应该记得我们可以通过点击(我们自己添加的)工具条按钮或者菜单项来访问它图是它的界面
图旧的向导
它只有一个页面右上角没有图片我们想输入更多的信息并提供一个很好的图片换句话说我们希望定制这个向导
我们来分析一下这个向导请打开InvokatronWizardjava文件请注意这个类是如何扩展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)如果为trueNext按钮就可视
· performHelp()重载它来提供内容敏感的帮助信息当点击Help按钮的时候向导会调用它
编写向导的代码
有了这些方法之后我们就能够开发出具有极大的灵活性的向导了我们现在修改以前建立的Invokatron向导给它添加一个页面来请求用户输入初始的文档数据我们还给向导添加了一个图片新代码是粗体的
public class InvokatronWizard extends Wizard
implements INewWizard {
private InvokatronWizardPage page;
private InvokatronWizardPage page;
private ISelection selection;
public InvokatronWizard() {
super();
setNeedsProgressMonitor(true);
ImageDescriptor image =AbstractUIPluginimageDescriptorFromPlugin(Invokatron icons/InvokatronIconGIF);
setDefaultPageImageDescriptor(image);
}
public void init(IWorkbench workbenchIStructuredSelection selection) {
thisselection = selection;
}
在构造函数中我们打开了进度条并设置了向导的图片你可以下载并保存下面的图片
请把这个图片保存在Invokatron/icons文件夹之下为了更容易载入这个图片我们使用了便捷的AbstractUIPluginimageDescriptorFromPlugin()方法
请注意你应该知道尽管这个向导是INewWizard类型的但是并非所有的向导都是用于建立新文档的你可以参考其它一些资料来了解如何建立独立的向导的信息
下面是addPages()方法
public void addPages() {
page=new InvokatronWizardPage(selection);
addPage(page);
page = new InvokatronWizardPage(selection);
addPage(page);
}
在这个方法中我们添加了一个新页面(InvokatronWizardPage)我们在后面编辑它下面是用户点击向导的完成按钮的时候执行的一些方法
public boolean performFinish() {
//首先把所有的页面数据保存在变量中
final String containerName = pagegetContainerName();
final String fileName =pagegetFileName();
final InvokatronDocument properties = new InvokatronDocument();
propertiessetProperty(InvokatronDocumentPACKAGEpagegetPackage());
propertiessetProperty(InvokatronDocumentSUPERCLASSpagegetSuperclass());
propertiessetProperty(InvokatronDocumentINTERFACESpagegetInterfaces());
//现在调用完成(finish)方法
IRunnableWithProgress op =new IRunnableWithProgress() {
public void run(IProgressMonitor monitor)
throws InvocationTargetException {
try {
doFinish(containerName fileNamepropertiesmonitor);
} catch (CoreException e) {
throw new InvocationTargetException(e);
} finally {
monitordone();
}
}
};
try {
getContainer()run(true false op);
} catch (InterruptedException e) {
return false;
} catch (InvocationTargetException e) {
Throwable realException =egetTargetException();
MessageDialogopenError(getShell()ErrorrealExceptiongetMessage());
return false;
}
return true;
}
为了保存数据我们必须做一个后台事务该事务是由向导的容器(Eclipse工作台)来执行的并且必须实现IRunnableWithProgress接口包含(唯一)一个run()方法传递进来的IProgressMonitor允许我们报告事务的进度实际的数据保存工作在一个辅助方法(doFinish())中进行
private void doFinish(String containerNameString fileName Properties properties
IProgressMonitor monitor)
throws CoreException {
// 建立一个示例文件
monitorbeginTask(Creating + fileName );
IWorkspaceRoot root = ResourcesPlugingetWorkspace()getRoot();
IResource resource = rootfindMember(new Path(containerName));
if (!resourceexists() || !(resource instanceof IContainer)) {
throwCoreException(Container \ + containerName + \ does not exist);
}
IContainer container =(IContainer)resource;
final IFile iFile = containergetFile(new Path(fileName));
final File file =iFilegetLocation()toFile();
try {
OutputStream os = new FileOutputStream(file false);
propertiesstore(os null);
osclose();
} catch (IOException e) {
eprintStackTrace();
throwCoreException(Error writing to file + filetoString());
}
//确保项目已经刷新了该文件在Eclipse API 之外建立
containerrefreshLocal(IResourceDEPTH_INFINITE monitor);
monitorworked();
monitorsetTaskName(Opening file for editing);
getShell()getDisplay()asyncExec(new Runnable() {
public void run() {
IWorkbenchPage page =PlatformUIgetWorkbench()getActiveWorkbenchWindow()getActivePage();
try {
IDEopenEditor(pageiFiletrue);
} catch (PartInitException e) {
}
}
});
monitorworked();
}
我们还做了很多工作
· 我们检索了自己希望保存文件的位置(用Eclipse的IFile类)
· 我们还获取了该File
· 我们把属性保存到了这个位置
· 接着我们让Eclipse工作台刷新项目这样就可以显示该文件了
· 我们最后调度了一个事务它在以后执行这个事务包括在编辑器中打开那个新文件
· 在整个过程中我们通过调用IProgressMonitor对象(它是作为参数传递进来的)的方法来提示用户目前的进展情况
最后一个方法是一个辅助的方法当该文件保存失败的时候它在向导中显示错误信息
private void throwCoreException(String message) throws CoreException {
IStatus status =new Status(IStatusERRORInvokatronIStatusOKmessagenull);
throw new CoreException(status);
}
}
向导可以捕获CoreException异常接着可以把它所包含的Status对象显示给用户看向导不会被关闭
编写新的向导页面的代码
下一步我们编写InvokatronWizardPage它的整个类都是全新的
public class InvokatronWizardPage extends WizardPage {
private Text packageText;
private Text superclassText;
private Text interfacesText;
private ISelection selection;
public InvokatronWizardPage(ISelection selection) {
super(wizardPage);
setTitle(Invokatron Wizard);
setDescription(This wizard creates a new+ file with *invokatron extension);
thisselection = selection;
}
private void updateStatus(String message) {
setErrorMessage(message);
setPageComplete(message == null);
}
public String getPackage() {
return packageTextgetText();
}
public String getSuperclass() {
return superclassTextgetText();
}
public String getInterfaces() {
return interfacesTextgetText();
}
上面的构造函数设置了页面的标题(在标题栏下方高亮度显示)和描述(在页面标题的下方显示)我们还有一些辅助方法 updateStatus处理页面特定的错误信息的显示如果没有错误信息就意味着页面完成了因此下一步按钮就可以使用了还有数据字段内容的getter(获取)方法下面是createControl()方法它建立了页面的所有可视化组件
public void createControl(Composite parent) {
Composite controls =new Composite(parent SWTNULL);
GridLayout layout = new GridLayout();
controlssetLayout(layout);
layoutnumColumns = ;
layoutverticalSpacing = ;
Label label =new Label(controls SWTNULL);
labelsetText(&Package:);
packageText = new Text(controlsSWTBORDER | SWTSINGLE);
GridData gd = new GridData(GridDataFILL_HORIZONTAL);
packageTextsetLayoutData(gd);
packageTextaddModifyListener(
new ModifyListener() {
public void modifyText(ModifyEvent e) {
dialogChanged();
}
});
label = new Label(controls SWTNULL);
labelsetText(Blank = default package);
label = new Label(controls SWTNULL);
labelsetText(&Superclass:);
superclassText = new Text(controlsSWTBORDER | SWTSINGLE);
gd = new GridData(GridDataFILL_HORIZONTAL);
superclassTextsetLayoutData(gd);
superclassTextaddModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
dialogChanged();
}
});
label = new Label(controls SWTNULL);
labelsetText(Blank = Object);
label = new Label(controls SWTNULL);
labelsetText(&Interfaces:);
interfacesText = new Text(controlsSWTBORDER | SWTSINGLE);
gd = new GridData(GridDataFILL_HORIZONTAL);
interfacesTextsetLayoutData(gd);
interfacesTextaddModifyListener(
new ModifyListener() {
public void modifyText(ModifyEvent e) {
dialogChanged();
}
});
label = new Label(controls SWTNULL);
labelsetText(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);
}
}
这个工作是在三个工具类PackageValidatorSuperclassValidator和 InterfacesValidator的帮助下完成的接下来我们编写这些类
验证类
验证可以在插件的用户输入数据的任何部分中进行因此把验证代码放入可重复使用的类中是有意义的这样就不用把它复制到多个位置下面是一个验证类的例子
public class InterfacesValidator implements ICellEditorValidator
{
public String isValid(Object value)
{
if( !( value instanceof String) )
return null;
String interfaces = ((String)value)trim();
if( interfacesequals())
return null;
String[] interfaceArray = interfacessplit();
for (int i = ; i < interfaceArraylength; i++)
{
IStatus status = JavaConventionsvalidateJavaTypeName(interfaceArray[i]);
if (statusgetCode() != IStatusOK)
return Validation of interface + interfaceArray[i] + : + statusgetMessage();
}
return null;
}
}
其它的验证类与它非常类似
Eclipse类库中的另外一个极好的类是JavaConventions它为我们验证数据!它包含了很多验证方法例如
· validateJavaTypeName() 检查类和接口的名称
· validatePackageName() 检查程序包的名称
· validateFieldName() 检查数据成员的名称
· validateMethodName() 检查方法的名称
· validateIdentifierName() 检查变量的名称
现在我们不需要ICellEditorValidator接口但是在以后的文章中我们是需要它的
结果
到目前为止我们拥有了一个可以工作的向导它拥有一张图片和两个页面第二个页面建立了原来的Invokatron文档图显示了结果
定制的向导
闪亮的发明
我们可以看到通常是数据驱动应用程序的外表(Presentation)也是很重要的丑陋的发明难以出售但是闪亮的发明可能容易出售但是数据是我们这些程序员实现的非常本质的东西
在本文中我们首先决定了自己将处理的数据然后我们以定制向导的方式来获取这些数据下一篇文章将继续讲解显示的问题包括定制的编辑器和属性页面