EMF介绍系列(六、自定义命令)

摘要:
在EMF生成的应用程序中,用户发出的每个命令都可以被撤销。例如,如果修改了产品的价格,则可以通过单击“撤消”按钮恢复原始价格。当然,新的价格可以通过重做来恢复。在EMF中,命令框架实际上可以分为两部分,一部分是独立于模型的通用命令,另一部分是编辑命令,后者基于前者。通用命令可以与EMF完全分离,也就是说,这个命令框架可以应用于任何需要命令框架的应用程序,包括非EMF应用程序。

EMF生成的应用程序里,用户的发出的每一条命令都是可以撤销(Undo)的,例如修改了产品的价格,按一下撤销按钮就能恢复原来的价格,当然还可 以通过重做(Redo)再回到新的价格。为了实现这个功能,应用程序里维护了一个用于存放命令的类似栈的数据结构(CommandStack),每一条执 行过的命令都被存放在那里,需要撤销时取出最近一条命令进行撤销。这个数据结构是由EditingDomain对象负责维护的, EditingDomain相当于编辑模型时的环境。

在EMF里命令框架实际上可以分为两大部分,一部分是与模型无关的通用命令,另一部分是.Edit命令,后者是建立在前者的基础之上的。EMF对模 型的任何修改都是通过命令完成的,例如当用户在属性视图里修改一个对象的属性时,会生成一个新的SetCommand实例,然后执行它的execute ()方法,这个方法里对模型进行修改(实际上是通过doExecute()方法),成功执行完成后这个命令被放入命令栈以便撤销时使用。

通用命令可以完全脱离EMF使用,也就是说,这个命令框架可以应用到任何需要命令框架的应用程序中,包括非EMF应用程序。它位于 org.eclipse.emf.common.command包里,其中Command接口定义了什么是“命令“,一个命令具有execute()、 undo()和redo()等方法,还有canExecute()和canUndo()方法用于判断命令是否可被执行或撤销(考虑到资源消耗,有些命令可 能设计为不可撤销更合理)。另一个重要的接口是前面提到过的CommandStack,它的作用是保存所有命令,可以通过 addCommandStackListener()方法注册监听器来观察CommandStack的状态变化。CompoundCommand接口可以 把多个命令按顺序包装成一个组合命令,它具有原子性,类似数据库里事务(Transaction)的概念,只有所有命令都可执行时这个组合命令才可执行, 撤销也是如此。

EMF在.Edit框架提供了针对EMF模型编辑所需要的一些命令(位于org.eclipse.emf.edit.command包),例如 SetCommand用于修改对象的属性,CreateChildCommand的作用是创建一个子元素,还有MoveCommand、 CopyCommand和CutToClipboardCommand等等。这些命令都实现Command接口,并且大部分继承自 AbstractOverrideableCommand这个抽象类,它给我们带来的影响是在Command接口里的方法名前面都加了一个do,比如 execute()变为doExecute()、canUndo()变为doCanUndo()等等,我们在扩展这些.Edit命令时要覆盖doXXX方 法。.Edit命令是通过反射的方式来修改模型的。

EMF提供的这些命令为我们完成基本的模型编辑功能,多数情况下直接使用它们就可以了,但有时通过自定义的命令可以实现一些特别的需求。举个例子来 说,在网上商店的例子里,假设要求产品的价格只精确到小数点后两位,那么我们要在用户输入新价格以后立刻对这个数值进行四舍五入处理,这个操作就可以利用 自定义命令完成。因为利用了.Edit提供的类,所以一般我们应该扩展.Edit命令,具体来说就是SetCommand。

首先通过继承SetCommand创建我们的SetPriceCommand,在这个方法里覆盖doExecute()方法,SetCommand 里有很多可供利用的环境变量,我们要用到的是owner和value这两项,前者是要修改的对象,在这里是产品对象,后者是属性的新值,在这里也就是新价 格。所以我们的SetPriceCommand可以像下面这样写(为了使代码最简,我们直接把EObject类型转换为Product类型,这样就不需要 用反射的方式了):

