GEF配合EMF是很常用的eclipse可视化应用的解决方案,用EMF的好处就是可以根据uml,schema等生成应用模型代码,并且emf生成的模型代码包含模型的监听机制,省去了很多代码量。同样EMF.EDIT框架包含了模型的content,label provider和property source的支持,应用可以方便使用使用,你可以看到EMF.EDIT框架为每个模型生成了命名形式为 ****ProviderAdapter的类,继承自ItemProviderAdapter,又实现了诸如IEditingDomainItemProvider, IStructuredItemContentProvider, ITreeItemContentProvider, IItemLabelProvider, IItemPropertySource的接口,看出来了吧,这个了还是干了很多事儿的。今天主要讨论如何利用EMF.EDIT提供的对模型property source的支持,实现gef编辑器属性页(又省了N多代码啊,实现属性页支持一般要实现自己的IPropertySource和IPropertyDescriptor,很多代码啊)。先看一下实现后的样子,使用了eclipse tabbed property page:
Category tab:
A to Z tab:
咋实现的呢?由于俺也有2年多没摸gef/emf了,于是乎想起了当年作为入门学习的八进制同学的blog(http://www.cnblogs.com/bjzhanghao)借鉴了关于利用emf实现属性页的那篇文章,有兴趣的可以看一下。但是使用EMF.EDIT实现属性也还是有点问题。主要有:
1. 没有实现属性分类的可配置化,只能a-z的排列
2. 由于emf属性页改变模型使用emf自己的command框架,和gef的command框架不同,这就造成emf属性页修改模型后,undo有点问题。这个问题八进制也提过,不过现在我看了一下,似乎没当时那么严重了,gef/emf也在发展,dirty可以显示,就是undo完了值有问题。一会儿详细说明原因和解决办法。
下面就谈谈俺如何解决上面这两个问题的,不知道各位大侠有否更好的解决方法,希望指教点化。
先说第2点吧,导致undo问题的原因是在ItemPropertyDescriptor中的setPropertyValue(Object object, Object value)方法,第二个参数value不是真正要改变的值,而是PropertyValueWrapper对象,这个要归功于ItemPropertyDescriptor中的getPropertyValue方法把原本的属性值包装成了PropertyValueWrapper对象,于是乎SetValueCommand中保存的需要undo的属性值是PropertyValueWrapper类型,于是乎在undo是调用setPropertyValue就会出错。
解决办法很简单,实现自己的ItemPropertyDescriptor覆盖setPropertyValue方法:
public class KulItemPropertyDescriptor extends ItemPropertyDescriptor {
public KulItemPropertyDescriptor(AdapterFactory adapterFactory,
ResourceLocator resourceLocator,
String displayName,
String description,
EStructuralFeature feature,
boolean isSettable,
boolean multiLine,
boolean sortChoices,
Object staticImage,
String category,
String [] filterFlags) {
super(adapterFactory, resourceLocator, displayName, description, feature, isSettable, multiLine, sortChoices, staticImage, category, filterFlags);
}
@Override
public void setPropertyValue(Object object, Object value) {
Object validValue = null;
if(value instanceof PropertyValueWrapper) {
validValue = ((PropertyValueWrapper) value).getEditableValue(null);
} else {
validValue = value;
}
super.setPropertyValue(object, validValue);
}
}
要使用自己的ItemPropertyDescriptor,需要做的就是实现自己ItemProviderAdapter,让所有**PropertyAdapter继承它,参考代码:
public class CustomItemProviderAdapter extends ItemProviderAdapter {
public CustomItemProviderAdapter(AdapterFactory adapterFactory) {
super(adapterFactory);
}
protected ItemPropertyDescriptor createItemPropertyDescriptor(
AdapterFactory adapterFactory,
ResourceLocator resourceLocator,
String displayName,
String description,
EStructuralFeature feature,
boolean isSettable,
boolean multiLine,
boolean sortChoices,
Object staticImage,
String category,
String[] filterFlags) {
return new KulItemPropertyDescriptor(
adapterFactory,
resourceLocator,
displayName,
description,
feature,
isSettable,
multiLine,
sortChoices,
staticImage,
category,
filterFlags);
}
}
好,现在第2个问题解决了,第一个问题其实也好解决。应为EMF.EDIT生成的代码,默认每个属性的category是null,可以看一下**ProviderAdapter中的创建PropertyDescriptor的方法,要给属性赋予一个category于是乎实现自己的PropertySource在覆盖createPropertyDescriptor方法,返回自己的IPropertyDescriptor,在自己的IPropertyDescriptor中重写getCategory方法就ok了,参考代码:
private class KulPropertySource extends PropertySource {
private PropertyHolderType propertyHolder = null;
public KulPropertySource(Object object,
IItemPropertySource itemPropertySource) {
super(object, itemPropertySource);
propertyHolder = PropertyConfigLoader.getInstance()
.getPropertyHolder(((EObject) object).eClass().getName());
}
public IPropertyDescriptor[] getPropertyDescriptors() {
Collection<IPropertyDescriptor> result = new ArrayList<IPropertyDescriptor>();
List<IItemPropertyDescriptor> itemPropertyDescriptors = itemPropertySource
.getPropertyDescriptors(object);
if (itemPropertyDescriptors != null) {
for (IItemPropertyDescriptor itemPropertyDescriptor : itemPropertyDescriptors) {
result.add(createPropertyDescriptor(itemPropertyDescriptor));
}
}
return result.toArray(new IPropertyDescriptor[result.size()]);
}
protected IPropertyDescriptor createPropertyDescriptor(
IItemPropertyDescriptor itemPropertyDescriptor) {
return new PropertyDescriptor(object, itemPropertyDescriptor) {
public String getCategory() {
CategoryType ct = PropertyConfigLoader.getInstance()
.getCategory(propertyHolder,itemPropertyDescriptor.getDisplayName(object));
if(ct != null) {
return ct.getId();
}
return "Other";
}
};
}
}
这个KulPropertySource类是啥?怎么用?其实这个就是你自己的IPropertySourceProvider中getPropertySource要返回的PropertySource对象。那IPropertySourceProvider又是啥?这个就是UndoablePropertySheetEntry.setPropertySourceProvider()方法要传入的参数,UndoablePropertySheetEntry是啥?就是PropertySheetPage.setRootEntry()要传入的参数,PropertySheetPage是啥?你因该了解吧。