先簡單回顧一下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包裡,實際項目裡還是建議分別放在對應的包裡。
本文配套源碼