如果要使用ArcEngine开发GIS系统的话,首先我们用到的就是MapControl和TocControl以及ToolBarControl。用这三个UI我们就能做出一个简单的GIS系统。MapControl是显示数据用的,TocControl是显示地图数据树用的,ToolBarControl就是放置命令和工具的载体。TooBarControl是一个命令和工具的载体UI,上面可以放很多命令和工具,我们可以猜想ToolBarControl可能能够接受一个接口或者基类,这样我们就可以往这个工具条上加很多继承该接口或基类的命令和工具。
这个接口就是ICommand,该接口在ArcEngine中是一个特别重要的接口,是Engine体系中组织功能的接口。例如我们在工具条上使用的打开地图、加载数据、放大、缩小等都是继承至该接口,同样我们在扩展Engine的功能时要让这样功能体现的界面上,入口我们一般也是继承ICommand接口,然后这些接口就可以和Engine已经提供的命令一起使用了。从该接口的命名上我们也可以看出,该接口是一个命令接口。该接口直接执行某个个动作,而不和地图发生交互。想Engine中ControlsMapZoomInFixedCommand、ControlsMapZoomOutFixedCommand工具都是直接继承ICommand的,可以理解成点击这些按钮执行命令后,地图就进行放大或缩小,而不需要和地图交互。还有一点就是命令时无状态的,例如我们在使用ArcMap时,当你点一下菜单栏上的按钮,立即响应该功能,而该按钮还是正常状态,没有被按下去,一般该按钮就是一个命令,但也有特殊情况,有可能按钮按下去预示着是某种状态。
Engine中ICommand的定义如下:
using System; using System.Runtime.InteropServices; namespace ESRI.ArcGIS.SystemUI { [TypeLibType(256)] [Guid("36B06538-4437-11D1-B970-080009EE4E51")] [InterfaceType(1)] public interface ICommand { [DispId(1610678280)] [ComAliasName("ESRI.ArcGIS.esriSystem.OLE_HANDLE")] int Bitmap { get; } [DispId(1610678275)] string Caption { get; } [DispId(1610678281)] string Category { get; } [DispId(1610678273)] bool Checked { get; } [DispId(1610678272)] bool Enabled { get; } [DispId(1610678279)] int HelpContextID { get; } [DispId(1610678278)] string HelpFile { get; } [DispId(1610678277)] string Message { get; } [DispId(1610678274)] string Name { get; } [DispId(1610678276)] string Tooltip { get; } void OnClick(); void OnCreate(object hook); } }
该接口定义了命令的标题、图标、提示信息等属性,并定义了初始化、点击响应的函数。基本上和我们在ArcMap上看到的信一致。
那需要和地图交互的功能如何定义呢?ITool接口。Engine中提供了ITool接口用来提供和地图交互的一些功能。
Engine中ITool接口的定义如下:
namespace ESRI.ArcGIS.SystemUI { [Guid("2A6B0172-4ED2-11D0-98BE-00805F7CED21")] [InterfaceType(1)] [TypeLibType(256)] public interface ITool { [ComAliasName("ESRI.ArcGIS.esriSystem.OLE_HANDLE")] [DispId(1610678272)] int Cursor { get; } bool Deactivate(); bool OnContextMenu(int x, int y); void OnDblClick(); void OnKeyDown(int keyCode, int shift); void OnKeyUp(int keyCode, int shift); void OnMouseDown(int button, int shift, int x, int y); void OnMouseMove(int button, int shift, int x, int y); void OnMouseUp(int button, int shift, int x, int y); void Refresh(int hdc); } }
因为工具是要和地图交互的,和地图交互主要是靠鼠标和键盘,所以ITool接口中包含了一些和鼠标键盘交互的函数。例如鼠标按下、鼠标移动、鼠标弹起,键盘的按下和弹起等。既然ITool也是和ICommand的并列的可以在界面显示的一个按钮,那为什么ITool接口没有标题、图标,提示信息以及状态这些属性呢?我不知道Engine的设计人员是怎么考虑的,我个人认为在Engine里面ITool接口是应该继承ICommand接口比较合适。
不过虽然ITool接口没有继承ICommand接口,但我们一般很少直接去继承者两个接口的,Engine里面为我们提供了BaseCommand和BaseTool两个基类,BaseCommand是继承ICommand接口的基类,这个是很明显的,而BaseTool即继承了ITool接口还继承了ICommand接口。这也验证了我们上面说的工具也是一个命令。
四个接口和类的继承关系如下:
命令是无状态的,而工具是有状态的,一个主控件(MapControl、PagelayoutControl、GlobeControl、SceneControl)都有CurrentTool属性,也就是说整个系统当前的工具只有一个。这个我们很容易理解,因为电脑一般情况下只有一个鼠标和一个键盘,要响应鼠标和键盘的事件,只能是一个工具。如果想切换工具时,直接给这些控件的CurrentTool属性赋值就可以了。
当前工具属性定义如下:
namespace ESRI.ArcGIS.Controls { [Guid("D00F1736-7A95-4F5E-B54F-E2863C425DC3")] [TypeLibType(256)] [InterfaceType(1)] public interface IToolbarBuddy { [DispId(1610678272)] ITool CurrentTool { get; set; } } }
虽然该属性定义到了IToolbarBuddy接口上了,但MapControl、PagelayoutControl、GlobeControl、SceneControl这四个控件对象都是继承该接口的。
在给此参数赋值后,系统会自动调用上个工具的Deactive函数,把上个工具的Checked属性设置为False,调用当前新赋的工具的OnCreate函数,并把Checked属性设置为True,这样两个工具就自动的切换了。上个工具会为Deactive中做一些清理工作,例如没有画完的线,定位留下的高亮数据等。按钮的选中状态也做了相应的变化。新赋值的工具在OnCreate函数中做一些初始化操作。
此时当鼠标再在地球上做一下点击移动操作时,系统响应的就是新赋的工具的鼠标点击、移动函数。
命令就更简单了,除了初始化函数之外,就是OnClick函数了,Onclick函数就是该命令的主功能函数,命令的响应就写到该函数中。