这章我将说一下如何去实现一个数据表在编辑器上显示,并且能够进行位置和尺寸的改变。我们将涉及到的内容有:Figure,EditPolicy,Command。示例代码下载
1.在Editor上实现一个简单的数据表
在上一章中,我们实现了一个空的Editor,画布上什么都没有,今天我会让它显示点东西。
我们已经说过了,Editor要显示一个图形,是根据我们给Viewer设置的模型,找到对应的EditPart,然后调用EditPart的createFigure得到图形,最后绘制在Editor上,大致如下:
我们需要做的事情就让我们的TableEditPart复写基类的createFigure方法,然后返回一个Figure图形,最后再在代码中更改我们最初设置的模型内容。
创建TableFigure
一般来说,当我们需要生成一个Figure图形的时候,最好的办法就是从Figure类继承一个新的类出来,然后复写Figure的paintFigure方法,绘制出我们想要的图案。见下代码:
public
class
TableFigure extends Figure {
protected
void
paintFigure(Graphics graphics) {
super.paintFigure(graphics);
//
得到Figure的Bounds
Rectangle bounds
=
getBounds();
//
在它周围绘制一个矩形,宽度和高度稍微小一点,以便能全部显示
graphics.drawRectangle(bounds.x,bounds.y,bounds.width
-
1
, bounds.height
-
1
);
}
}
TableFigure 显示的是一个矩形图形。getBounds方法是得到的是Figure的范围对象,这个对象是一个Recangle类型,包括有图形的坐标(Point),以及尺寸(Dimension),而这个范围对象并不是我们去指定的,而是根据TableFigure的父Figure来给它设定的。这里需要说一下,在GEF中,EditPart在绘制Figure的时候,步骤是先绘制好自己的Figure,然后查看自己的子EditPart,获得他们的Figure,确定他们的大小位置(根据默认或者是本身Figure的布局管理器来得到)绘制在自身Figure之上,也就是说,这是一个递归的过程。
修改TableEditPart代码
我们已经生成了一个TableFigure类,现在我们需要让TableEditPart的createFigure方法返回这个类的一个实例。
public
class
TableEditPart extends DBEditPartBase {
protected
IFigure createFigure() {
//
返回Table的Figure
return
new
TableFigure();
}
}
重新设置模型
我们创建Editor的时候,在初始化Viewer的方法中,生成了一个Schema模型给它,现在我们需要把我们的Table模型添加上去,也就是说,让新的Table模型作为Schema的子对象添加进去
protected
void
initializeGraphicalViewer() {
//
硬编码生成一个数据库模型
//
这个数据库中有一个表
Schema schema
=
new
Schema();
Table table
=
new
Table();
schema.addChild(table);
this
.getGraphicalViewer().setContents(schema);
}
可以看得出来,我们其实只是修改了我们的模型,但是图形显示是和EditPart相关的,SchemaEditPart是如何得知它会拥有一个子EditPart —— TableEditPart的呢?仔细看下DBBaseEditPart就可以很清楚了。
由于我们复写了EditPart的getModelChildren,返回的是DBBaseEditPart对应模型的子模型,所以EditPart就可以通过这些子模型来得到子编辑单元(Chilren EditPart)
protected
List getModelChildren() {
if
(getModel() instanceof DBBase){
return
((DBBase)getModel()).getChildren();
}
return
super.getModelChildren();
}
最后让我们运行一下,得到了一个绘制有矩形的Editor:
2.初步讨论EditPolicy
在刚才我们实现的编辑其中,已经将Table模型的图形显示了出来,但是仅仅只是有一个矩形画在画布上,当我们点击它的时候没有任何反应,好像完全只是一张静态的图片,我们要的效果的并不是这个,而是需要一个能够对其操作的图形。
在第一章里我简单地提到了在GEF中事件处理的过程,看看前面的文章可以知道,我们想要能够对我们显示的TableFigure图形进行编辑,需要的是一个能够处理相关Request的EditPolicy。
EditPolicy的类型有很多,大致分为图形相关和图形无关两大类,我在这个例子中使用到了这几个图形相关的EditPolicy:LayoutEditPolicy ,NonResizeableEditPolicy。
LayoutEditPolicy 一般是作为父EditPart所具有的EditPolicy,就是说,如果我们的Figure需要对它的子EditPart进行一些图形方面管理的话,使用这个EditPolicy比较合适。它能够处理一些容器类EditPart的应该具备的操作:增加一个EditPart,删除一个EditPart,移动子EditPart对应图形等。
并且它还能够为它的子EditPart设置对应的EditPolicy,这样一来就统一了子EditPart的一些行为。一会我讲具体说一下这个问题。
NonResizeableEditPolicy,顾名思义,它是一个不处理尺寸变化的EditPolicy,但是它能够处理EditPart对应Figure位置变化,由于我们的数据表的大小需要根据它所拥有列来决定,外界不应该对它的尺寸进行修改,所以我在这里选用了它。
怎么使用EditPolicy呢?EditPolicy是被"安装"到EditPart上的,在EditPart中,有一个接口方法:createEditPolicies,我们要在这里面进行安装。安装EditPolicy使用installEditPolicy方法。
回过头再看看我们所要用的这两个EditPolicy,他们两个都应该安装到哪个EditPart上呢。很显然,LayoutEditPolicy应该安装到父EditPart,也就是SchemaEditPart,这样一来,SchemaEditPart就能对TableEditPart进行管理了; NonResizeableEditPolicy就应该属于TableEditPart,我们需要用它来改变TableEditPart对应Figure 的位置。
当然,LayoutEditPolicy和NonResizeableEditPolicy不能实例化后直接安装到 EditPart上,因为我们还需要复写他们的一些方法,稍后我们会提到。我们先创建两个类,分别继承LayoutEditPolicy, NonResizeableEditPolicy:
public
class
SchemaLayoutEditPolicy extends LayoutEditPolicy {
protected
EditPolicy createChildEditPolicy(EditPart child) {
return
null
;
}
protected
Command getCreateCommand(CreateRequest request) {
return
null
;
}
protected
Command getDeleteDependantCommand(Request request) {
return
null
;
}
protected
Command getMoveChildrenCommand(Request request) {
return
null
;
}
}
public
class
TableNonResizableEditPolicy extends NonResizableEditPolicy {
}
很明显,这辆个类只是继承了基类,并没有复写或实现基类的方法。
姑且这样,我们先把SchemaLayoutEditPolicy安装到SchemaEditPart上:
protected
void
createEditPolicies() {
this
.installEditPolicy(EditPolicy.LAYOUT_ROLE,
new
SchemaLayoutEditPolicy());
}
installEditPolicy 的第一个参数其实并没有什么用,这个参数是一个String类型,随便写个字符串也不会影响到我们EditPolicy的安装以及它的工作的(也有人说,如果第一个参数重复的话,EditPolicy会被覆盖掉。我没有研究过,各位朋友可以试一下)。
好了,SchemaEditPart所需要的EditPolicy已经搞定,剩下TableEditPart了。大家可能会认为,安装它的EditPolicy也和 SchemaEditPart一样,复写createEditPolicies方法,然后installEditPolicy即可。是的,这样没有问题,但是由于我们的SchemaLayoutEditPolicy中有这么一个接口方法:createChildEditPolicy,这就是我在前面所说的,为了统一管理,给子EditPart安装所对应的EditPolicy。虽然这样做和直接安装的效果差不多(应该是一样,但是也有可能有一些差别),但是我认为还是把子EditPolicy的安装交给父EditPolicy吧:
protected
EditPolicy createChildEditPolicy(EditPart child) {
if
(child instanceof TableEditPart)
return
new
TableNonResizableEditPolicy();
return
new
NonResizableEditPolicy();
}
好了,让我们运行一下。呵呵,是不是可以点击我们的“数据表”了。但是这样还是不能移动我们的数据表。
3. 修改我们的类 ;Command 的使用
在第一章我已经讲过了,我们的模型有时需要添加一些和模型本身无关但和图形有关的属性。因为这样一来我们就能够记录我们的图形发生的位置变化,再通过模型的改变去通知EditPart刷新我们的图形。
我们先为Table增加一个属性:location
protected
Point location
=
new
Point(
0
,
0
);
/*
*
* @return 返回 location.
*/
public
Point getLocation() {
return
location;
}
/*
*
* @param location 设置 location
*/
public
void
setLocation(Point location) {
Point old
=
this
.location;
this
.location
=
location;
}
这个属性代表了图形目前所在的位置坐标。
有朋友要问:这里只是有了属性,那当属性改变的时候怎么去通知呢?我记得我也在第一章讲了,一般的做法是为我们的模型增加一个属性更改的事件发生源:PropertyChangeSupport
我们把事件发生源写到基类DBBase中,并增加几个方法去发送事件以及添加删除监听器:
public
static
final String PRO_FIGURE
=
"
__figure__property
"
;
private
PropertyChangeSupport support
=
new
PropertyChangeSupport(
this
);
public
void
addPropertyChangeListener(PropertyChangeListener l){
support.addPropertyChangeListener(l);
}
public
void
removePropertyChangeListener(PropertyChangeListener l){
support.removePropertyChangeListener(l);
}
public
void
fireFigurePropertyChange(Object old,Object now){
support.firePropertyChange(PRO_FIGURE,old,now);
}
好了,我们的事件源做好了,下面该想想让谁去监听了。
毫无疑问,我们的监听器应该是DBBaseEditPart,因为它才有能力去刷新Figure,所以我们需要更改DBBasEditPart代码,如下:
public
class
DBEditPartBase extends AbstractGraphicalEditPart implements PropertyChangeListener{
public
void
activate() {
if
(getModel()
!=
null
&&
getModel() instanceof DBBase){
((DBBase)getModel()).addPropertyChangeListener(
this
);
}
super.activate();
}
public
void
deactivate() {
if
(getModel()
!=
null
&&
getModel() instanceof DBBase){
((DBBase)getModel()).removePropertyChangeListener(
this
);
}
super.deactivate();
}
public
void
propertyChange(PropertyChangeEvent evt) {
String pName
=
evt.getPropertyName();
if
(pName.equals(DBBase.PRO_FIGURE)){
this
.refreshVisuals();
}
}
}
大家注意下propertyChange方法,当我们在获得事件类型为PRO_FIGURE后,就会直接去调用refreshVisuals去刷新我们的Figure。
但是refreshVisuals其实是空方法,它什么都没有做!
所以我们必须在TableEditPart中要复写它:
protected
void
refreshVisuals() {
super.refreshVisuals();
//
得到当前Figure的位置和大小
Rectangle rect
=
this
.getFigure().getBounds();
//
获得更改后的位置
Point p
=
((Table) getModel()).getLocation();
//
我们只更改Table的位置
((GraphicalEditPart)
this
.getParent()).setLayoutConstraint(
this
,
this
.getFigure(),
new
Rectangle(p, rect.getSize()));
}
最后一句代码是什么含义呢?这是让TableEditPart去找到它的父EditPart,也就是SchemaEditPart,再让它去“约束” TableEditPart图形的位置和大小,当然了,我们这里没有改变大小,只是通过Table模型的Location属性去更改它的位置而已。当调用了setLayoutConstraint方法后,我们的图形就会自动进行重绘。
接下来,让Table模型中更改location属性时将更改事件发送出来,以便EditPart能够截获并处理:
public void setLocation(Point location) {
Point old = this.location;
this.location = location;
this.fireFigurePropertyChange(old,this.location);
}
我们已经做了很多调整,改了不少代码了,这会运行看看吧!
对不起,我们的TableFigure还是不会移动!
这是由于我们忘记了在TableNonResizeableEditPolicy中做文章。
刚才已经说过了,TableNonResizeableEditPolicy能够处理对图形移动的,但是它只是通知我们图形移动了,要找我们索取一个 Command去执行这种变化。所以我们还需要写一个Command类,让这个Command去执行对模型位置的更改(关于Command的介绍请回过头看
第一章):
public class TableMoveCommand extends Command {
private ChangeBoundsRequest request;
private Table model;
public void execute() {
Point old = getModel().getLocation();
int x = request.getMoveDelta().x;
int y = request.getMoveDelta().y;
getModel().setLocation(new Point(old.x+x,old.y+y));
}
}
然后我们在TableNonResizeableEditPolicy中复写getMoveCommand方法:
protected Command getMoveCommand(ChangeBoundsRequest request) {
TableMoveCommand command = new TableMoveCommand();
command.setModel((Table)getHost().getModel());
command.setRequest(request);
return command;
}
好了!这会再运行看看,是不是能移动了?
4.结束语我们今天把上次例子代码进行了一些修改,得到了一个能够随意改变位置的矩形,其中主要简单地讲述了一些EditPolicy如何使用。
本人文笔比较烂,说事情总说不清,如果有不清楚的地方请留言,我会进行修改,尽量让大家都能看懂。
以后的章节,我们会继续讨论EditPolicy以及Figure布局等问题。