翻译|其它|编辑:郝浩|2008-01-14 10:09:30.000|阅读 1472 次
概述:
# 界面/图表报表/文档/IDE等千款热门软控件火热销售中 >>
Visual Editor for Java 是基于 eclipse 的 GUI 可视化编辑插件,该插件以 Java 代码为中心,实现了所见即所得的用户界面编辑方式,广泛支持目前的 SWT、AWT、Swing 等控件包。本文通过一个完整的对 Visual Editor 的扩展示例,向读者展示扩展 Visual Editor 的控件和属性的方法。
扩展 Visual Editor 的控件和属性
Visual Editor for Java 是基于 eclipse 的 GUI 可视化编辑插件,该插件以 Java 代码为中心,实现了所见即所得的用户界面编辑方式,广泛支持目前的 SWT、AWT、Swing 等控件包。本文通过一个完整的对 Visual Editor 的扩展示例,向读者展示扩展 Visual Editor 的控件和属性的方法。
背景
Visual Editor for Java 实现了基于 Java 源代码的所见即所得的用户界面开发模式。当 Java 源代码打开后,Visual Editor 将对其进行解析,寻找可以识别的控件,并将其展现至图形化的编辑器中,其外观和运行时完全一致。支持用户由 Palette 引发的控件拖拽的操作,支持对控件属性的编辑,同时实时的生成相应的代码。
图 1:Visual Editor 工作区概览
如上图截图所示,Visual Editor 的编辑区主要由五部分构成:
Visual Editor 各部分的交互情况,可以由下图表示,该图中初始化动作,就是通过对 Java 代码的解析,将其内容反应至属性视图或者属性编辑器中,这是代码生成的逆过程。
图 2:Visual Editor 各模块交互图
Palette 中提供了常用的 UI 控件的实现,包括 Swing、AWT、SWT 等的支持。如果用户希望根据自身需求,定制所需的控件,又希望能够通过 Visual Editor 进行识别和编辑,则需要对 Visual Editor 进行相应的扩展。这就是本文讲述的内容。
引言
本文希望对 Visual Editor 进行扩展,加入符合特定业务逻辑的控件,该控件的需求如下:
用户名
在该实例过程中,本文将说明如何:
控件实现
TextItem 是该控件的具体实现,读者可以参阅本文附件的源码,需要提及的是:
清单1:设置 TextItemModel
public void setModel(TextItemModel model){ this.model = model; titleLabel.setText(model.getTitle()); this.pack(); } |
清单 2: 判断字符长度越界
contentText.addVerifyListener(new VerifyListener(){ public void verifyText(VerifyEvent e) { String startStr = ((Text) e.widget).getText().substring(0, e.start); String endStr = ((Text) e.widget).getText().substring(e.end); String str = startStr + e.text + endStr; if ((model != null) && (str.length() > model.getMaxLength())) { e.doit = false; if (beep) Display.getCurrent().beep(); } } }); |
![]() ![]() |
BeanInfo 实现
BeanInfo 类实现了 java.beans.BeanInfo 接口,它被 Visual Editor 用来描述控件在属性视图中的行为。因为 BeanInfo 类仅在设计用户界面时被用到,因此运行时是不需要该类的。Visual Editor 定义了一些规则,用以将 BeanInfo 类与其描述的控件相联系起来,正如下文所述。
控件可以继承其父类或其他控件的属性行为,由 java.beans.Introspector 来获取相应控件的 BeanInfo 描述,并作为 getAdditionalBeanInfo() 方法的返回值,就可以将其他控件的属性行为继承至本控件,如下面代码所示:
清单 3:继承控件的属性行为
public BeanInfo[] getAdditionalBeanInfo() { try{ return new BeanInfo[]{Introspector.getBeanInfo(Control.class)}; } catch (IntrospectionException e){ return new BeanInfo[0]; } } |
java.beans.PropertyDescriptor 用以描述控件的属性,该属性应该在控件中存在一对公开的(public 的) Get 方法和 Set 方法进行访问。例如,setName(String name) 和 getName() 方法,对应的 property 的名字就是“name”,注意第一个字母是小写。对 PropertyDescriptor 还有很多可选的设定,比如设定该 property 是否显示,在属性视图中显示的名称以及该属性的描述等。
以本控件的 Beep 属性为例,该属性是一个布尔型属性,对应文本框是否有超出输入长度的声音提示。描述该属性的 PropertyDescriptor 如下:
清单4:描述 Beep 属性的 PropertyDescriptor
public PropertyDescriptor getBeepPropertyDescriptor(){ PropertyDescriptor pd; try { pd = new PropertyDescriptor("beep", TextItem.class); pd.setDisplayName("TextItem notifier"); pd.setShortDescription("Beep when the length exceed specified."); pd.setValue("enumerationValues", new Object[] { "No Beep", Boolean.FALSE, "developerworks.ve.example.textitem.TextItem.NO_BEEP", "Beep", Boolean.TRUE, "developerworks.ve.example.textitem.TextItem.BEEP"}); } catch (IntrospectionException e) { pd = null; } return pd; } |
PropertyDescriptor 的构造函数传入的参数分别为属性名称和控件类,通过这两个信息就可以反射获取到控件类中某一属性特定的内容。该属性显示在属性视图中的效果如下图所示:
图 3:属性视图结构
对应代码来看:
textitem.setBeep(developerworks.ve.example.textitem.TextItem.NO_BEEP);
在 BeanInfo 类中,体现对属性描述的方法是 getPropertyDescriptors(),它返回上文所述的 PropertyDescriptor 的一个数组,每个 PropertyDescriptor 都包含了对该控件的某个属性的具体描述。
属性编辑器的实现
对 TextItem 的 TextItemModel 属性来说,它是一个定制的属性类,而不是简单的数据类型或者字符串类型,对于它,用户可能希望拥有独立的编辑器对其进行编辑,实现的效果大概像下面的样子:
图 4:属性编辑器预览
该属性编辑对话框是 TitleAreaDialog 的一个实现,实现过程略去不谈,如何在属性视图中激活该对话框?
属性视图中每个属性都是 key 和 value 的集合,对于 Value 的设置,可以是简单的文本框,比如字符串型,整数型等,也可以是下拉列表框,如布尔型等。与此同时,也可以实现为弹出对话框的编辑框。该种类的编辑框可以是 JFace 的 DialogCellEditor 的实现子类,并继承 Visual Editor 的 INeedData 接口。DialogCellEditor 是抽象类,它的唯一的抽象方法就是 openDialogBox(),用以打开上图所示的对话框。
定制化的编辑框,如何建立与控件相应属性的连接?这需要借助 Visual Editor 的 override 机制。BeanInfo 提供了 override 的技术,用以扩展属性的编辑行为。Visual Editor 通过 override 文件来扩展出丰富的可定制化的编辑器。
向 Visual Editor 扩展 override 文件,需要对“org.eclipse.jem.beaninfo.registrations”扩展点进行扩展,使其能够识别扩展的 Override 文件的位置。本例中的扩展如下:
清单 5:org.eclipse.jem.beaninfo.registrations 扩展点
<extension point="org.eclipse.jem.beaninfo.registrations"> <registration container="developerworks.ve.example.textitem"> <override package="developerworks.ve.example.textitem" path="overrides/developerworks/ve/example/textitem"> </override> </registration> </extension> |
其中,path 指向保存 override 文件的路径。对应于 TextItem 而言,其 override 文件为 TextItem.override,该文件的目的就是由定制话的编辑行为覆盖掉原有属性的编辑行为。本例中的 TextItem.override 如下:
清单 6:Override 文件清单
<?xml version="1.0" encoding="UTF-8"?> <xmi:XMI xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:org.eclipse.ve.internal.cde.decorators= "http:///org/eclipse/ve/internal/cde/decorators.ecore" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" xmlns:event="event.xmi"> <event:Add featureName="eStructuralFeatures"> <addedEObjects xsi:type="ecore:EReference" name="model" unsettable="true"> <eAnnotations xsi:type="org.eclipse.ve.internal.cde.decorators:BasePropertyDecorator" cellEditorClassname= "developerworks.ve.example.textitem/ \ developerworks.ve.example.textitem.TextItemModelEditor" /> </addedEObjects> </event:Add> </xmi:XMI> |
该文件中值得注意的几个地方地方:
通过上述几步操作,即可以用定制化的编辑器行为覆盖掉 BeanInfo 中缺省的编辑器行为,实现了对属性编辑的自由扩展。
属性编辑器与 Java 代码的同步
属性编辑器与 Java 代码有同步的需求,即可以将已经生成的 Java 代码进行解析,将解析的结果赋给打开后的属性编辑器显示,同时又需要对用户在属性编辑器中输入的信息进行解析,并生成 Java 代码。这两个是互逆的过程。
Visual Editor 的核心部分,是描述被编辑控件的EMF模型,该模型描述了控件实例,控件之间的各种关系,以及可对控件进行编辑操作的各种属性等内容。该模型的实例实现了 org.eclipse.jem.internal.instantiation.base.IJavaInstance 接口。也就是所,上述的解析 Java 代码的过程,可以转化为解析 IJavaInstance 对象的过程;而生成 Java 代码的过程,也就是生成 IJavaInstance 对象的过程。
如何解析 IJavaInstance 对象?对于本例来讲,下面是解析的一个片段,可以看到,对 IJavaObjectInstance 对象的解析,就是对其主体构造函数的解析,因为 set 和 get 方法对该属性操作,会创建一个该属性对象对应的实例,该创建就是通过构造函数。解析出该构造函数,就可以还原该对象的内容。getArguments() 方法,就是获取该构造函数传入的参数,由这些参数,最终还原出该对象。
清单 7:解析 IJavaObjectInstance 对象
JavaAllocation allocation = ((IJavaObjectInstance) getValue()).getAllocation(); PTExpression exp = ((ParseTreeAllocation) allocation).getExpression(); PTClassInstanceCreation instance = ((PTClassInstanceCreation) exp); PTStringLiteral stringLiteral = (PTStringLiteral) instance.getArguments().get(0); String title = stringLiteral.getLiteralValue(); PTNumberLiteral numberLiteral = (PTNumberLiteral) instance.getArguments().get(1); int max = Integer.parseInt(numberLiteral.getToken()); model = new TextItemModel(title, max); |
如何生成 Java 代码?即如何生成 IJavaObjectInstance 的实例?还是要回到构造函数上来,通过初始化字符串,传入正确类型的参数,生成相应的构造函数语句,就能够创建出 Visual Editor 可识别的 IJavaObjectInstance 实例,从而转化为代码编辑区中的 Java 代码。本例中解析的代码如下:
清单8:创建 IJavaObjectInstance 实例
private static final String TEXTITEMMODELCLASSPATH = "developerworks.ve.example.textitem.TextItemModel"; private IJavaInstance createJavaObject(TextItemModel model) { return BeanUtilities.createJavaObject(TEXTITEMMODELCLASSPATH, JavaEditDomainHelper.getResourceSet(editDomain), getJavaAllocation(model)); } private JavaAllocation getJavaAllocation(TextItemModel model) { String initString = "new " + TEXTITEMMODELCLASSPATH + "(" + "\"" + model.getTitle() + "\", " + model.getMaxLength() + ")"; return BeanPropertyDescriptorAdapter.createAllocation(initString, editDomain); } |
扩展控件至 Palette
到目前为止,围绕该控件的编码工作已经完成:拥有了定制化的控件,描述该控件的 BeanInfo,以及对该控件进行编辑的编辑器。下一步要做的就是将该控件集成到 Visual Editor 的编辑器中,供用户使用了。
如何扩展该控件至 Palette?Visual Editor 的 Palette 由 EMF 的模型来描述,EMF 可以序列化为 XMI,而插件可以定制它们自己的XMI文件,描述控件的种类,归属于哪些控件组,以及图标和实现类等信息。对于本例中的 TextItem 来讲,编写了 palette.xmi 文件来描述该控件,使其被 Palette 所识别,对应于 TextItem,是<children></children>中的内容,分别描述了该控件由哪种图标表示,实现类是哪个,在 Palette 中显示的名称,以及创建后的实例,默认采用的名称。
清单 9:Palette.xmi 文件
<?xml version="1.0" encoding="UTF-8"?> <org.eclipse.ve.internal.cde.palette:Drawer xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" xmlns:org.eclipse.ve.internal.cde.palette= "http:///org/eclipse/ve/internal/cde/palette.ecore" xmlns:org.eclipse.ve.internal.cde.utility= "http:///org/eclipse/ve/internal/cde/utility.ecore"> <entryLabel xsi:type="org.eclipse.ve.internal.cde.utility:ConstantString" string="Custom Widgets"/> <children xsi:type="org.eclipse.ve.internal.cde.palette:EMFCreationToolEntry" icon16Name="platform:/plugin/developerworks.ve.example.textitem/icons/sample.gif" creationClassURI="java:/developerworks.ve.example.textitem#TextItem"> <entryLabel xsi:type="org.eclipse.ve.internal.cde.utility:ConstantString" string="TextItem"/> <keyedValues xsi:type="ecore:EStringToStringMapEntry" key="org.eclipse.ve.internal.cde.core.nameincomposition" value="textitem"/> </children> </org.eclipse.ve.internal.cde.palette:Drawer> |
该 palette.xmi 文件,需要 Visual Editor 知道其路径,因此需要在对“org.eclipse.ve.java.core.contributors”扩展点做如下扩展,使其能够读取到扩展的定义文件。
清单10:“org.eclipse.ve.java.core.contributors”扩展点
<extension point="org.eclipse.ve.java.core.contributors"> <palette container="developerworks.ve.example.textitem" categories="palette.xmi"/> </extension> |
更改控件的创建参数
经过上述定制,控件已经显示于 Palette 中,拖拽控件至图形编辑区已经可以预览该控件和生成相应的代码,生成控件的 Java 代码为 new TextItem(parent, SWT.NONE)。也就是说,控件生成传入构造函数的参数,第二项 style 默认为 SWT.NONE。如果希望该控件构造函数传入的参数 style 为 SWT.BORDER 或其他样式,如何定制呢?
Visual Editor 提供了这种定制的支持,同样依赖于 XMI 文件,实现对构造函数默认参数的覆盖。本例中,BorderTextItem 是 TextItem 的另一种使用,不同点仅在于生成代码中,构造函数传入参数不同。BorderTextItem 传入的 style 参数为 SWT.BORDER,并将该样式赋给了该控件的文本框。覆盖该构造函数参数的 XMI 文件如下:
清单 11:BorderTextItem.xmi
<?xml version="1.0" encoding="UTF-8"?> <xmi:XMI xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" xmlns:cdm="http:///org/eclipse/ve/internal/cdm.ecore" xmlns:utility="http:///org/eclipse/ve/internal/cde/utility.ecore" xmlns:org.eclipse.jem.internal.instantiation= b"http:///org/eclipse/jem/internal/instantiation.ecore" xmlns:java="java.xmi" xmlns:textitem="java:/developerworks.ve.example.textitem"> <textitem:TextItem xmi:id="BorderTextItem_1"> <allocation xsi:type="org.eclipse.jem.internal.instantiation:ParseTreeAllocation"> <expression xsi:type="org.eclipse.jem.internal.instantiation:PTClassInstanceCreation" type="developerworks.ve.example.textitem.TextItem"> <arguments xsi:type="org.eclipse.jem.internal.instantiation:PTName" name="{parentComposite}"/> <arguments xsi:type="org.eclipse.jem.internal.instantiation:PTFieldAccess" field="BORDER"> <receiver xsi:type="org.eclipse.jem.internal.instantiation:PTName" name="org.eclipse.swt.SWT"/> </arguments> </expression> </allocation> </textitem:TextItem> <cdm:AnnotationEMF annotates="BorderTextItem_1"> <keyedValues xmi:type="ecore:EStringToStringMapEntry" key="org.eclipse.ve.internal.cde.core.nameincomposition" value="bordertextitem"/> </cdm:AnnotationEMF> </xmi:XMI> |
本文件中,<textitem:TextItem> </textitem:TextItem>标记符内的内容就是该控件的定义。
这里的TextItem规范了该控件的返回类型,即创建该控件后赋予的引用的类型。其地位如同“TextItem item = new TextItem(parent, SWT.BORDER)”语句中的左端的返回类型 TextItem。PTClassInstanceCreation 就是创建该控件的语句,里面传入的 arguments 就是构造函数的传入参数 parent 和 SWT.BORDER。对于第二项参数,替换了原有的创建行为 SWT.NONE,receiver 的名称为 org.eclipse.swt.SWT,field 就是 BORDER,这就意味着构造函数传入的第二个参数为 SWT.BORDER 了。之后要做的就是在表示 Palette 的 palette.xmi 中增加一个控件项,将该控件项与此 XMI 文件做连接,就完成了构造函数参数的覆盖。加入的控件项如下:
清单 12:palette.xmi 文件中 BorderTextItem 定义
<children xsi:type="org.eclipse.ve.internal.cde.palette:EMFPrototypeToolEntry" icon16Name="platform:/plugin/developerworks.ve.example.textitem/icons/sample.gif" icon32Name="" id="" prototypeURI="platform:/plugin/developerworks.ve.example.textitem/ \ palette/BorderTextItem.xmi#BorderTextItem_1"> <entryLabel xsi:type="org.eclipse.ve.internal.cde.utility:ConstantString" string="BorderTextItem"/> </children> |
prototyleURI 指向了定义该控件的位置,即 developerworks.ve.example.textitem 插件下的 palette 文件夹内的 BorderTextItem.xmi 文件,对应该文件中的 BorderTextItem_1 创建项。由此建立了 Palette 与构造函数的 XMI 文件的联系。
定义 ClassPath 容器
用户如何将定制化的控件加到他们的构建路径里?和 JRE System Library 一样,如果我们的控件打包为 textitem.jar,他们可能希望有下图演示的方便的构建方式:
图 5:添加 Class Library 预览
如何定制 ClassPath 容器?
首先区分一下,本文前述的 Java 类,哪些需要在 runtime 时使用,哪些仅仅是 Visual Editor 使用:
所以,打包 runtime 用的 jar 包,仅需将 TextItem 类和 TextItemModel 类打包为 textitem.jar,这是创建 ClassPath 容器的第一步。而 TextItemBeanInfo 和 TextItemModelEditor 为插件扩展才会用到的类,则可以另外打包为 textitembeaninfo.jar,放入导出的插件中,并对扩展点“org.eclipse.jem.beaninfo.registrations”进行扩展,令 Visual Editor 能够找到扩展的 BeanInfo 类和属性编辑器类:
清单 13:扩展 BeanInfo 项
<beaninfo path="textitembeaninfo.jar"> <searchpath package="developerworks.ve.example.textitem"/> </beaninfo> |
之后就是将 textitem.jar 做成 Class Library 并加入到 Class Path 容器中了。该步需要对 eclipse 的 jdt 的一些扩展点做扩展,扩展如下,可以参阅 eclipse 的帮助文件中扩展点的说明了解各扩展点的含义,此处不再详述:
清单 14:扩展 Classpath Container Page 扩展点
<extension point="org.eclipse.jdt.ui.classpathContainerPage"> <classpathContainerPage name="Custom Widgets" class="org.eclipse.ve.internal.java.wizard.RegisteredClasspathContainerWizardPage" id="developerworks.ve.example.textitem"> </classpathContainerPage> </extension> <extension point="org.eclipse.jdt.core.classpathContainerInitializer"> <classpathContainerInitializer class="org.eclipse.ve.internal.java.core.RegisteredClasspathContainerInitializer" id="developerworks.ve.example.textitem"> </classpathContainerInitializer> </extension> <extension point="org.eclipse.ve.java.core.registrations"> <registration container="developerworks.ve.example.textitem" description="Custom Widgets"> <library runtime="textitem.jar"/> </registration> </extension> |
使用定制后的控件
至此,定制控件的插件已经完成。将该插件打包放入 eclipse 的 plugin 文件夹下,重启 eclipse,就可以激活该插件,在 Visual Editor 中使用定制后的控件了。下面从使用者的角度贯穿一下如何使用该定制控件,读者可以根据每个步骤,思考在本文中是如何实现该步骤的功能的:
Step1:创建 Java 的 Project,将名为 Custom Widgets 的 ClassPath 容器以及 SWT 的类容器加入项目的构建路径:
图 6:添加构建路径
Step2: 创建 Visual Class,可以在 Style 中选中 SWT 的 Composite 或 Shell 作为该 Visual Class 的基类面板,这里选择为 Composite:
B>图 7:新建 Visual Class
Step3:由 Palette 中拖拽 TextItem 或 BorderTextItem 至图形编辑区的 Composite 上,代码编辑区可以相应生成代码。
图 8:拖拽控件
Step4:在属性视图中编辑该控件的属性“TextItem notifier”设置为 BEEP,由 TextItem model 的编辑器打开编辑对话框,输入 Title为“User Name: ”,最大长度为 8。
图 9:编辑属性
Step5:运行该 Java 文件,预览一下成果,运行效果应该如下图所示,当在文本框中输入的用户名字符数超过 8 个,则会有 Beep 的声音提示:
图 10:运行截图
结束语
本文向您阐述了如何扩展 Visual Editor,加入定制化的控件供用户使用,以及相关的很多问题和解决方案,供您参考。通过这种定制,用户可以很容易的所见即所得的将定制后的控件放到他们希望的地方,并能够图形化的编辑该控件的各种属性,使得开发 Java 用户界面的工作变得简单和可视化。
本文附件中包含了该插件和其源码,读者可以进行体验和参考。请尽量阅读该插件的完整代码,这有助于您了解本文的内容。
本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@evget.com
文章转载自:IBM