ArcObjects SDK開發 007 自定義App-Command-Tool框架
為什麼我們要自己再設計一套App-Command框架,而不直接使用AO API中的AxControl-ICommand這套已經非常好的框架呢?
1、宿主不同。我們系統的宿主物件除了可能要包含MapControl等地圖顯示控制元件外,還可能會包含我們業務系統特有的資訊。例如當前登入使用者,在一些Command中,可能需要根據當前登入使用者的覺得來判斷功能是否可用等。
2、AO中的ICmmand和ITool已經和UI繫結到一起了,我們並不想直接用AO中定義的ToolBar,這樣會和我們的系統風格不一致。還有ICommand中定義的Bitmap以及ITool中定義的Cursor都是int型別,這並不符合我們的使用習慣。如果我們使用傳統選單+工具條的模式,使用的都是16*16的圖示,如果我們採用Office的Ribbon風格,那麼可能會出現很多32*32的圖示,這個如何相容?
3、我們想讓我們定義的工具適應更多的UI。例如定義的Command和Tool和繫結到WPF自帶的按鈕上,也可以繫結到第三方庫例如DEV定義的按鈕上。這就需要多UI進行抽象。
2、基於ArcObjects SDK設計的App-Command-Tool框架
我們參考借鑑AO,定義我們自己的App-Command框架如下,定義的時候,主要是解決上述的幾個問題。我們定義的框架如下。
IApplication、ICommand、IMapTool以及ICmmandUI四個介面以及MapApplication類是整個框架的核心部分。除了上圖中體現出來的內容外,框架還包含Command、MapTool以及 ViewSynchronizer等基類和輔助類。
我們在MapApplication類中封裝了宿主與ITool介面的互動。封裝程式碼如下。
this.AxMapControl.OnMouseDown += (x, y) => { this._CrruteTool.OnMouseDown(y.button, y.shift, y.x, y.y); }; this.AxMapControl.OnMouseMove += (x, y) => { this._CrruteTool.OnMouseMove(y.button, y.shift, y.x, y.y); }; this.AxMapControl.OnMouseUp += (x, y) => { this._CrruteTool.OnMouseUp(y.button, y.shift, y.x, y.y); }; this.AxMapControl.OnDoubleClick += (x, y) => { this._CrruteTool.OnDblClick(); }; this.AxMapControl.OnKeyDown += (x, y) => { this._CrruteTool.OnKeyDown(y.keyCode, y.shift); }; this.AxMapControl.OnKeyUp += (x, y) => { this._CrruteTool.OnKeyUp(y.keyCode, y.shift); };
對於宿主來說,並不關心當前使用的到底是哪個Tool,只管在觸發動作的時候,去呼叫當前工具對應的函式即可。
public IMapTool CrruteTool { get { return this._CrruteTool; } set { this._CrruteTool.OnDeActivate(); this._CrruteTool.IsChecked = false; this._CrruteTool = value; if (this._CrruteTool == null) { this._CrruteTool = new NullMapTool(this); } this._CrruteTool.OnActive(); this._CrruteTool.IsChecked = true; } }
切換工具的時候,首先要呼叫工具的OnDeActivate函式,把當前工具的使用痕跡清理掉。設定新工具,呼叫新工具的OnActive函式,啟用該工具。
ArcObjects SDK本身為我們提供了很多已經實現好的工具和命令,如何把這些命令融合到我們自己的框架中呢?
public class MapFullExtentCommand : MapCommand { private ESRI.ArcGIS.SystemUI.ICommand _EsriCommand = null; public MapFullExtentCommand(MapApplication pMapApplication) : base(pMapApplication) { this._EsriCommand = new ControlsMapFullExtentCommandClass(); this._EsriCommand.OnCreate(pMapApplication.MapControl); this.SetIcon(CommandIconSize.IconSize16, "MapTools/Res/MapFullExtent16.png"); } public override void OnClick() { base.OnClick(); this._EsriCommand.OnClick(); } }
我們初始化了一個ArcObjects SDK定義的ControlsMapFullExtentCommandClass類,並與我們定義的MapApplication中的MapControl繫結。實現命令點選函式的時候,直接呼叫AO中定義的全圖類的OnClick函式即可。
public class MapZoomInTool : MapTool { private readonly ESRI.ArcGIS.SystemUI.ITool _EsriTool = null; public MapZoomInTool(MapApplication pMapApplication) : base(pMapApplication) { this._EsriTool = new ControlsMapZoomInToolClass(); this.SetIcon(CommandIconSize.IconSize16, "MapTools/Res/MapZoomIn16.png"); } public override void OnActive() { base.OnActive(); (this._EsriTool as ESRI.ArcGIS.SystemUI.ICommand).OnCreate(this.MapApplication.ActiveControl); this.MapApplication.ActiveControl.CurrentTool = this._EsriTool; } public override void OnDeActivate() { base.OnDeActivate(); this.MapApplication.ActiveControl.CurrentTool = null; } public override void OnMouseDown(int button, int shift, int x, int y) { if (button == 4) { this.MapApplication.AxControlPan(); } base.OnMouseDown(button, shift, x, y); } }
我們初始化了一個ArcObjects SDK定義的ControlsMapZoomInToolClass類,在啟用該工具的時候和當前啟用的Control繫結,如果是資料模式,會繫結MapControl,如果是佈局模式,會繫結PageLayoutControl。並把定義的工具賦值給當前啟用的Control的CurrentTool屬性。失活的時候,把當前啟用Control的CurrentTool設定為null。
這樣我們就可以充分利用ArcObjects SDK已經實現的各類命令和工具了。
如果我們想在已有工具的基礎上做些其他事情呢?例如在出圖的時候,選擇一個元素,在右側顯示該元素的屬性面板。正常思路下,我們會點選PageLayoutControl,根據座標去判斷是否選中的Element,如果選中了,則把Element顯示為選中狀態,並獲取該物件,在右側顯示其屬性面板。
那是不是有更簡單的方法?ArcObjects SDK是有選擇Element工具的,類名稱為ControlsSelectToolClass,使用該工具可以使用滑鼠進行點選、框選、刪除、移動以及調整元素大小等操作,這些功能如何我們自己去寫程式碼實現,將有非常大的工作量。如果能用這個工具,那就再好不過了。但我們需要解決兩個問題。
1、ArcObjects SDK中定義的選擇類,在選擇元素後,我們要捕捉到該動作,並獲取選中的元素,顯示元素的面板;
2、ArcObjects SDK中定義的選擇類,選擇元素後,按下Delete鍵,會刪除元素,這個邏輯我們需要控制,禁止刪除MapFarme,並且刪除其他元素的時候,要彈出提示是否確定刪除對話方塊,確定後,再刪除。
public class SelectTool : MapTool { private readonly LayoutDesignApplication _LayoutDesignAplication = null; private readonly ControlsSelectToolClass _EsriTool = null; private double _MouseDownPageX = 0; private double _MouseDownPageY = 0; public SelectTool(LayoutDesignApplication pLayoutDesignAplication) : base(pLayoutDesignAplication) { this._LayoutDesignAplication = pLayoutDesignAplication; this._EsriTool = new ControlsSelectToolClass(); this.SetIcon(CommandIconSize.IconSize16, "Designs/Res/Select16.png"); } public override void OnActive() { base.OnActive(); this._EsriTool.OnCreate(this._LayoutDesignAplication.ActiveControl); } public override void OnKeyDown(int keyCode, int shift) { if (keyCode == (int)ConsoleKey.Delete) { IGraphicsContainerSelect myGraphicsContainerSelect = this._LayoutDesignAplication.PageLayoutControl.GraphicsContainer as IGraphicsContainerSelect; IElement mySelectElement = myGraphicsContainerSelect.DominantElement; if (mySelectElement is IMapFrame == true) { return; } MessageBoxResult myMessageBoxResult = MessageBox.Show("Is it determined to remove?", "Info", MessageBoxButton.YesNo); if (myMessageBoxResult != MessageBoxResult.Yes) { return; } base.OnKeyDown(keyCode, shift); this._EsriTool.OnKeyDown(keyCode, shift); } else { base.OnKeyDown(keyCode, shift); } } public override void OnMouseDown(int button, int shift, int x, int y) { if (button == 4) { this._LayoutDesignAplication.AxControlPan(); } else { this._EsriTool.OnMouseDown(button, shift, x, y); } } public override void OnMouseUp(int button, int shift, int x, int y) { base.OnMouseUp(button, shift, x, y); this._EsriTool.OnMouseUp(button, shift, x, y); IPageLayoutControl myPageLayoutControl = this._LayoutDesignAplication.PageLayoutControl; IPageLayout myPageLayout = myPageLayoutControl.PageLayout; IGraphicsContainerSelect myGraphicsContainerSelect = myPageLayout as IGraphicsContainerSelect; IElement myDominantElement = myGraphicsContainerSelect.DominantElement; } public override void OnMouseMove(int button, int shift, int x, int y) { base.OnMouseMove(button, shift, x, y); this._EsriTool.OnMouseMove(button, shift, x, y); } }
完全自定義的Command就比較簡單些了。例如我們定義移除當前選中的圖層命令,定義如下。
public class LayerRemoveCommand : MapCommand { public LayerRemoveCommand(MapApplication pMapApplication) : base(pMapApplication) { this.Caption = "Remove"; this.IsEnabled = false; this.MapApplication.OnActiveStateChanged += (x, y) => { this.UpdateIsEnableState(); }; this.MapApplication.OnSelectTocObjectChanged += (x, y) => { this.UpdateIsEnableState(); }; } public override void OnClick() { base.OnClick(); ILayer myLayer = this.MapApplication.SelectTocObject as ILayer; if (myLayer == null) { MessageBox.Show("Please Select A Layer。"); return; } if (MessageBox.Show("Are You Sure Remove The Layer?", "Info", MessageBoxButton.YesNo) == MessageBoxResult.Yes) { this.MapApplication.MapControl.ActiveView.FocusMap.DeleteLayer(myLayer); this.MapApplication.TOCControl.Update(); } } private void UpdateIsEnableState() { if (this.MapApplication.ActivePattern == MapActivePattern.None) { this.IsEnabled = false; return; } ILayer myLayer = this.MapApplication.SelectTocObject as ILayer; this.IsEnabled = (myLayer != null); } }
該定義就可以新增到圖層的右鍵選單上,用來移除當前選中的圖層。我們不光可以通過呼叫宿主的屬性和事件來控制自己是否可用,還可以加入很多邏輯判斷。例如如果沒有選擇任何圖層,則提示使用者請選擇一個圖層。在移除的時候,可以提示使用者是否確定移除等。
自定義的工具如下所示。
public class PointTextTool : MapTool { private readonly LayoutDesignApplication _LayoutDesignApplication = null; public PointTextTool(LayoutDesignApplication pLayoutDesignApplication) : base(pLayoutDesignApplication) { this._LayoutDesignApplication = pLayoutDesignApplication; this.SetIcon(CommandIconSize.IconSize16, "Designs/Res/Text16.png"); } public override void OnMouseDown(int button, int shift, int x, int y) { base.OnMouseDown(button, shift, x, y); IPoint myPagePoint = this._LayoutDesignApplication.PageLayoutControl.ToPagePoint(x, y); IPageLayout myPageLayout = this._LayoutDesignApplication.PageLayoutControl.PageLayout; IGraphicsContainerSelect myGraphicsContainerSelect = myPageLayout as IGraphicsContainerSelect; myGraphicsContainerSelect.UnselectAllElements(); PointTextItem myPointTextItem = new PointTextItem(); myPointTextItem.X = myPagePoint.X; myPointTextItem.Y = myPagePoint.Y; this._LayoutDesignApplication.LayoutDesign.PageLayoutItemList.Add(myPointTextItem); myPointTextItem.Apply(this._LayoutDesignApplication); (myPageLayout as IActiveView).PartialRefresh(esriViewDrawPhase.esriViewGraphics, null, null); this._LayoutDesignApplication.CrruteTool = this._LayoutDesignApplication.SelectTool; } }
實現工具的OnMouseDown函式,獲取當前點選位置的座標,例項化一個文字元素,新增到該位置。然後馬上把工具切換到系統定義好的SelectTool上,這樣會避免不小心點選兩次,添加了兩個文字元素,提高使用者體驗。
切換到SelectTool後,再次點選剛新增文字元素就可以選中該元素,這樣右側該元素的資訊面板就展示出來了,完成了一個非常自然的操作過程。
通過命令和工具通過自己控制自己的狀態、行為等,可以做到很細微的邏輯控制,並且這些操作會很好的封裝在自己的程式碼中。這樣系統功能可以通過實現各類Command和Tool不斷擴充套件系統功能,但又不會影響系統的整體結構。