[Eclipse]GEF入门系列(九、增加易用性)
當(dāng)一個(gè)GEF應(yīng)用程序?qū)崿F(xiàn)了大部分必需的業(yè)務(wù)功能后,為了能讓用戶使用得更方便,我們應(yīng)該在易用性方面做些考慮。從3.0版本開(kāi)始, GEF增加了更多這方面的新特性,開(kāi)發(fā)人員很容易利用它們來(lái)改善自己的應(yīng)用程序界面。這篇帖子將介紹主要的幾個(gè)功能,它們有些在GEF 2.1中就出現(xiàn)了,但因?yàn)槎际顷P(guān)于易用性的而且以前沒(méi)有提到,所以放在這里一起來(lái)說(shuō)。( 下載示例代碼)
可折疊調(diào)色板
在以前的例子里,我們的編輯器都繼承自GraphicalEditorWithPalette。GEF 3.0提供了一個(gè)功能更加豐富的編輯器父類:GraphicalEditorWithFlyoutPalette,繼承它的編輯器具有一個(gè)可以折疊的工具條,并且能夠利用Eclipse自帶的調(diào)色板視圖,當(dāng)調(diào)色板視圖顯示時(shí),工具條會(huì)自動(dòng)轉(zhuǎn)移到這個(gè)視圖中。
圖1 可折疊和配置的調(diào)色板
與以前的GraphicalEditorWithPalette相比,繼承 GraphicalEditorWithFlyoutPalette的編輯器要多做一些工作。首先要實(shí)現(xiàn)getPalettePreferences() 方法,它返回一個(gè)FlyoutPreferences實(shí)例,作用是把調(diào)色板的幾個(gè)狀態(tài)信息(位置、大小和是否展開(kāi))保存起來(lái),這樣下次打開(kāi)編輯器的時(shí)候就可以自動(dòng)套用這些設(shè)置。下面使用偏好設(shè)置的方式保存和載入這些狀態(tài),你也可以使用其他方法,比如保存為.properties文件:
protected FlyoutPreferences getPalettePreferences() {return new FlyoutPreferences() {
public int getDockLocation() {
return SubjectEditorPlugin.getDefault().getPreferenceStore().getInt(IConstants.PREF_PALETTE_DOCK_LOCATION);
}
public void setDockLocation(int location) {
SubjectEditorPlugin.getDefault().getPreferenceStore().setValue(IConstants.PREF_PALETTE_DOCK_LOCATION,location);
}
…
};
}
然后要覆蓋缺省的createPaletteViewerProvider()實(shí)現(xiàn),在這里為調(diào)色板增加拖放支持,即指定調(diào)色板為拖放源(之所以用這樣的方式,原因是在編輯器里沒(méi)有辦法得到它對(duì)應(yīng)的調(diào)色板實(shí)例),在以前這個(gè)工作通常是在initializePaletteViewer ()方法里完成的,而現(xiàn)在這個(gè)方法已經(jīng)不需要了:
protected PaletteViewerProvider createPaletteViewerProvider() {return new PaletteViewerProvider(getEditDomain()) {
protected void configurePaletteViewer(PaletteViewer viewer) {
super.configurePaletteViewer(viewer);
viewer.addDragSourceListener(new TemplateTransferDragSourceListener(viewer));
}
};
}
GEF 3.0還允許用戶對(duì)調(diào)色板里的各種工具進(jìn)行定制,例如隱藏某個(gè)工具,或是修改工具的描述等等,這是通過(guò)給PaletteViewer定義一個(gè) PaletteCustomizer實(shí)例實(shí)現(xiàn)的,但由于時(shí)間關(guān)系,這里暫時(shí)不詳細(xì)介紹了,如果需要這項(xiàng)功能你可以參考Logic例子中的實(shí)現(xiàn)方法。
縮放
由于Draw2D中的圖形都具有天然的縮放功能,因此在GEF里實(shí)現(xiàn)縮放功能是很容易的,而且縮放的效果不錯(cuò)。GEF為我們提供了 ZoomInAction和ZoomOutAction以及對(duì)應(yīng)的RetargetAction(ZoomInRetargetAction和 ZoomOutRetargetAction),只要在編輯器里構(gòu)造它們的實(shí)例,然后在編輯器的ActionBarContributer類里將它們添加到想要的菜單或工具條位置即可。因?yàn)閆oomInAction和ZoomOutAction的構(gòu)造方法要求一個(gè)ZoomManager類型的參數(shù),而后者需要從GEF的RootEditPart中獲得(ScalableRootEditPart或 ScalableFreeformRootEditPart),所以最好在編輯器的 configureGraphicalViewer()里構(gòu)造這兩個(gè)Action比較方便,請(qǐng)看下面的代碼:
protected void configureGraphicalViewer() {super.configureGraphicalViewer();
ScalableFreeformRootEditPart root = new ScalableFreeformRootEditPart();
getGraphicalViewer().setRootEditPart(root);
getGraphicalViewer().setEditPartFactory(new PartFactory());
action = new ZoomInAction(root.getZoomManager());
getActionRegistry().registerAction(action);
getSite().getKeyBindingService().registerAction(action);
action = new ZoomOutAction(root.getZoomManager());
getActionRegistry().registerAction(action);
getSite().getKeyBindingService().registerAction(action);
}
假設(shè)我們想把這兩個(gè)命令添加到主工具條上,在DiagramActionBarContributor里應(yīng)該做兩件事:在 buildActions()里構(gòu)造對(duì)應(yīng)的RetargetAction,然后在contributeToToolBar()里添加它們到工具條(原理請(qǐng)參考前面關(guān)于菜單和工具條的 帖子):
protected void buildActions() {//其他命令
…
//縮放命令
addRetargetAction(new ZoomInRetargetAction());
addRetargetAction(new ZoomOutRetargetAction());
}
public void contributeToToolBar(IToolBarManager toolBarManager) {
//工具條中的其他按鈕
…
//縮放按鈕
toolBarManager.add(getAction(GEFActionConstants.ZOOM_IN));
toolBarManager.add(getAction(GEFActionConstants.ZOOM_OUT));
toolBarManager.add(new ZoomComboContributionItem(getPage()));
}
請(qǐng)注意,在contributeToToolBar()方法里我們額外添加了一個(gè)ZoomComboContributionItem 的實(shí)例,這個(gè)類也是GEF提供的,它的作用是顯示一個(gè)縮放百分比的下拉框,用戶可以選擇或輸入想要的數(shù)值。為了讓這個(gè)下拉框能與編輯器聯(lián)系在一起,我們要修改一下編輯器的getAdapter()方法,增加對(duì)它的支持:
public Object getAdapter(Class type) {…
if (type == ZoomManager.class)
return getGraphicalViewer().getProperty(ZoomManager.class.toString());
return super.getAdapter(type);
}
現(xiàn)在,打開(kāi)編輯器后主工具條中將出現(xiàn)下圖所示的兩個(gè)按鈕和一個(gè)下拉框:
圖2 縮放工具條
有時(shí)候我們想讓程序把用戶當(dāng)前的縮放值記錄下來(lái),以便下次打開(kāi)時(shí)顯示同樣的比例。這就須要在畫(huà)布模型里增加一個(gè)zoom變量,在編輯器的初始化過(guò)程中增加下面的語(yǔ)句,其中diagram是我們的畫(huà)布實(shí)例:
ZoomManager manager = (ZoomManager) getGraphicalViewer().getProperty(ZoomManager.class.toString());if (manager != null)
manager.setZoom(diagram.getZoom());
在保存模型前得到當(dāng)前的縮放比例放在畫(huà)布模型里一起保存:
ZoomManager manager = (ZoomManager) getGraphicalViewer().getProperty(ZoomManager.class.toString());if (manager != null)
diagram.setZoom(manager.getZoom());
輔助網(wǎng)格
你可能用過(guò)一些這樣的應(yīng)用程序,畫(huà)布里可以顯示一個(gè)灰色的網(wǎng)格幫助定位你的圖形元素,當(dāng)被拖動(dòng)的節(jié)點(diǎn)接近網(wǎng)格線條時(shí)會(huì)被"吸附"到網(wǎng)格上,這樣可以很容易的把畫(huà)布上的圖形元素排列整齊,GEF 3.0里就提供了顯示這種輔助網(wǎng)格的功能。
圖3 輔助編輯網(wǎng)格
是否顯示網(wǎng)格以及是否打開(kāi)吸附功能是由GraphicalViewer的兩個(gè)布爾類型的屬性(property)值決定的,它們分別是 SnapToGrid.PROPERTY_GRID_VISIBLE和SnapToGrid.PROPERTY_GRID_ENABLED,這些屬性是通過(guò)GriaphicalViewer.getProperty()和setProperty()方法來(lái)操作的。GEF為我們提供了一個(gè) ToggleGridAction用來(lái)同時(shí)切換它們的值(保持這兩個(gè)值同步確實(shí)符合一般使用習(xí)慣),但沒(méi)有像縮放功能那樣提供對(duì)應(yīng)的 RetargetAction,不知道GEF是出于什么考慮。另外因?yàn)檫@個(gè)Action沒(méi)有預(yù)先設(shè)置的圖標(biāo),所以把它直接添加到工具條上會(huì)很不好看,所以要么把它只放在菜單中,要么為它設(shè)置一個(gè)圖標(biāo),至于添加到菜單的方法這里不贅述了。
要想在保存模型時(shí)同時(shí)記錄當(dāng)前網(wǎng)格線是否顯示,必須在畫(huà)布模型里增加一個(gè)布爾類型變量,并在打開(kāi)模型和保存模型的方法中增加處理它的代碼。
幾何對(duì)齊
這個(gè)功能也是為了方便用戶排列圖形元素的,如果打開(kāi)了此功能,當(dāng)用戶拖動(dòng)的圖形有某個(gè)邊靠近另一圖形的某個(gè)平行邊延長(zhǎng)線時(shí),會(huì)自動(dòng)吸附到這條延長(zhǎng)線上;若兩個(gè)圖形的中心線(通過(guò)圖形中心點(diǎn)的水平或垂直線)平行靠近時(shí)也會(huì)產(chǎn)生吸附效果。例如下圖中,Subject1的左邊與 Subject2的右邊是吸附在一起的,Subject3原本是與Subject2水平中心線吸附的,而用戶在拖動(dòng)的過(guò)程中它的上邊吸附到 Subject1的底邊。
圖4 幾何對(duì)齊
幾何對(duì)齊也是通過(guò)GraphicalViewer的屬性來(lái)控制是否打開(kāi)的,屬性的名稱是 SnapToGeometry.PROPERTY_SNAP_ENABLED,值為布爾類型。在程序里增加吸附對(duì)齊切換的功能和前面說(shuō)的增加網(wǎng)格切換功能基本是一樣的,記住GEF為它提供的Action是ToggleSnapToGeometryAction。
要實(shí)現(xiàn)對(duì)齊功能,還有一個(gè)重要的步驟,那就是在畫(huà)布所對(duì)應(yīng)的EditPart的getAdapter()方法里增加對(duì) SnapToHelper類的回應(yīng),像下面這樣:
public?Object?getAdapter(Class?adapter)?{
????if?(adapter?==?SnapToHelper.class)?{
????????List?snapStrategies?=?new?ArrayList();
????????Boolean?val?=?(Boolean)getViewer().getProperty(RulerProvider.PROPERTY_RULER_VISIBILITY);
????????if?(val?!=?null?&&?val.booleanValue())
????????????snapStrategies.add(new?SnapToGuides(this));
????????val?=?(Boolean)getViewer().getProperty(SnapToGeometry.PROPERTY_SNAP_ENABLED);
????????if?(val?!=?null?&&?val.booleanValue())
????????????snapStrategies.add(new?SnapToGeometry(this));
????????val?=?(Boolean)getViewer().getProperty(SnapToGrid.PROPERTY_GRID_ENABLED);
????????if?(val?!=?null?&&?val.booleanValue())
????????????snapStrategies.add(new?SnapToGrid(this));
????????
????????if?(snapStrategies.size()?==?0)
????????????return?null;
????????if?(snapStrategies.size()?==?1)
????????????return?(SnapToHelper)snapStrategies.get(0);
????????SnapToHelper?ss[]?=?new?SnapToHelper[snapStrategies.size()];
????????for?(int?i?=?0;?i?<?snapStrategies.size();?i++)
????????????ss[i]?=?(SnapToHelper)snapStrategies.get(i);
????????return?new?CompoundSnapToHelper(ss);
????}
????return?super.getAdapter(adapter);
}
標(biāo)尺和輔助線
標(biāo)尺位于畫(huà)布的上部和左側(cè),在每個(gè)標(biāo)尺上可以建立很多與標(biāo)尺垂直的輔助線,這些顯示在畫(huà)布上的虛線具有吸附功能。
圖5 標(biāo)尺和輔助線
標(biāo)尺和輔助線的實(shí)現(xiàn)要稍微復(fù)雜一些。首先要修改原有的模型,新增加標(biāo)尺和輔助線這兩個(gè)類,它們之間的關(guān)系請(qǐng)看下圖:< /p>
圖6 增加標(biāo)尺和輔助線后的模型
與上篇帖子里的 模型圖比較后可以發(fā)現(xiàn),在Diagram類里增加了四個(gè)變量,其中除rulerVisibility以外三個(gè)的作用都在前面部分做過(guò)介紹,而rulerVisibility和它們類似,作用記錄標(biāo)尺的可見(jiàn)性,當(dāng)然只有在標(biāo)尺可見(jiàn)的時(shí)候輔助線才是可見(jiàn)的。我們新增了Ruler和 Guide兩個(gè)類,前者表示標(biāo)尺,后者表示輔助線。因?yàn)檩o助線是建立在標(biāo)尺上的,所以Ruler到Guide有一個(gè)包含關(guān)系(黑色菱形);畫(huà)布上有兩個(gè)標(biāo)尺,分別用topRuler和leftRuler這兩個(gè)變量引用,也是包含關(guān)系,也就是說(shuō),畫(huà)布上只能同時(shí)具有這兩個(gè)標(biāo)尺;Node到Guide有兩個(gè)引用,表示Node吸附到的兩條輔助線(為了簡(jiǎn)單起見(jiàn),在本文附的例子中并沒(méi)有實(shí)際使用到它們,Guide類中定義的幾個(gè)方法也沒(méi)有用到)。Guide類里的map變量用來(lái)記錄吸附在自己上的節(jié)點(diǎn)和對(duì)應(yīng)的吸附邊。要讓畫(huà)布上能夠顯示標(biāo)尺,首先要將原先的GraphicalViewer改放在一個(gè) RulerComposite實(shí)例上(而不是直接放在編輯器上),后者是GEF提供的專門(mén)用于顯示標(biāo)尺的組件,具體的改變方法如下:
//定義一個(gè)RulerComposite類型的變量private RulerComposite rulerComp;
//創(chuàng)建RulerComposite,并把GraphicalViewer創(chuàng)建在其上< span style="color: #008000;">
protected void createGraphicalViewer(Composite parent) {
rulerComp = new RulerComposite(parent, SWT.NONE);
super.createGraphicalViewer(rulerComp);
rulerComp.setGraphicalViewer((ScrollingGraphicalViewer) getGraphicalViewer());
}
//覆蓋getGraphicalControl返回RulerComposite實(shí)例< span style="color: #008000;">
protected Control getGraphicalControl() {
return rulerComp;
}
然后,要設(shè)置GraphicalViewer的幾個(gè)有關(guān)屬性,如下所示,其中前兩個(gè)分別表示左側(cè)和上方的標(biāo)尺,而最后一個(gè)表示標(biāo)尺的可見(jiàn)性:
getGraphicalViewer().setProperty(RulerProvider.PROPERTY_VERTICAL_RULER,new SubjectRulerProvider(diagram.getLeftRuler()));getGraphicalViewer().setProperty(RulerProvider.PROPERTY_HORIZONTAL_RULER,new SubjectRulerProvider(diagram.getTopRuler()));
getGraphicalViewer().setProperty(RulerProvider.PROPERTY_RULER_VISIBILITY,new Boolean(diagram.isRulerVisibility()));
在前兩個(gè)方法里用到了SubjectRulerProvider這個(gè)類,它是我們從RulerProvider類繼承過(guò)來(lái)的, RulerProvider是一個(gè)比較特殊的類,其作用有點(diǎn)像EditPolicy,不過(guò)除了一些getXXXCommand()方法以外,還有其他幾個(gè)方法要實(shí)現(xiàn)。需要返回Command的方法包括:getCreateGuideCommand()、getDeleteGuideCommand()和 getMoveGuideCommand(),分別返回創(chuàng)建輔助線、刪除輔助線和移動(dòng)輔助線的命令,下面列出創(chuàng)建輔助線的命令,其他兩個(gè)的實(shí)現(xiàn)方式是類似的,你可以在本文所附例子中找到它們的代碼:
public class CreateGuideCommand extends Command {private Guide guide;
private Ruler ruler;
private int position;
public CreateGuideCommand(Ruler parent, int position) {
setLabel("Create Guide");
this.ruler = parent;
this.position = position;
}
public void execute() {
guide = ModelFactory.eINSTANCE.createGuide();//創(chuàng)建一條新的輔助線
guide.setHorizontal(!ruler.isHorizontal());
guide.setPosition(position);
ruler.getGuides().add(guide);
}
public void undo() {
ruler.getGuides().remove(guide);
}
}
接下來(lái)再看看RulerProvider的其他方法,SubjectRulerProvider維護(hù)一個(gè)Ruler對(duì)象,在構(gòu)造方法里要把它的值傳入。此外,在構(gòu)造方法里還應(yīng)該給Ruler和Guide模型對(duì)象增加監(jiān)聽(tīng)器用來(lái)響應(yīng)標(biāo)尺和輔助線的變化,下面是Ruler監(jiān)聽(tīng)器的主要代碼(因?yàn)槭褂昧薊MF作為模型,所以監(jiān)聽(tīng)器實(shí)現(xiàn)為Adapter。如果你不用EMF,可以使用PropertyChangeListener實(shí)現(xiàn)):
public void notifyChanged(Notification notification) {switch (notification.getFeatureID(ModelPackage.class)) {
case ModelPackage.RULER__UNIT:
for (int i = 0; i < listeners.size(); i++)
((RulerChangeListener) listeners.get(i)).notifyUnitsChanged(ruler.getUnit());
break;
case ModelPackage.RULER__GUIDES:
Guide guide = (Guide) notification.getNewValue();
if (getGuides().contains(guide))
guide.eAdapters().add(guideAdapter);
else
guide.eAdapters().remove(guideAdapter);
for (int i = 0; i < listeners.size(); i++)
((RulerChangeListener) listeners.get(i)).notifyGuideReparented(guide);
break;
}
}
可以看到監(jiān)聽(tīng)器在被觸發(fā)時(shí)所做的工作實(shí)際上是觸發(fā)這個(gè)RulerProvider的監(jiān)聽(tīng)器列表(listeners)里的所有監(jiān)聽(tīng)器,而這些監(jiān)聽(tīng)器就是RulerEditPart或GuideEditPart,而我們不需要去關(guān)心這兩個(gè)類。Ruler的事件有兩種,一是單位(象素、厘米、英寸)改變,二是創(chuàng)建輔助線,在創(chuàng)建輔助線的情況要給這個(gè)輔助線增加監(jiān)聽(tīng)器。下面是Guide監(jiān)聽(tīng)器的主要代碼:
public void notifyChanged(Notification notification) {Guide guide = (Guide) notification.getNotifier();
switch (notification.getFeatureID(ModelPackage.class)) {
case ModelPackage.GUIDE__POSITION:
for (int i = 0; i < listeners.size(); i++)
((RulerChangeListener) listeners.get(i)).notifyGuideMoved(guide);
break;
case ModelPackage.GUIDE__MAP:
for (int i = 0; i < listeners.size(); i++)
((RulerChangeListener) listeners.get(i)).notifyPartAttachmentChanged(notification.getNewValue(),guide);
break;
}
}
Guide監(jiān)聽(tīng)器也有兩種事件,一是輔助線位置改變,二是輔助線上吸附的圖形的增減變化。請(qǐng)注意,這里的循環(huán)一定不要用 iterator的方式,而應(yīng)該用上面列出的下標(biāo)方式,否則會(huì)出現(xiàn)ConcurrentModificationException異常,原因和 RulerProvider的notifyXXX()實(shí)現(xiàn)有關(guān)。我們的SubjectRulerProvider構(gòu)造方法如下所示,它的主要工作就是增加監(jiān)聽(tīng)器:
public SubjectRulerProvider(Ruler ruler) {this.ruler = ruler;
ruler.eAdapters().add(rulerAdapter);
//載入模型的情況下,ruler可能已經(jīng)包含一些guides,所以要給它們?cè)黾颖O(jiān)聽(tīng)器< span style="color: #008000;">
for (Iterator iter = ruler.getGuides().iterator(); iter.hasNext();) {
Guide guide = (Guide) iter.next();
guide.eAdapters().add(guideAdapter);
}
}
在RulerProvider里還有幾個(gè)方法要實(shí)現(xiàn)才能正確使用標(biāo)尺:getRuler()返回RulerProvider維護(hù)的 Ruler實(shí)例,getGuides()返回輔助線列表,getGuidePosition(Object)返回某條輔助線在標(biāo)尺上的位置(以pixel 為單位),getPositions()返回標(biāo)尺上所有輔助線位置構(gòu)成的整數(shù)數(shù)組。以下是本例中的實(shí)現(xiàn)方式:
public Object getRuler() {return ruler;
}
public List getGuides() {
return ruler.getGuides();
}
public int[] getGuidePositions() {
List guides = getGuides();
int[] result = new int[guides.size()];
for (int i = 0; i < guides.size(); i++) {
result[i] = ((Guide) guides.get(i)).getPosition();
}
return result;
}
public int getGuidePosition(Object arg0) {
return ((Guide) arg0).getPosition();
}
有了這個(gè)自定義的RulerProvider類,再通過(guò)把該類的兩個(gè)實(shí)例被放在GraphicalViewer的兩個(gè)屬性(PROPERTY_VERTICAL_RULER和PROPERTY_HORIZONTAL_RULER)中,畫(huà)布就具有標(biāo)尺的功能了。GEF提供了用于切換標(biāo)尺可見(jiàn)性的命令:ToggleRulerVisibilityAction,我們使用和前面同樣的方法把它加到主菜單即可控制顯示或隱藏標(biāo)尺和輔助線。
位置和尺寸對(duì)齊
圖形編輯工具大多具有這樣的功能:選中兩個(gè)以上圖形,再按一下按鈕就可以讓它們以某一個(gè)邊或中心線對(duì)齊,或是調(diào)整它們?yōu)橥瑯拥膶挾雀叨?。GEF提供AlignmentAction和MatchSizeAction分別用來(lái)實(shí)現(xiàn)位置對(duì)齊和尺寸對(duì)齊,使用方法很簡(jiǎn)單,在編輯器的 createActions()方法里構(gòu)造需要的對(duì)齊方式Action(例如對(duì)齊到上邊、下邊等等),然后在編輯器的 ActionBarContributor里通過(guò)這些Action對(duì)應(yīng)的RetargetAction將它們添加到菜單或工具條即可。編輯器里的代碼如下,注意最后一句的作用是把它們加到selectionAction列表里以響應(yīng)選擇事件:
IAction action=new AlignmentAction((IWorkbenchPart)this,PositionConstants.LEFT);getActionRegistry().registerAction(action);
getSelectionActions().add(action.getId());
…
AlignmentAction的構(gòu)造方法的參數(shù)是編輯器本身和一個(gè)代表對(duì)齊方式的整數(shù),后者可以是 PositionConstants.LEFT、CENTER、RIGHT、TOP、MIDDLE、BOTTOM中的一個(gè); MatchSizeAction有兩個(gè)子類,MatchWidthAction和MatchHeightAction,你可以使用它們達(dá)到只調(diào)整寬度或高度的目的。下圖是添加在工具條中的按鈕,左邊六個(gè)為位置對(duì)齊,最后兩個(gè)為尺寸對(duì)齊,請(qǐng)注意,當(dāng)選擇多個(gè)圖形時(shí),被六個(gè)黑點(diǎn)包圍的那個(gè)稱為"主選擇",對(duì)齊時(shí)以該圖形所在位置和大小為準(zhǔn)做調(diào)整。
圖7 位置對(duì)齊和尺寸對(duì)齊
總結(jié)
以上是生活随笔為你收集整理的[Eclipse]GEF入门系列(九、增加易用性)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 愚人节的欢乐
- 下一篇: DebootstrapChroot