public class SetPriceCommand extends SetCommand {
    
public SetPriceCommand(EditingDomain domain, EObject owner, EStructuralFeature feature, Object value) {
        
super(domain, owner, feature, value);
    }
    
public void doExecute() {
        Product product 
= (Product) owner;
        
double newPrice = ((Double) value).doubleValue();
        newPrice 
= Math.max(0, newPrice);//New price value must >= 0
        newPrice=Math.round(newPrice*100)/100d;//Max fraction digits is 2    
        product.setPrice(newPrice);
    }
}

要让这个自定义命令生效,必须在ProductItemProvider里覆盖createSetCommand()方法,因为这个方法缺省是返回SetCommand的,我们要在这里做一个判断:如果修改的是价格属性,就返回我们自定义的这个命令,如下所示:

protected Command createSetCommand(EditingDomain domain, EObject owner, EStructuralFeature feature, Object value,
        
int index) {
    
if (feature == ShopPackage.eINSTANCE.getProduct_Price())
        
return new SetPriceCommand(domain, owner, feature, value);
    
return super.createSetCommand(domain, owner, feature, value, index);
}

这样当用户在属性页里改价格属性时,就会调用我们的SetPriceCommand了。顺便说一句,在GEF里 也有类似的EditDomain和Command,只是GEF里的Command一般通过EditPolicy的createXXXCommand()方 法来创建。因为GEF和EMF的两套Command机制没有实现统一的接口,所以结合GEF和EMF的时候常会遇到一些问题,需要额外的代码帮助解决,请 参考这两处讨论(讨论1,讨论2)。

最后要说一句,CreateChildCommand有点特殊,它是.Edit命令但不继承 AbstractOverrideableCommand,而且如果想在创建子元素时自动完成一些工作,不应该通过扩展这个类完成,而应该在 XXXItemProvider的collectNewChildDescriptors()方法里处理,这个方法决定每个对象可以创建哪些子元素,你可 以修改它的代码以对新建的元素做一些处理。

参考资料: Eclipse Modeling Framework A Developers Guide,第3.3节、第14.1节。

点击下载源代码

免责声明:文章转载自《EMF介绍系列(六、自定义命令)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Openfire验证机制的修改(整合自定义用户表)多线程系列之生产者和消费者问题下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

TCL基本语法2

TCL基本语法21、format和scan两个基本的函数,和C语言中的sprintf和scanf的作用基本相同。format将不同类型的数据压缩在字符串中,scan将字符串中的数据提取出来。setnameJacksetage100setworker[format"%sis%dyearsold"$name$age]puts$workerscan$worker"...

Delete from join 用法

delete(别名)fromtblA(别名)leftjointblb(别名)on。。。...

db2数据导出导入del与ixf格式

ixf格式保存的是结构和数据,是一个二进制文件,ixf文件不可视。...

(转)JavaScript-性能优化之函数节流(throttle)与函数去抖(debounce)

简单来说,JavaScript性能优化的函数节流和函数去抖动功能节流就是使函数在极短的时间间隔内无法连续调用。下一个函数调用只能在上次函数执行超过指定的时间间隔后进行。对于这两个需求,有两种解决方案:去抖动和节流。Throwle和debouck是解决请求和响应速度不匹配问题的两种解决方案。抛出以相等的间隔执行函数。如果事件在反跳时间间隔t内再次触发,则将再次...

seata启动报错的可能原因,以及解决方案

seata启动错误的可能原因及解决方案。首先,我下载了seata 0.9版和jdk 12.0.2版。启动错误的截图是:它显示无法创建虚拟机。我尝试了很多方法,但都没有解决。...

android的apk权限查看

使用aapt工具查看apk包的权限aapt默认路劲为androidSDK安装路径下的build-tools文件夹下,需要讲aapt复制到adb的存放路径下即可在cmd中使用aapt命令查看apk的权限命令如下:aaptdumpbadgingxxx.apk如果觉得cmd窗口不好查找,可以将对应的输出重定向到文件中...