先简单回顾一下Request在GEF里的作用。Request是GEF里一个比较重要的角色,Tool将原始的鼠标事件转换为EditPart可以识别的请求,Request则承载了这些请求信息。举例来说,用户在调色板(Palette)里选择了创建节点工具(CreationTool),然后在画布区域按下鼠标左键,这时产生在画布上的鼠标单击事件将被CreationTool转换为一个CreateRequest,它里面包含了要创建的对象,坐标位置等信息。 EditPart上如果安装了能够处理CreateRequest的EditPolicy,则相应的EditPolicy会根据这个 CreateRequest创建一个Command,由后者实际执行创建新对象的必要操作。
GEF已经为我们提供了很多种类的Request,其中最常用的是CreateRequest及其子类 CreateConnectionRequest,其他比较常见的还有SelectionRequest,ChangeBoundsRequest和 ReconnectRequest等等。要实现一个典型的图形化应用程序,例如UML类图编辑器,这些预定义的Request基本够用了。然而各种稀奇古怪的需求我相信大家也见过不少,很多需求不太符合约定俗成的使用习惯,因此实现起来更多依赖开发人员的编码,而不是开发框架带来的便利。在这种时候,我们唯一的期望就是开发框架提供足够的扩展机制,以便让我们额外编写的代码能和其他代码和平共处,幸好GEF是具有足够的扩展性的。
再回到Request的问题上,为了说明什么情况下需要自定义Request,我在前文“应用实例”里的示例应用基础上假设一个新的需求:
在Palette里增加三个工具,作用分别是把选中节点的背景颜色改变为红色、绿色和蓝色。
假如你用过Photoshop或类似软件,这个需求很像给节点上色的“油漆桶”或“上色工具”,当然在用户界面的背后,实际应用里这些颜色可能代表一个节点的重要程度,优先级或是异常信息等等。现在,让我们通过创建一个自定义的Request来实现这个需求,还是以前文中的示例项目为基础。
一、首先,原来的模型里节点(Node)类里没有反映颜色的成员变量,所以先要在Node类里添加一个color属性,以及相应的 getter/setter方法,注意这个setter方法里要和其他成员变量的setter方法一样传递模型改变的消息。仿照其他成员变量,还应该有一个静态字符串变量,用来区分消息对应哪个属性。
final public static String PROP_COLOR = "COLOR";
protected RGB color = new RGB(255, 255, 255);
public RGB getColor() {
return color;
}
public void setColor(RGB color) {
if (this.color.equals(color)) {
return;
}
this.color = color;
firePropertyChange(PROP_COLOR, null, color);
}
二、然后,要让Node的color属性变化能够反映到图形上,因此要修改NodePart里的propertyChanged()和 refreshVisuals()方法,在前者里增加对color属性的响应,在后者里将NodeFigure的背景颜色设置为Node的color属性对应的颜色。(注意,Color对象是系统资源对象,实际使用里需要缓存以避免系统资源耗尽,为节约篇幅起见,示例代码直接new Color()了)
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(Node.PROP_COLOR))//Response to color change
refreshVisuals();
}
protected void refreshVisuals() {
((NodeFigure) this.getFigure()).setBackgroundColor(new Color(null, node.getColor()));//TODO cache color instances
}
三、现在来创建我们自己的Request,因为目的是改变颜色,所以不妨叫做ChangeColorRequest
。它应当继承自org.eclipse.gef.Request,我们需要ChangeColorRequest上带有两样信息:1.需要改变颜色的节点;2.目标颜色。因此它应该有这两个成员变量。
import org.eclipse.gef.Request;
import org.eclipse.swt.graphics.RGB;
import com.example.model.Node;
public class ChangeColorRequest extends Request{
final static public String REQ_CHANGE_COLOR="REQ_CHANGE_COLOR";
private Node node;
private RGB color;
public ChangeColorRequest(Node node, RGB color) {
super();
this.color = color;
this.node = node;
setType(REQ_CHANGE_COLOR);
}
public RGB getColor() {
return color;
}
public Node getNode() {
return node;
}
public void setNode(Node node) {
this.node = node;
}
public void setColor(RGB color) {
this.color = color;
}
}
ChangeColorRequest看起来和一个JavaBean差不多,的确如此,因为Request的作用就是传递翻译后的鼠标事件。如果你看一下org.eclipse.gef.Request的代码,你会发现Request还有一个type属性,这个属性一般是一个字符串(在gef的RequestConstants里预定义了一些,如RequestConstants.REQ_SELECTION_HOVER),EditPolicy可以根据它决定是否处理这个Request。在我们的例子里,顺便定义了这样一个常量字符串REQ_CHANGE_COLOR
,在后面的 ChangeColorEditPolicy里会用到它。
四、现在有一个问题,这个Request的实例应该在哪里生成?答案是在Tool里,用户在画布区域按下鼠标左键时,当前 Palette里被选中的Tool负责创建一个Request。我们现在面对的这个需求需要我们创建一种新的Tool:ChangeColorTool。我们让ChangeColorTool继承org.eclipse.gef.tools.SelectionTool,因为“上色工具”的用法和“选择工具”基本上差不多。显然,我们需要覆盖的是handleButtonDown()
方法,用来告诉gef如果用户当前选择了这个工具,在画布区域按下鼠标会发生什么事情。代码如下:
import org.eclipse.gef.EditPart;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.tools.SelectionTool;
import org.eclipse.swt.graphics.RGB;
import com.example.model.Node;
import com.example.parts.NodePart;
public class ChangeColorTool extends SelectionTool {
private RGB color;
public ChangeColorTool(RGB color) {
super();
this.color = color;
}
/**
* If target editpart is an {@link NodePart}, create a {@link ChangeColorRequest} instance,
* get command from target editpart with this request and execute.
*/
@Override
protected boolean handleButtonDown(int button) {
//Get selected editpart
EditPart editPart = this.getTargetEditPart();
if (editPart instanceof NodePart) {
NodePart nodePart = (NodePart) editPart;
Node node = (Node) nodePart.getModel();
//Create an instance of ChangeColorRequest
ChangeColorRequest request = new ChangeColorRequest(node, color);
//Get command from the editpart
Command command = editPart.getCommand(request);
//Execute the command
this.getDomain().getCommandStack().execute(command);
return true;
}
return false;
}
}
五、有了Tool,还需要用ToolEntry把它包装起来添加到Palette里。所以我们创建一个名为 ChangeColorToolEntry并继承org.eclipse.gef.palette.ToolEntry的类,覆盖createTool()
方法,让它返回我们的ChangeColorTool实例。这个ChangeColorToolEntry代码应该很容易理解:
import org.eclipse.gef.SharedCursors;
import org.eclipse.gef.Tool;
import org.eclipse.gef.palette.ToolEntry;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.RGB;
public class ChangeColorToolEntry extends ToolEntry {
private RGB color;
public ChangeColorToolEntry(RGB color, String label, String shortDesc, ImageDescriptor iconSmall,
ImageDescriptor iconLarge) {
super(label, shortDesc, iconSmall, iconLarge);
this.color = color;
}
@Override
public Tool createTool() {
ChangeColorTool tool = new ChangeColorTool(color);
tool.setUnloadWhenFinished(false);//Switch to selection tool after performed?
tool.setDefaultCursor(SharedCursors.CROSS);//Any cursor you like
return tool;
}
}
六、要把三个这样的ToolEntry添加到Palette里,当然是通过修改原来的PaletteFactory类。为节约篇幅,这里就不帖它的代码了,可以下载并参考示例代码PaletteFactory.java里的createCategories()
和createColorDrawer()
方法。
到目前为止,ChangeColorRequest已经可以发出了,接下来要解决的问题是如何让EditPart处理这个请求。
七、我们知道,GEF里任何对模型的修改都是通过command完成的,因此定义一个ChangeColorCommand
肯定是需要的。它的execute()
方法和undo()
方法如下所示:
public class ChangeColorCommand extends Command{
private RGB oldColor;
@Override
public void execute() {
oldColor = node.getColor();
node.setColor(color);
}
@Override
public void undo() {
node.setColor(oldColor);
}
}
八、EditPolicy负责接收所有的Request,所以还要创建一个ChangeColorEditPolicy
。在下面列出的代码里,你会看到我们定义了一个新的Role
字符串,过一会儿我们在EditPart上安装这个EditPolicy的时候要以这个字符串作为Key,以避免覆盖EditPart上已有的其他EditPolicy。
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editpolicies.AbstractEditPolicy;
import org.eclipse.swt.graphics.RGB;
import com.example.model.Node;
public class ChangeColorEditPolicy extends AbstractEditPolicy {
final static public String CHANGE_COLOR_ROLE = "CHANGE_COLOR_ROLE";
@Override
public Command getCommand(Request request) {
//Judge whether this request is intersting by its type
if (request.getType() == ChangeColorRequest.REQ_CHANGE_COLOR) {
ChangeColorRequest theRequest = (ChangeColorRequest) request;
//Get information from request
Node node = theRequest.getNode();
RGB color = theRequest.getColor();
//Create corresponding command and return it
ChangeColorCommand command = new ChangeColorCommand(node, color);
return command;
}
return null;
}
}
九、最后还是回到EditPart,前面在第二个步骤里我们曾经修改过的NodePart里还有最后一处需要添加,那就是在installEditPolicies()
方法里添加刚刚创建的ChangeColorEditPolicy:
protected void createEditPolicies() {
//Add change color editpolicy
installEditPolicy(ChangeColorEditPolicy.CHANGE_COLOR_ROLE, new ChangeColorEditPolicy());
}
现在我们已经完成了所有必要的修改,来看一下运行结果。
总结一下,需要创建的类有:ChangeColorRequest, ChangeColorTool, ChangeColorToolEntry, ChangeColorCommand, ChangeColorEditPolicy;需要修改的类有:Node, NodePart, PaletteFactory。在实例项目里,为了方便大家浏览,所有新创建的类都放在com.example.request包里,实际项目里还是建议分别放在对应的包里。
下载示例代码(在eclipse3.2.1和gef3.2下编译通过)
搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/06/21/792446.html
请保留原始链接:https://bjzhanghao.com/p/2711