首页 > 开发 > 综合 > 正文

Eclipse插件开发之定制向导

2024-07-21 02:15:04
字体:
来源:转载
供稿:网友

最大的网站源码资源下载站,

  以前我有一个微型的便携式电子地址薄。我一直认为它很不错,直到有一天它停止运行了。销售该产品的人员无法找回我的联系地址名册,却提议更换一台。这时候我才知道数据的重要性。这个闪亮的小发明与存储在它里面的数据相比根本就不值一提。

  在这个序列文章的第一部分中,我介绍了eclipse插件的开发环境,并开发了一个简单的插件。在第二部分,我添加了工具条按钮、菜单项和对话框。它实际上没有实现任何具体功能。它简单地用某种字体显示了示例文本内容。现在我们要让它能够管理实际的数据。我们将修改这个插件,让它实现我们所需要的功能。本文讨论的是编辑器文档,并演示了如何定制一个向导。

  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)也是很重要的。丑陋的发明难以出售,但是闪亮的发明可能容易出售。但是数据是我们这些程序员实现的非常本质的东西。

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

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表