ArcGIS Engine二次开发

ArcGIS Engine二次开发

1 ArcGIS Engine二次开发——基础篇
1.1 第一个简单的ArcGIS Engine地图显示程序
这个例子将引导您创建第一个简单的地图显示程序,并添加基本的缩放和漫游功能。如果您之前没有接触过ArcGIS Engine的开发,那么这个例子是您迈入ArcGIS Engine二次开发大门的极好例子,将从零开始引导您一步一步完成任务。
1.1.1 创建一个新的工程
首先打开Microsoft Visual Studio 2005,点击菜单栏中的“文件”—>“新建”—>“项目”,在弹出的对话框中选择新建一个Visual C#的Windows应用程序,之后更改项目名称为“地图浏览”,更改文件的路径为个人实习文件夹,点击“确定”即可。

图 1新建项目对话框
选中项目“地图浏览”中的窗体“Form1”,修改其Name属性为“MainForm”,Text属性为“地图浏览”,

图 2窗体命名

1.1.2 添加控件及引用
点击编译器最左侧的“工具箱”(不存在时可通过“视图”“工具箱”打开),在弹出的选择项中找到“ArcGIS Windows Forms”项,单击其中的MapControl,之后在Form1的空白处单击鼠标左键不放并拖拽鼠标,直到调整MapControl到合适的大小再松开鼠标(您也可以直接在工具箱中双击MapControl,该控件则会自动加入到Form1中)。用同样的方法,再将LicenseControl添加到Form1中。

图 3 打开工具箱

图 4工具箱

如果您在工具箱中找不到MapControl,则请依次尝试以下两种解决方案。首先单击工具栏,待工具箱弹出之后,在工具箱的任意位置上单击鼠标右键,从弹出菜单中选择“重置工具箱”。如果这一步操作之后仍然无法看到MapControl,则在工具箱的任意位置上单击鼠标右键,找到“常规”选项卡,然后在“常规”选项卡上单击鼠标右键,在弹出菜单中单击“选择项(I)…”,在弹出的对话框中选择“.NET Framework组件”,找到“LicenseControl”和“MapControl”,将这两项前的复选框打上勾,最后点击确定即可(如果在“.NET Framework组件”这个面板中找不到这两项,则选择“COM 组件”面板,在“ESRI LicenseControl”和“ESRI MapControl”前面打勾)。

图 5重置工具箱

图 6选择项

图 7选择工具箱项

图 8选择工具箱项

添加好MapControl和LicenseControl之后,调整Form1和MapControl的位置与大小,如下图所示:

图 9窗体布局

1.1.3 添加地图
在MapControl上单击鼠标右键,选择“属性”,则会弹出MapControl的属性设置面板,在之前也介绍过,通过这个面板可以完成许多简单的工作。
如图所示,点击“Map”面板,之后点击 按钮,在弹出的对话框中选择路径为“……\GIS设计与开发\例子数据\China”,再在此路径下选择“bou2_4p”,点击“Open”。之后在MapControl的属性页上点击“确定”即可。

图 10“Map”面板

至此,我们已经完成了一个最简单的地图显示程序。.点击“启动调试”按钮(或者在“调试”菜单下选择相应命令,或者按键盘的F5键),可以得到如下的运行结果。

图 11初次运行结果

1.1.4 添加代码
我们没有书写任何代码,就得到了一个最简单的地图显示程序。但这个程序还不能与用户交互,下一步我们需要添加一些代码,让程序能响应用户的鼠标,完成放大和全图显示的功能。
选中MapControl控件,单击属性窗口中的事件按钮 ,可以看到MapControl控件能够响应的所有事件(关于每个事件的详细使用方法等请参见帮助系统),我们可以通过双击对应事件进入代码编辑界面,这里我们选择“OnMouseDown”事件(注:控件的“OnMouseDown”事件也可以通过双击控件直接进入到代码编辑界面),下一步就需要在这个事件中添加响应鼠标的相关代码。

图 12 MapControl控件支持的所有方法

请您在axMapControl1的OnMouseDown事件中添加代码,如下所示:

    private void axMapControl1_OnMouseDown(object sender, ESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseDownEvent e)
    {
        if (e.button == 1)
            this.axMapControl1.Extent = this.axMapControl1.TrackRectangle();
        else if (e.button == 2)
            this.axMapControl1.Extent = this.axMapControl1.FullExtent;
}

再次运行程序,鼠标左键在地图上拉框可以实现地图的放大功能,而右键单击地图则会还原地图的全图显示。

图 23任意比例尺放大功能
如果将代码替换如下,则能实现左键放大,右键漫游的功能。

    private void axMapControl1_OnMouseDown(object sender, ESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseDownEvent e)
    {
        if (e.button == 1)
            this.axMapControl1.Extent = this.axMapControl1.TrackRectangle();
        else if (e.button == 2)
            this.axMapControl1.Pan();
    }

注释:
代码中根据e中包含的“button”值来判断鼠标的单击操作是来自何处,若button值为1,则为鼠标左键,2代表鼠标右键,4代表鼠标中键。当判断得到是鼠标左键单击时,执行“this.axMapControl1.Extent = this.axMapControl1.TrackRectangle();”该语句调用了“TrackRectangle()”方法,这个方法是在地图上拖拽出一个矩形,之后将这个矩形赋值给当前地图的显示区域(Extent),这样就实现了地图的放大功能。类似的,若鼠标右键单击,则将全图范围赋值给当前的显示范围,实现了地图的全图显示功能。
1.1.5 小结
通过这个例子,我们制作出了一个最简单的地图浏览程序AEMapView,并能响应一些基本的鼠标操作。在MapControl的属性页中,其实还有许多内容您可以尝试,例如在“General”面板中可以直接加入地图文件(.mxd或者.mxt),您也可以利用刚才的方式一次性多加入一些图层而不仅仅加入“bou2_4p”一个,同时可以更改各图层的叠放次序,也可以在“Data”面板中设置地图的旋转角度(Rotation)等,您还可以设置MapControl的显示方式,是否支持地图的预览功能,边框样式等等。您可以做一些尝试,看看能得到哪些有趣的结果,这些尝试对您今后熟悉ArcGIS Engine的开发是有一定帮助的。如果需要重置MapControl,只需要点击“Data”面板中的“Reset”按钮。当您完成了这个例子,并做了一些积极的尝试之后,您就可以接着学习下一个小节的内容了。
1.2 属性查询
查询是GIS中非常重要的一个功能,下面将分别介绍属性查询和空间查询的制作方法。
1.2.1 添加控件
如果上一小节的工程已经关闭,则将其打开,如果您之后又在MapControl中添加了一些别的数据,请将其删除,只保留一个“bou2_4p”图层,请务必注意这一步,这直接关系到您下面的工作能否顺利进行。用之前讲过添加控件的方式,在窗体中添加一个Label和一个TextBox。将Label控件的“Text”属性修改为“城市名称”,TextBox控件的Name属性修改为txtStateName。控件添加完毕后效果如下:

图 24添加Label控件和TextBox控件后界面

1.2.2 添加代码
首先添加引用。首先可以在项目的“解决方案资源管理器窗口”中单击展开“引用”选项,查看项目中已添加引用。

图 25项目已添加“引用”

这个项目中我们需要使用“ESRI.ArcGIS.Carto”和“ESRI.ArcGIS.Geodatabase”两个引用项,点击菜单栏上的“项目”—>“添加引用”(或者在“解决方案资源管理器窗口”中右击“引用”,在弹出菜单中选择“添加引用”),在弹出的对话框中选择需要添加的引用,同时选择“ESRI.ArcGIS.Carto”和“ESRI.ArcGIS.Geodatabase”(选择的时候按下Ctrl键以同时选择多个),这里“ESRI.ArcGIS.Carto”在添加MapControl控件时已自动添加,我们只添加“ESRI.ArcGIS.Geodatabase”,点击确定。

图 26添加引用对话框
之后双击TextBox控件,进入代码编辑界面。在代码编辑区域的命名空间(namespace)的上方输入以下内容:

using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geodatabase;

如下图所示:

图 27引用添加位置

之后在控件TextBox的事件中选择KeyUp,在KeyUp事件中添加以下代码:

图 28KeyUp方法

    private void txtStateName_KeyUp(object sender, KeyEventArgs e)
    {
        //判断鼠标键值,如果Enter键按下抬起后,进入查询
        if (e.KeyCode==Keys.Enter)
        {
            //定义图层,要素游标,查询过滤器,要素
            IFeatureLayer pFeatureLayer;
            IFeatureCursor pFeatureCursor;
            IQueryFilter pQueryFilter;
            IFeature pFeature;

            //获取图层
            pFeatureLayer = this.axMapControl1.Map.get_Layer(0) as IFeatureLayer;
            //如果图层名称不是states,程序退出
            if (pFeatureLayer.Name != "bou2_4p")
                return;
            //清除上次查询结果
            this.axMapControl1.Map.ClearSelection();

            //pQueryFilter的实例化
            pQueryFilter = new QueryFilterClass();
            //设置查询过滤条件
            pQueryFilter.WhereClause = "NAME='" + txtStateName.Text + "'";
            //查询
            pFeatureCursor = pFeatureLayer.Search(pQueryFilter, true);
            //获取查询到的要素
            pFeature = pFeatureCursor.NextFeature();

            //判断是否获取到要素
            if (pFeature!=null)
            {
                //选择要素
                this.axMapControl1.Map.SelectFeature(pFeatureLayer, pFeature);
                //放大到要素
                this.axMapControl1.Extent = pFeature.Shape.Envelope;
            }
            else
            {
                //没有得到pFeature的提示
                MessageBox.Show("没有找到名为" + txtStateName.Text + "的省", "提示");
            }
        }
    }

运行程序,分别向编辑框中输入“吉林省”和“长春省”,键入回车,如下图所示:

图 29吉林省查询结果

图 30长春省查询结果

注释:
if (e.KeyCode==Keys.Enter)
上述代码是一个判断语句,即当用户输入回车的时候,开始进行查询。
下面两行代码是定义查询的范围,默认为上一小节中添加的图层“bou2_4p”,如果找不到这个图层则自动退出。

      //获取图层
      pFeatureLayer = this.axMapControl1.Map.get_Layer(0) as IFeatureLayer;
      //如果图层名称不是states,程序退出
    if (pFeatureLayer.Name != " bou2_4p ")
         return;
      //清除上次查询结果
      this.axMapControl1.Map.ClearSelection();

下面部分是生成一个新的查询器,选择条件(WhereClause)就是检索是否有与用户输入相符的州,并将结果从查询得到的pCursor中读取出来。

       //pQueryFilter的实例化
       pQueryFilter = new QueryFilterClass();
       //设置查询过滤条件
    pQueryFilter.WhereClause = "NAME='" + txtStateName.Text + "'";
       //查询
       pFeatureCursor = pFeatureLayer.Search(pQueryFilter, true);
      //获取查询到的要素
    pFeature = pFeatureCursor.NextFeature();

下面部分是一个判断语句,若查询得到的结果为空,则刷新地图,弹出对话框通知用户没有查询到结果,并退出程序。如果查询得到的结果不为空,则将这个结果加入地图的选择集,并将地图的显示范围定为查询结果的外轮廓,这样得到的州将高亮显示同时居中放大到屏幕中心。
       //判断是否获取到要素
       if (pFeature!=null)
      {
          //选择要素
          this.axMapControl1.Map.SelectFeature(pFeatureLayer, pFeature);
        //放大到要素
          this.axMapControl1.Extent = pFeature.Shape.Envelope;
        }
        else
        {
            //没有得到pFeature的提示
            MessageBox.Show("没有找到名为" + txtStateName.Text + "的省", "提示");
        }

1.2.3 小结
这一部分中,我们接触到了基本的属性查询。但是在这个例子中,我们不能实现对属性表中任意属性字段的查询(在这个程序中,我们只能查询省名——NAME,而不能对别的字段进行查询),而且这个查询不支持模糊查询。为了使查询变的更加丰富,更加人性化,请您参考IQueryFilter接口中WhereClause属性的设置方法,拓展WhereClause可以得到许多有趣的结果。在书写代码的过程中,对任何有疑问的地方,或者您想要拓展的位置,都可以在帮助系统中查询相关的接口和属性,查看最原始的定义,帮助系统中的解释和定义对于您熟悉ArcObjects,熟悉ArcGIS Engine的二次开发以及后续的工作都是十分重要的,请一定不要忽视这个环节。如果您已经尝试了一些变化,或者对本小节的内容比较熟悉了,则可以进入下一小节的学习。
1.3 空间查询
上一小节我们已经学习了如何进行属性查询,在这一小节中,我们将继续学习GIS中的另一种查询方式——空间查询,其中有点查询、线查询、矩形查询、圆查询。本节我们将空间查询的方法抽象为一个独立的函数,这个函数中我们将根据不同的空间查询方式,返回查询得到的一个或多个要素的名称并在地图上高亮显示。
1.3.1 添加控件
新建一个C#.Net工程,向工程中添加控件,如下图所示:

图 31 窗体布局

其中包括MapControl,4个Button,一个TextBox。属性设置如下:
窗体及控件属性设置
类型 Name Text 用途
Form MainForm 空间查询 主窗体
TextBox txtTips 请在地图上选取地物! 系统操作提示
Button btnPointQuery 点查询 点查询
Button btnLineQuery 线查询 线查询
Button btnRectQuery 矩形查询 矩形查询
Button btnCircleQuery 圆查询 圆查询

通过在控件属性中添加地图的方法,向Mapcontrol中添加例子数据。(例子数据是位于China文件夹下的bou2_4p)如下图所示:

图 32添加数据

下面我们在MainForm的代码页添加空间查询的函数。本例中我们需要添加ESRI.ArcGIS.Carto、ESRI.ArcGIS.Geometry、ESRI.ArcGIS.Geodatabase、ESRI.ArcGIS.Controls四个个命名空间。

我们仍然需要上节中的ConvertPixelToMapUnits(IActiveView activeView,double pixelUnits)函数,请自行添加。
1.3.2 添加代码
先在类中添加一个公共函数,用来根据屏幕像素计算实际的地理距离。

    /// <summary>
    /// 根据屏幕像素计算实际的地理距离
    /// </summary>
    /// <param name="activeView">屏幕视图</param>
    /// <param name="pixelUnits">像素个数</param>
    /// <returns></returns>
    private double ConvertPixelToMapUnits(IActiveView activeView,double pixelUnits)
    {
        double realWorldDiaplayExtent;
        int pixelExtent;
        double sizeOfOnePixel;
        double mapUnits;
        
        //获取设备中视图显示宽度,即像素个数
        pixelExtent=activeView.ScreenDisplay.DisplayTransformation.get_DeviceFrame().right-                activeView.ScreenDisplay.DisplayTransformation.get_DeviceFrame().left;
        //获取地图坐标系中地图显示范围
        realWorldDiaplayExtent = activeView.ScreenDisplay.DisplayTransformation.VisibleBounds.Width;
        //每个像素大小代表的实际距离
        sizeOfOnePixel = realWorldDiaplayExtent / pixelExtent;
        //地理距离
        mapUnits = pixelUnits * sizeOfOnePixel;

        return mapUnits;
    }

然后添加空间查询的方法,空间查询函数代码如下:

    /// <summary>
    /// 空间查询
    /// </summary>
    /// <param name="mapControl">MapControl</param>
    /// <param name="geometry">空间查询方式</param>
    /// <param name="fieldName">字段名称</param>
    /// <returns>查询得到的要素名称</returns>
    private string QuerySpatial(AxMapControl mapControl, IGeometry geometry, string fieldName)
    {
        //本例添加一个图层进行查询,多个图层时返回
        if (mapControl.LayerCount > 1)
            return null;

        //清除已有选择
        mapControl.Map.ClearSelection();

        //查询得到的要素名称
        string strNames=null;

        IFeatureLayer pFeatureLayer;
        IFeatureClass pFeatureClass;
        //获取图层和要素类,为空时返回
        pFeatureLayer = mapControl.Map.get_Layer(0) as IFeatureLayer;
        pFeatureClass = pFeatureLayer.FeatureClass;
        if (pFeatureClass == null)
            return null;

        //初始化空间过滤器
        ISpatialFilter pSpatialFilter;
        pSpatialFilter = new SpatialFilterClass();
        pSpatialFilter.Geometry = geometry;
        //根据图层类型选择缓冲方式
        switch (pFeatureClass.ShapeType)
        {
            case esriGeometryType.esriGeometryPoint:
                pSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelContains;
                break;
            case esriGeometryType.esriGeometryPolyline:
                pSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelCrosses;
                break;
            case esriGeometryType.esriGeometryPolygon:
                pSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects;
                break;
        }
        //定义空间过滤器的空间字段
        pSpatialFilter.GeometryField = pFeatureClass.ShapeFieldName;

        IQueryFilter pQueryFilter;
        IFeatureCursor pFeatureCursor;
        IFeature pFeature;
        //利用要素过滤器查询要素
        pQueryFilter = pSpatialFilter as IQueryFilter;
        pFeatureCursor = pFeatureLayer.Search(pQueryFilter, true);
        pFeature = pFeatureCursor.NextFeature();

        int fieldIndex;
        while (pFeature != null)
        {
            //选择指定要素
            fieldIndex = pFeature.Fields.FindField(fieldName);
            //获取要素名称
            strNames = strNames + pFeature.get_Value(fieldIndex) + ";";
            //高亮选中要素
            mapControl.Map.SelectFeature((ILayer)pFeatureLayer, pFeature);
            mapControl.ActiveView.Refresh();
            pFeature = pFeatureCursor.NextFeature();
        }

        return strNames;
    }

定义鼠标标记的成员变量mMouseFlag。在设计页面双击点查询按钮,进入点击按钮响应事件填写如下代码。

    private void btnPointQuery_Click(object sender, EventArgs e)
    {
        mMouseFlag = 1;
        this.axMapControl1.MousePointer = esriControlsMousePointer.esriPointerCrosshair;
    }

相应的线查询、矩形查询、圆查询添加同样的代码,但nMouseFlag得值要有所改变。
线查询:nMouseFlag=2
矩形查询:nMouseFlag=3
圆查询:nMouseFlag=4

为MapControl控件添加OnMouseDown事件,填入以下代码
private void axMapControl1_OnMouseDown(object sender, IMapControlEvents2_OnMouseDownEvent e)
{
//记录查询到的要素名称
string strNames = “查询到的要素为:”;
//查询的字段名称
string strFieldName=“NAME”;
//点查询
if (mMouseFlag == 1)
{
IActiveView pActiveView;
IPoint pPoint;
double length;
//获取视图范围
pActiveView = this.axMapControl1.ActiveView;
//获取鼠标点击屏幕坐标
pPoint = pActiveView.ScreenDisplay.DisplayTransformation.ToMapPoint(e.x, e.y);
//屏幕距离转换为地图距离
length = ConvertPixelToMapUnits(pActiveView, 2);

            ITopologicalOperator pTopoOperator;
            IGeometry pGeoBuffer;
            //根据缓冲半径生成空间过滤器
            pTopoOperator = pPoint as ITopologicalOperator;
            pGeoBuffer = pTopoOperator.Buffer(length);
            strNames = strNames + QuerySpatial(this.axMapControl1, pGeoBuffer, strFieldName);
        }             
        else if (mMouseFlag==2)//线查询
        {
            strNames = strNames+QuerySpatial(this.axMapControl1, this.axMapControl1.TrackLine(), strFieldName);
        }                
        else if (mMouseFlag==3)//矩形查询
        {
            strNames = strNames + QuerySpatial(this.axMapControl1, this.axMapControl1.TrackRectangle(), strFieldName);
        }                
        else if (mMouseFlag==4)//圆查询
        {
            strNames = strNames + QuerySpatial(this.axMapControl1, this.axMapControl1.TrackCircle(), strFieldName);
        }
        else 
        {
            strNames = "未得到空间要素!";
        }
        //提示框显示提示
        this.txtTips.Text =strNames;
    }

注释:
距离转换函数请参看程序注释。

Button的Click事件中是将nMouseFlag设置为1,并将鼠标在MapControl上的形状改变为十字丝状。

            //获取视图范围
            pActiveView = this.axMapControl1.ActiveView;
            //获取鼠标点击屏幕坐标
            pPoint = pActiveView.ScreenDisplay.DisplayTransformation.ToMapPoint(e.x, e.y);
            //屏幕距离转换为地图距离
            length = ConvertPixelToMapUnits(pActiveView, 2);

上述代码是在MapControl的OnMouseDown事件中,当您单击鼠标左键的时候,获取点击位置的屏幕坐标,并将屏幕上的两个像素大小的距离转换成地图上的距离,作为查询的缓存半径。

            //根据缓冲半径生成空间过滤器
            pTopoOperator = pPoint as ITopologicalOperator;
            pGeoBuffer = pTopoOperator.Buffer(length);
            pSpatialFilter = new SpatialFilterClass();
            pSpatialFilter.Geometry = pGeoBuffer;

上述代码是以鼠标的点击位置,以缓冲距离length为半径,生成一个缓冲区。

            pSpatialFilter = new SpatialFilterClass();
            pSpatialFilter.Geometry = pGeoBuffer;
            //根据图层类型选择缓冲方式
            switch (pFeatureClass.ShapeType)
            {
                case esriGeometryType.esriGeometryPoint:
                    pSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelContains;
                    break;
                case esriGeometryType.esriGeometryPolyline:
                    pSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelCrosses;
                    break;
                case esriGeometryType.esriGeometryPolygon:
                    pSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects;
                    break;
            }
            //定义空间过滤器的空间字段
            pSpatialFilter.GeometryField = pFeatureClass.ShapeFieldName;

上述代码是设置pSpatialFilter的各项参数,供后续查询,包括空间查询的几何形状(之前生成的缓冲区),空间查询的方式(相交,包含等)以及Shape字段。

                fieldIndex = pFeature.Fields.FindField("NAME");
                MessageBox.Show("查找到“" + pFeature.get_Value(fieldIndex) + "”", "提示");

这两句代码是找出“NAME”所在的列数,并将其显示出来。

点击运行,运行效果如下图所示:

图 33 线查询运行效果
仔细研读代码,您会发现,在这部分中我们并没有用到什么新的知识,只是在结构上做了调整,应为空间查询都是需要使用一个IGeometry对象进行空间求交进行查询的。所以我们将公共的代码放在公共的模块中进行调用。有心的同学可能发现,我们为了判断用户在MapControl上的操作,我们引入了一个全局变量nMouseFlag,程序中多一个全局变量,对程序的结构的封闭性就有所破坏,能不能去掉这个全局变量而是Mapcontrol自主判断是哪个功能进行操作呢?答案是肯定的,我们可以使用BaseCommand和BaseTool来完成这个工作,详细的用法在3.4和3.5小节将会介绍。

1.3.3 小结
在这一小节中,我们学习了如何进行简单的空间查询。空间查询不仅包括点查询,还包括线查询,矩形查询,多边形查询等(为了实现这些功能,可以参考MapControl中的TrackRectangle等方法)。对于这一小节的代码,强烈建议您参看帮助系统中对相关接口的解释和定义,以进一步熟悉接口的使用,这对后面的学习以及掌握ArcGIS Engine二次开发是极有好处的。如果您对这一部分比较熟悉了,可以进入下一小节。在第四章中,我们介绍了控件命令(Control Commands),并提到ArcGIS Engine允许用户自定义开发一些控件命令,在下两小节中,我们将具体学习如何开发。
1.4 BaseCommand开发实例
在这一小节和下一小节中中,我们将学习ArcEngine中基于BaseCommand和BaseTool的功能开发步骤。基于BaseCommand的功能实现与Button的功能类似,是当鼠标点击按钮的时候,MapControl控件会对其中的命令做出相应响应而无需额外的操作,如ArcMap中的居中放大FixedZoomIn,全图FullExtent等。
在这一小节中,我们将基于BaseCommand制作一个“固定比例尺放大”的按钮,当鼠标单击按钮时,地图将居中放大一倍。
1.4.1 添加控件
如果上一小节的程序已经关闭,则重新打开,同时保证MapControl控件中加载了至少一个图层。在主窗体(MapViewForm)中添加一个Button,将其Name属性改为btnFixedZoomIn,Text属性更改为“居中放大”。
1.4.2 添加BaseCommand
点击菜单栏上的“项目”—>“添加类”,弹出以下对话框。

图 34添加新项对话框

如上图所示,在类别中选择ArcGIS项,在右侧的模板中选择“BaseCommand”项,并在名称中将其更改为“FixedZoomIn”,点击添加,出现如下对话框。

图 35类别选择向导

我们这个命令是用于MapControl控件的,所以在选择项中选择“ArcMap,MapControl or PageLayoutControl Command”或者“MapControl or PageLayoutControl Command”,这里我们选择后者,点击OK。
1.4.3 添加代码
双击解决方案资源管理器中的FixedZoomIn.cs项,进入该类的代码编写界面。首先按照前几节介绍过的方法,加入引用“ESRI.ArcGIS.Geometry”,并在该类的最上方添加如下代码:
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geometry;

将 base.m_caption、base.m_toolTip 更改为“居中放大”,将base.m_name 更改为“FixedZoomIn”。之后在OnClick()函数中添加如下代码:

    public override void OnClick()
    {
        // TODO: Add FixedZoomIn.OnClick implementation
        //获取当前视图范围
        IActiveView pActiveView = m_hookHelper.ActiveView;
        IEnvelope pEnvelope = pActiveView.Extent;
        //扩大视图范围并刷新视图
        pEnvelope.Expand(0.5, 0.5, true);
        pActiveView.Extent = pEnvelope;
        pActiveView.Refresh();
}

转到主窗体(MapViewForm),双击“居中放大”按钮,进入该按钮Click事件相应函数,添加如下代码:

    private void btnZoomIn_Click(object sender, EventArgs e)
    {
        //声明与初始化
        FixedZoomIn fixedZoomin = new FixedZoomIn();
        //与MapControl关联
        fixedZoomin.OnCreate(this.axMapControl1.Object);
        fixedZoomin.OnClick();
}

1.4.4 运行
运行程序,点击“居中放大”时,地图会放大一倍。点查询功能依然可用,如下图:

图 36程序运行结果

1.4.5 小结
在这一小节中,我们学习到了如何制作一个BaseCommand。使用BaseCommand的好处主要有,首先按照面向对象的思想,居中放大这个功能已经被封装在我们自己书写的类中,若是以后需要再将这个功能移植到别的程序,或者由多个程序员共同完成一个程序时,只需要将这个类复制到相关工程下,稍作调整即可运行;其次,这样做可以是代码更易读,而且当需要完成许多不同的功能时,这种方法的优势就体现出来了,因为我们不需要再单独设立一个MouseFlag变量来判断具体用户点击了哪个按钮,MapControl的OnMouseDown事件中也无需再添加冗长的代码,而是分散到各类中,增强了程序的稳定性。在新建FixedZoomIn类的同时,我们发现还会附带生成一个FixedZoomIn.bmp位图文件,您可以双击这个图标以做相关编辑更改工作,也可以用自己的图标来替换(注意保持文件名一致)。这个图标的作用,是在使用ToolbarControl的时候,用于显示按钮图标的。您可以尝试着在工程中加入一根ToolbarControl,并使用AddItem方法添加我们写好的这个类,看能否得到一样的结果。
使用ArcEngine自带BaseCommand基类,可以方便的开发出相关的Command按钮,从这个实例我们可以看出,根据Command按钮随鼠标点击MapControl及时响应的特性,我们一般只需要重载BaseCommand 的OnClick()函数即可。然后在功能的实现处首先调用OnCreate()函数实现与MapControl的关联,调用OnClick()函数实现功能响应。这样有效的提高了我们进行功能开发的效率。
其实,对于一些基本的地图操作Command的功能,ArcEngine进行了完整的封装,我们在使用时可以直接使用ArcEngine的封装类进行实例化。仍以“固定比例尺放大”为例,我们可以在“居中放大”按钮的Click事件中直接使用ArcEngine的封装类实现(注意,在这个示例中需要添加“ESRI.ArcGIS.SystemUI”和“ESRI.ArcGIS.Controls”的引用),代码如下:

        ICommand command = new ControlsMapZoomInFixedCommandClass();
        command.OnCreate(this.axMapControl1.Object);
   command.OnClick();

这种方法比我们基于BaseCommand的开发方法更加简便,我们在此介绍的目的是为了让大家掌握这种基本的开发方法,方便用于其他Command功能的开发。如果您对本小节的内容比较熟悉,也做了一些积极的尝试,那么您可以进入下一小节的学习。在下一小节中我们将学习BaseTool的开发方法。
1.5 BaseTool开发实例
经过上一小节的学习,我们了解到了如何自定义BaseCommand来拓展ArcGIS的应用。我们将学习基于BaseTool的自定义功能开发,BaseTool与BaseCommand有些相似的地方,它们都是点击之后可以对MapControl控件做相应的操作,但是BaseCommand点击之后MapControl会直接予以相应,不需要额外的操作,而对于BaseTool来说,点击该功能之后,只是开启一个交互的过程,需要用户再用鼠标、键盘等对地图做进一步交互式的操作,MapControl控件才会予以相应,如ArcMap中的放大ZoomIn、漫游Pan等。
为了实现BaseTool与BaseCommand功能实现的差异,在这一小节中,我们将剖析ArcMap的放大(ZoomIn)功能,并利用BaseTool进行实现。
1.5.1 打开工程
我们这里需要在上一小节的基础上继续完善,如果您已经将MapView关闭,请重新打开。在主窗体(MapViewForm)中添加一个Button,将其Name属性改为btnZoomIn,Text属性更改为“拉框放大”。
3.5.2 添加BaseTool
在菜单栏上选择“项目”——“添加类”,出现如下对话框:

图 37添加新项对话框

在类别中选中ArcGIS,在模板中选择BaseTool,并将名称更改为“ZoomIn”,点击添加,出现如下对话框:

图 38类别选择向导

我们这个工具是要用于MapControl,仍选择“MapControl or PageLayoutControl Command”,点击OK。
1.5.2 添加代码
双击解决方案资源管理器中的 ZoomIn.cs,进入该类的代码编写界面。
首先添加ESRI.ArcGIS.Carto、ESRI.ArcGIS.Geodatabase、ESRI.ArcGIS.Geometry、ESRI.ArcGIS…Display四个引用,类似的,将 base.m_caption、base.m_toolTip 更改为“拉框放大”,将base.m_name 更改为“ZoomIn”。
我们简单分析一下拉框放大的执行过程,点击“拉框放大”按钮后,鼠标在MapControl的视图中的拉框过程可以分解为三个事件,鼠标在视图上的按下(MouseDown),鼠标按下在视图上的移动(MouseMove),鼠标放开(MouseUp),我们需要在鼠标按下时刻和放开时刻记录鼠标点击的坐标,然后可以得到一个新的视图范围,完成放大操作。
下面添加代码,首先我们需要在这个类中定义三个成员变量,三个成员变量的功能如注释所示。

    //记录鼠标位置
    private IPoint m_point;
    //标记MouseDown是否发生
    private Boolean m_isMouseDown;
    //追踪鼠标移动产生新的Envelope
    private INewEnvelopeFeedback m_feedBack;

在ZoomIn.cs类中的OnMouseDown函数中添加如下代码:

    public override void OnMouseDown(int Button, int Shift, int X, int Y)
    {
        //当前地图视图为空时返回
        if (m_hookHelper.ActiveView == null)
            return;
        //获取鼠标点击位置
        m_point = m_hookHelper.ActiveView.ScreenDisplay.DisplayTransformation.ToMapPoint(X, Y);
        m_isMouseDown = true;
}

在ZoomIn.cs类中的OnMouseMove函数中添加如下代码:

    public override void OnMouseMove(int Button, int Shift, int X, int Y)
    {
        //MouseDown为发生时返回
        if (!m_isMouseDown)
            return;

        IActiveView pActiveView = m_hookHelper.ActiveView;
        //m_feedBack追踪鼠标移动
        if (m_feedBack == null)
        {
            m_feedBack = new NewEnvelopeFeedbackClass();
            m_feedBack.Display = pActiveView.ScreenDisplay;
            //开始追踪
            m_feedBack.Start(m_point);
        }
        //追踪鼠标移动位置
        m_feedBack.MoveTo(pActiveView.ScreenDisplay.DisplayTransformation.ToMapPoint(X, Y));

}

在ZoomIn.cs类中的OnMouseUp函数中添加如下代码:

    public override void OnMouseUp(int Button, int Shift, int X, int Y)
    {
        //MouseDown为发生时返回
        if (!m_isMouseDown) return;

        IActiveView pActiveView = m_hookHelper.ActiveView;

        //获取MouseUp发生时的范围并放大
        IEnvelope pEnvelope;
        if (m_feedBack == null)//鼠标未拉框时进行固定比例尺放大
        {
            pEnvelope = pActiveView.Extent;
            pEnvelope.Expand(0.5, 0.5, true);
            pEnvelope.CenterAt(m_point);
        }
        else
        {
            //停止追踪
            pEnvelope = m_feedBack.Stop();

            //判断新的范围的高度和宽度是否为零
            if (pEnvelope.Width == 0 || pEnvelope.Height == 0)
            {
                m_feedBack = null;
                m_isMouseDown = false;
            }
        }
        //获取新的范围
        pActiveView.Extent = pEnvelope;
        //刷新视图
        pActiveView.Refresh();
        m_feedBack = null;
        m_isMouseDown = false;

}

再进入MapViewForm窗体的代码界面,定义成员变量

        private ZoomIn mZoomIn = null;

双击MapViewForm窗体上的“拉框放大”按钮,进入Click事件响应函数,将其中的代码删除,用下列代码替代:

    private void btnZoomIn_Click(object sender, EventArgs e)
    {
        //初始化
        mZoomIn = new ZoomIn();
        //与MapControl的关联
        mZoomIn.OnCreate(this.axMapControl1.Object);
        //设置鼠标形状
        this.axMapControl1.MousePointer = esriControlsMousePointer.esriPointerZoomIn;
}

将MapControl控件的OnMouseDown响应函数中的内容全部删除,添加代码如下:

        if (mZoomIn != null)
            mZoomIn.OnMouseDown(e.button, e.shift, e.x, e.y);

在MapControl控件的OnMouseMove响应函数中添加代码如下:

        if (mZoomIn != null)
            mZoomIn.OnMouseMove(e.button, e.shift, e.x, e.y);

在MapControl控件的OnMouseUp响应函数中添加代码如下:

        if (mZoomIn != null)
            mZoomIn.OnMouseUp(e.button, e.shift, e.x, e.y);

1.5.3 运行

图 39程序运行结果

如上图所示,首先点击“拉框放大”按钮,然后在MapControl中按下鼠标拉框,即可完成放大,点击不拖动鼠标情况下为居中放大。
1.5.4 小结
在这一小节中,我们学习了如何制作BaseTool,正如同前一小节的小结中写到的那样,当有许多功能(例如漫游,点查询等)时,由于BaseTool在生成的时候会自动和MapControl控件关联起来。在这个例子中,我们通过重载自定义了OnMouseDown、OnMouseMove和OnMouseUp三个函数,实现Tool类型功能的响应。当然,这里的拉框放大功能在ArcEngine中也进行了封装。利用封装类来实现Tool类型的工具时,同样需要定义ICommand接口,通过ICommand接口来实现与MapControl的关联。通过查询帮助文档我们可以发现,本节中我们所自定义的BaseTool工具也是从接口ICommand和ITool同时继承得到的。以“拉框放大”为例,利用ArcEngine封装的类库实现基本的Tool类型功能的代码如下(注意,在这个示例中需要添加“ESRI.ArcGIS.SystemUI”和“ESRI.ArcGIS.Controls”的引用),感兴趣的同学可以将这段代码拷贝到“拉框放大”按钮的Click事件中,删除原来的代码,运行程序可以看到一致的效果。

        //Tool的定义和初始化
        ITool tool = new ControlsMapZoomInToolClass();

//查询接口获取ICommand
ICommand command = tool as ICommand;
//Tool通过ICommand与MapControl的关联
command.OnCreate(this.axMapControl1.Object);
command.OnClick();
//MapControl的当前工具设定为tool
this.axMapControl1.CurrentTool = tool;

如果您对这一小节的内容比较熟悉了,就可以开始学习本章最后一小节的内容了。在下一小节中,我们将尝试构建一个小型GIS应用。
1.6 通过代码添加图层
为了使得程序更加灵活,我们需要在程序运行后动态的向MapControl中添加图层。如何通过代码来添加地图是在本小节需要学习的。
首先我们创建一个新的Windows应用程序,名称为“AddData”,然后在窗体上添加MapControl、LicenceControl和四个Button控件,窗体及控件属性设置如下:

表 4窗体及控件属性设置
类型 Name Text 用途
Form MainForm 添加数据 主窗体
Button btnClear 清空图层 清空图层
Button btnMxd 打开MXD 打开MXD文档
Button btnShp 添加Shp 添加Shp图层
Button btnGdbVector 添加GDB矢量 添加GDB矢量数据
界面效果如下图:

图 40添加数据界面效果

为了测试多个添加数据操作的方便,我们添加了一个清空图层的按钮,双击该按钮进入代码编辑界面,添加代码如下:

    private void btnClear_Click(object sender, EventArgs e)
    {
        //如果MapControl图层个数大于零就清空图层
        if (this.axMapControl1.LayerCount > 0)
            this.axMapControl1.ClearLayers();
    }

1.6.1 通过代码添加MXD文件
MXD文件是ArcMap产生的地图索引文件,需要注意的是MXD文件并不含有地图数据。打开MXD文件比较简单,使用OpenFileDialog来实现,核心代码如下。需要注意的是,应为MXD文件只是个索引文件。在测试这部分程序时,你需要用ArcMap生成一个新的MXD文件。双击“打开MXD”按钮,进入代码编辑界面,添加代码如下:
private void btnMxd_Click(object sender, EventArgs e)
{
//文件路径名称,包含文件名称和路径名称
string strName=null;

        //定义OpenFileDialog,获取并打开地图文档
        OpenFileDialog openFileDialog=new OpenFileDialog();
        openFileDialog.Title="打开MXD";
        openFileDialog.Filter="MXD文件(*.mxd)|*.mxd";
        if (openFileDialog.ShowDialog()==DialogResult.OK)
        {
            strName=openFileDialog.FileName;
            if (strName!="")
            {
                this.axMapControl1.LoadMxFile(strName);
            }
        }
        //地图文档全图显示
        this.axMapControl1.Extent=this.axMapControl1.FullExtent;
} 

1.6.2 通过代码添加shp图层
添加shp图层的方法与打开mxd的思路一致,代码如下:

   private void btnShp_Click(object sender, EventArgs e)
    {
        //文件路径名称,包含文件名称和路径名称
        string strName = null;
        //文件路径
        string strFilePath=null;
        //文件名称
        string strFileName=null;

        //定义OpenFileDialog,获取并打开地图文档
        OpenFileDialog openFileDialog = new OpenFileDialog();
        openFileDialog.Title = "添加Shp";
        openFileDialog.Filter = "shp文件(*.shp)|*.shp";
        if (openFileDialog.ShowDialog() == DialogResult.OK)
        {
            strName = openFileDialog.FileName;
            if (strName != "")
            {
                strFilePath=System.IO.Path.GetDirectoryName(strName);
                strFileName=System.IO.Path.GetFileNameWithoutExtension(strName);
                this.axMapControl1.AddShapeFile(strFilePath, strFileName);
            }
        }
        //地图文档全图显示
        this.axMapControl1.Extent = this.axMapControl1.FullExtent;
    }

1.6.3 通过代码加载GeoDatabase中的数据
Geodatabase是ArcInfo8引入的一种全新的面向对象的空间数据模型,是建立在DBMS之上的统一的、智能的空间数据模型。Geodatabase以层次结构的数据对象来组织地理数据。这些数据对象存储在要素类(Feature Classes)、对象类(0bject classes)和数据集(Feature datasets)中。Object Class可以理解为是一个在Geodatabase中储存非空间数据的表。而Feature class是具有相同几何类型和属性结构的要素(Feature)的集合。Geodatabase提供了不同层次的空间数据存储方案,可以分成PersonalGeodatabase(个人空间数据库)、File Geodatabase(基于文件格式的数据库)和ArcSDE Geodatabase(企业级空间数据库)三种形式。本节以PersonalGeodatabase为例,实现PersonalGeodatabase的数据加载。Personal Geodatabase主要适用于在单用户下工作的C/S系统,适用于小型项目的地理信息系统。Personal Geodatabase实际上就是一个Microsoft Access数据库,最大数据容量为2G,并且仅支持windows平台。
在正式开始动手之前,我们先来简单分析一下Geodatabase模型中主要对象与物理存储之间的对应关系,在ArcCatalog中打开我们所使用的示例数据,展开目录,可以看到下图的关系,在物理级别上,mdb数据库对应于数据模型中的Workspace,数据库中包含一个或多个数据集(Dataset),数据集中包含一个或多个要素类(Featureclass),因此,我们在进行mdb中数据加载时,首先需要获取要素数据集,然后获取要素数据集中的要素类,才能添加到MapControl中进行显示。

图 41 Geodatabase中的对象层次关系
本例中我们需要添加“ESRI.ArcGIS.Geodatabase”、“ESRI.ArcGIS.DataSourcesGDB”和“ESRI.ArcGIS.Carto”三个命名空间。
我们需要编写一个独立的方法,该方法根据指定的路径名称读取mdb,并返回其中包含的要素类,代码如下:

    private List<IFeatureClass> OpenMdb(string mdbpath)
    {
        List<IDataset> pDatasets = new List<IDataset>();
        List<IFeatureClass> pFeatureClasses = new List<IFeatureClass>();
        //定义空间工厂,打开mdb数据库
        IWorkspaceFactory pAccessFactory = new AccessWorkspaceFactoryClass();
        IWorkspace pWorkspace = pAccessFactory.OpenFromFile(mdbpath, 0);
        //获取数据集的集合
        IEnumDataset pEnumDataset = pWorkspace.get_Datasets(esriDatasetType.esriDTAny);
        pEnumDataset.Reset();
        IDataset pDataset = pEnumDataset.Next();
        while (pDataset != null)
        {
            //数据集为featuredataset
            if (pDataset is IFeatureDataset)
            {
                string strDatasetName = pDataset.Name;
                //定义要素工厂,获取要素类的集合
                IFeatureWorkspace pFeatureWorkspace = pWorkspace as IFeatureWorkspace;
                IFeatureDataset pFeatureDataset = pFeatureWorkspace.OpenFeatureDataset(strDatasetName);
                IEnumDataset pEnumDataset2 = pFeatureDataset.Subsets;
                pEnumDataset.Reset();
                IDataset pDataset2 = pEnumDataset.Next();
                //遍历要素类的集合吗,并将要素类加入要素类集合pFeatureClasses
                while (pDataset2 != null)
                {
                    if (pDataset2 is IFeatureClass)
                    {
                        pFeatureClasses.Add(pDataset2 as IFeatureClass);
                    }
                    pDataset2 = pEnumDataset2.Next();
                }
                pDatasets.Add(pDataset);
            }
            pDataset = pEnumDataset.Next();
        }
        return pFeatureClasses;
    }
注意:这段代码中数据集的集合和要素类的集合的获取都使用了C#中泛型集合的知识,如List<IDataset> pDatasets = new List<IDataset>(),这是C#语言的概念,用于管理一个指定类型的集合,如此处的IDataset。
下面双击“添加GDB数据”按钮,进入代码编辑界面,添加代码如下:

   private void btnGdbVector_Click(object sender, EventArgs e)
    {
        //定义OpenFileDialog,获取路径
        OpenFileDialog openFileDialog = new OpenFileDialog();
        openFileDialog.Title = "添加GDB矢量数据";
        openFileDialog.Filter = "MDB文件(*.mdb)|*.mdb";
        //定义数据集的集合,用于存储mdb中的数据集
        List<IDataset> pDatasets=new List<IDataset>();
        //定义要素类集合,用于获取数据集中的要素类
        List<IFeatureClass> pFeatureClasses = new List<IFeatureClass>();
        if (openFileDialog.ShowDialog() == DialogResult.OK)
        {
            //获取数据集的集合
            pFeatureClasses = this.OpenMdb(openFileDialog.FileName);
            //变量要素类集合的每个要素类
            foreach (IFeatureClass pFeatureClass in pFeatureClasses)
            {
                IFeatureLayer pFeatureLayer = new FeatureLayerClass();
                pFeatureLayer.FeatureClass = pFeatureClass;
                //要素图层加入到MapControl
                this.axMapControl1.AddLayer((ILayer)pFeatureLayer);
            }
        }
        this.axMapControl1.Extent = this.axMapControl1.FullExtent;
    }

1.6.4 小结
到此为止,有关通过代码添加图层的专题我们就介绍到这里。从以上的三个例子可以看出,添加数据的基本思路是相通的,通过OpenFileDialog来指定过滤文件的类型,获取用户选中的文件路径和名称,然后利用ArcEngine中对应的方法来获取数据,最后添加到MapControl中显示。其中添加mxd和shp是比较基本和常用的方法,添加MDB数据略显繁琐,在我们给出的例子中仅实现了包含要素类型数据的打开,有兴趣的同学可以尝试包含栅格等数据类型的mdb。这一节在同学们掌握通过代码打开数据的方法的同时,希望同时掌握OpenFileDialog的使用方法,在实际的开发中,它也是经常用到的。
1.7 构建一个简单的GIS应用
在这一小节中,我们不会再像前五小节一样,只是针对某个具体的功能,而是将构建一个初具规模的小型GIS应用。强烈建议您在开始这小节的学习之前,再次熟悉之前的几个小节,这样对于您掌握这一小节的内容是十分有帮助的。在这一小节中,我们的重点是如何利用C#.NET迅速搭建其一个GIS应用,也即框架的搭建,而不是具体某个功能如何实现,所以对这一小节中所有的代码不再给出详细的解释,请您自行参照帮助系统了解各接口的详细定义与使用方法。我们展示的例子中,有些类似功能在实际开发过程中不会采用这一小节中展示的方式,但这样能更好的向您介绍第三章最后一部分提到的一些拓展控件。
在构建小型GIS应用的过程中,首先应该做需求分析和功能设计,再进行用户界面的设计,之后进行程序框架搭建和具体的编码工作,最后完成测试和维护。
1.7.1 功能概述
之前我们所做的程序都是在MapControl中预先加入数据,这一小节中,我们将改变这一做法,制作与数据无关的程序。在这个程序中,我们将按照Windows编程的一般方法,根据功能完成窗体界面设计,然后编码实现;在这里,我们将对前面所做的功能做一个整合,构成一个相对完整的GIS系统。
1.7.2 新建及整理工程
在这一小节中,我们将新建一个工程,我们将这个工程命名为“MyGIS”。进入MyGIS工程编辑界面之后,我们看到解决方案资源管理器。右键点击MyGIS,在弹出的右键菜单中点击“添加”—>“新建文件夹”,建立三个文件夹,分别命名为“Classes”、 “Forms”和“Resources”,用来存放系统自定义类、窗体和系统资源。
并将Form1.cs重命名为MainForm.cs,Text属性修改为MyGIS,并移动到Forms文件夹下。
在Forms文件夹右击点击“添加”—>“Windows窗体”,添加两个窗体,分别用于空间查询和属性查询,参数设置如下表。

表 5功能窗体参数设置
窗体名称(Name) Text属性 描述
SpatialQueryForm 空间查询 用于空间查询参数设置
AttributeQueryForm 属性查询 用于属性查询参数设置
注意:我们在项目中添加文件夹时,文件夹的名字会自动加入到我们新建的工程文件的命名空间中,比如这里我们创建的两个新窗体的命名空间(namespace)为“MyGIS.Forms”,原来MainForm的命名空间为“MyGIS”,这里我们将MainForm的命名空间也统一改为“MyGIS.Forms”。

图 42 MainForm命名空间修改前后

.NET Framework 使用命名空间(namespace)来组织它的众多类。在较大的编程项目中,声明自己的命名空间可以帮助控制类和方法名的范围。如我们的项目中在命名空间“MyGIS.Forms”下的MainForm、SpatialQueryForm和AttributeQueryForm三个窗体就构成了一个逻辑组合,假如另一个命名空间“YourGIS.Forms”也包含另一个SpatialQueryForm窗体,则我们在定义SpatialQueryForm实例时会造成歧义,程序会分不清我们定义的SpatialQueryForm窗体的来源,而通过Using关键字添加SpatialQueryForm命名空间的引用即可实现区分。
1.7.3 布局主界面
我们的MyGIS的主界面需要添加菜单栏,一个工具栏,一个状态栏和地图操作相关MapControl,TOCControl,下面我们就开始动手搭建主界面吧。
1.7.3.1 添加菜单栏
添加菜单栏,在属性窗口中点击Items项右侧的按钮,弹出如下对话框:

图 43菜单栏的项集合编辑器

首先添加一级菜单。点击窗体上方的“添加”按钮三次,加入三个MenuItem,并将其Name属性分别修改为“menuFile”,“menuView”和“menuQuery”,将其Text属性分别修改为“文件”,“视图”,“查询”。
然后添加二级菜单,方法是选择某个菜单项的DropDownItem属性,用类似方法为菜单添加二级项目。如下所示(汉字为Text属性,省略号表示下一级菜单,括号内为Name):
文件(menuFile)
……打开(menuFileOpen)
……添加数据(menuAddData)
……退出(menuExit)
地图浏览(menuView)
……放大(menuZoomIn)
……缩小(menuZoomOut)
……中心放大(menuFixedZoomIn)
……中心缩小(menuFixedZoomOut)
……漫游(menuPan)
……全图显示(menuFullExtent)
查询(menuQuery)
……属性查询(menuAttributeQuery)
……空间查询(menuSpatialQuery)
1.7.3.2 添加工具栏
向主窗体中添加工具条(ToolStrip),点击工具栏属性表中Items项右侧的按钮,弹出如下对话框:

图 44工具栏的项集合编辑器

向其中添加六个按钮,属性设置如下:

表 6工具栏属性设置
图标 Name Text
toolFixedZoomIn 居中放大
toolFixedZoomOut 居中缩小
toolZoomIn 放大
toolZoomOut 缩小
toolPan 漫游
toolFullExtent 全图显示
注意:在添加图片资源文件时,我们通过“项目资源文件”选项,将所需的六个图标添加到项目中,这样它们能够自动加载到Resources文件夹中。如下图所示。

图 45 添加项目资源文件

制作好的工具栏如下图所示:

图 46制作好的工具栏

1.7.3.3 添加状态栏
向窗体中添加一个状态栏(StatusStrip),点击状态栏属性表中Items项右侧的按钮,弹出如下对话框:

图 47状态栏的项集合编辑器

向其中添加3个StatusLabel,属性设置如下:
表 7 状态栏属性设置
名称(Name) Text属性 Spring属性 描述
StatusBlank True 留作空白
StatusScale 比例尺 False 显示当前视图的比例尺
StatusCoordinate 当前坐标 False 显示当前坐标信息
1.7.3.4 添加控件
在工具栏中找到SpliterContainer控件, ,在MainForm窗体中添加两个。第一个用于分隔图层视图(TOCControl)和地图信息,采用默认布局“竖直”左侧为Panel1,右侧为Panel2。第二个SpliterContainer添加到第一个的Panel2中,用于分隔地图视图(MapControl)和地图属性信息,采用“水平拆分器方向”得到的默认布局为竖直布局,,如下图所示:

图 48添加好SpliterContainer控件后的窗体布局

在第二个SpliterContainer的Panel1中添加一个MapControl控件和LicenseControl控件。找到MapControl控件中的Dock属性,点击其中的正方形,将Dock属性设置为Fill,如下图所示:

图 49 MapControl的Dock属性

向第一个SpliterContainer的Panel1中添加一个TOCControl控件,将其Dock属性设为Fill,将其Buddy属性设为当前的axMapControl1,如下图所示。

图 50伙伴控件设置

向第二个SpliterContainer的Panel2中添加DataGridView控件,并将其Dock属性设为Fill。用于显示空间查询得到要素的属性信息。
制作好的界面如下图所示:

图 51制作好的程序界面布局

1.7.4 实现工具类
在这里,我们将自己动手实现放大,缩小,居中放大,居中缩小,漫游和全图显示六个工具类,与上一小结介绍的方法类似,我们这里以“漫游”为例介绍实现的过程,其他类的实现请大家参照前面小结的方法以及示例程序自己完成。
将鼠标移动到解决方案资源管理器,鼠标右键点击Classes文件夹,再点击弹出菜单的“添加”——“新建项”,选择ArcGIS项中的Base Tool,将名字更改为Pan.cs,添加即可。之后在解决方案资源管理器找到Pan.bmp和Pan.cur,其中Pan.bmp是该工具在系统界面上显示的图标样式,Pan.cur是鼠标进行地图操作时的鼠标样式,这里我们的图标样式使用ArcEngine已经封装的图标,将这两个图标删除。
之后我们点击Pan.cs,如下图,对m_caption 、m_toolTip 、m_message和m_name做相应修改。代码如下。

        base.m_category = ""; 
        base.m_caption = "漫游"; 
        base.m_message = "漫游";
        base.m_toolTip = "漫游";
        base.m_name = "Pan";

添加如下引用:

using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Display;

添加两个成员变量:

    //获取视图范围
    private IScreenDisplay m_focusScreenDisplay=null;
    //标记操作进程
    private bool m_PanOperation;

向其中的OnMouseDown函数添加如下代码:

        //判断是否鼠标左键
        if (Button != 1) return;

        //获取视图范围并开始漫游
        IActiveView pActiveView = m_hookHelper.ActiveView;
        m_focusScreenDisplay = pActiveView.ScreenDisplay;
        IPoint pPoint = pActiveView.ScreenDisplay.DisplayTransformation.ToMapPoint(X,Y);
        m_focusScreenDisplay.PanStart(pPoint);
        //标记漫游操作为真
        m_PanOperation = true;

向其中的OnMouseMove函数添加如下代码:

        //判断是否鼠标左键
        if (Button != 1) return;
        //是否漫游状态
        if (!m_PanOperation) return;
        //追踪鼠标
        IPoint pPoint = m_focusScreenDisplay.DisplayTransformation.ToMapPoint(X, Y);
        m_focusScreenDisplay.PanMoveTo(pPoint);

向其中的OnMouseUp函数添加如下代码:

        //判断是否鼠标左键
        if (Button != 1) return;
        //是否漫游状态
        if (!m_PanOperation) return;

        IEnvelope pExtent = m_focusScreenDisplay.PanStop();

        //判断移动区域是否为空
        if (pExtent != null)
        {
            m_focusScreenDisplay.DisplayTransformation.VisibleBounds = pExtent;
            m_focusScreenDisplay.Invalidate(null, true, (short)esriScreenCache.esriAllScreenCaches);
        }
        //关闭漫游状态
        m_PanOperation = false;

这样就完成了Pan.cs类的制作。在下面的程序中,调用这个类,即可完成“漫游”的功能。
按照之前两个小节讲述的做法,完成其余类的制作,具体方法这里不一一列举出,您可以参考提供的例子程序MyGIS,详细查看其中每一个类的制作方法,代码方面的问题如果有疑问,可以参看帮助系统,具体帮助系统的使用方法在第六章中有详细讲述。
1.7.5 实现属性查询
前面我们已经实现过属性查询,但是我们的程序只能查询固定的bou2_4p图层的NAME字段,在这里我们将对程序进行修改完善,实现图层和字段的可选查询。
首先打开“属性查询”窗体的设计器。添加三个Label控件,两个ComboBox,两个Button和一个TextBox。各控件属性设置如下:

表 8控件参数设置
名称(Name) Text属性 描述
lblLayer 选择图层: 标签
lblField 字段名称: 标签
lblFind 查找内容: 标签
cboLayer MapControl中的图层名称
cboField cboLayer选中图层的所有字段名称
txtValue 输入的查询对象名称
btnOk 查找 查询按钮
btnCancel 取消 取消查询按钮

界面效果如下:

图 52 属性查询窗口布局

进入窗体的代码编辑界面,首先添加三个引用:

using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geodatabase;
然后定义两个成员变量,一个用于存储地图数据,一个用于存储当前选中图层,如下

    //地图数据
    private AxMapControl mMapControl;
    //选中图层
    private IFeatureLayer mFeatureLayer;

然后修改其构造函数,构造函数中添加一个参数MapControl,用于获取MapControl中的数据,如下所示:

    public AttributeQueryForm(AxMapControl mapControl)
    {
        InitializeComponent();
        this.mMapControl = mapControl;
    }

在窗体的Load事件中添加代码,用于初始化cboLayer,获取MapControl中的图层名称,如下:

        //MapControl中没有图层时返回
        if (this.mMapControl.LayerCount <= 0)
            return;

        //获取MapControl中的全部图层名称,并加入ComboBox
        //图层
        ILayer pLayer;
        //图层名称
        string strLayerName;
        for (int i = 0; i < this.mMapControl.LayerCount; i++)
        {
            pLayer = this.mMapControl.get_Layer(i);
            strLayerName = pLayer.Name;
            //图层名称加入cboLayer
            this.cboLayer.Items.Add(strLayerName);
        }
        //默认显示第一个选项
        this.cboLayer.SelectedIndex = 0;

在CboLayer的SelectedIndexChanged事件中添加代码,当选中图层发生变化时,cboField中的字段名称重新获取,代码如下:

        //获取cboLayer中选中的图层
        mFeatureLayer = mMapControl.get_Layer(cboLayer.SelectedIndex) as IFeatureLayer;
        IFeatureClass pFeatureClass = mFeatureLayer.FeatureClass;
        //字段名称
        string strFldName;
        for (int i = 0; i < pFeatureClass.Fields.FieldCount;i++ )
        {
            strFldName = pFeatureClass.Fields.get_Field(i).Name;
            //图层名称加入cboField
            this.cboField.Items.Add(strFldName);
        }
        //默认显示第一个选项
        this.cboField.SelectedIndex = 0;
然后按照我们之前所讲的查询属性要素的方法在“查找”按钮的Click事件中添加代码,请你参照前面章节所述方式和示例程序尝试自行完成。这样我们就完成了“属性查询”窗体的设计实现。

1.7.6 实现空间查询
这一小结,我们进一步实现空间查询窗体的设计实现,我们的设想是通过该窗体选择查询的图层和查询的方式,然后将这两个参数传递给主窗体,主窗体实现查询,将查询得到的要素的属性显示在DataGridView控件中,下面开始动手吧。
首先打开“属性查询”窗体的设计器。添加两个Label控件,两个ComboBox,两个Button。各控件属性设置如下:

表 9控件参数设置
名称(Name) Text属性 描述
lblLayer 选择图层: 标签
lblMode 查询方式: 标签
cboLayer MapControl中的图层名称
cboMode 空间查询的方式
btnOk 确定 确定查询按钮
btnCancel 取消 取消查询按钮

界面效果如下:

图 53空间查询窗口布局

进入窗体的代码编辑界面,首先添加三个引用:

using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.Carto;
然后定义两个成员变量,一个用于存储地图数据,一个用于存储当前选中图层,如下

    //获取主界面的MapControl对象
    private AxMapControl mMapControl;
    //查询方式
    public int mQueryMode;
    //图层索引
    public int mLayerIndex;

然后修改其构造函数,构造函数中添加一个参数MapControl,用于获取MapControl中的数据,如下所示:

    public SpatialQueryForm (AxMapControl mapControl)
    {
        InitializeComponent();
        this.mMapControl = mapControl;
    }

在窗体的Load事件中添加代码,用于初始化cboLayer,获取MapControl中的图层名称,并初始化查询方式,代码如下:

        //MapControl中没有图层时返回
        if (this.mMapControl.LayerCount <= 0)
            return;

        //获取MapControl中的全部图层名称,并加入ComboBox
        //图层
        ILayer pLayer;
        //图层名称
        string strLayerName;
        for (int i = 0; i < this.mMapControl.LayerCount; i++)
        {
            pLayer = this.mMapControl.get_Layer(i);
            strLayerName = pLayer.Name;
            //图层名称加入ComboBox
            this.cboLayer.Items.Add(strLayerName);
        }

        //加载查询方式
        this.cboMode.Items.Add("矩形查询");
        this.cboMode.Items.Add("线查询");
        this.cboMode.Items.Add("点查询");
        this.cboMode.Items.Add("圆查询");

        //初始化ComboBox默认值
        this.cboLayer.SelectedIndex = 0;
        this.cboMode.SelectedIndex = 0;

在“确定”按钮添加代码如下:

        //设置鼠标点击时窗体的结果
        this.DialogResult = DialogResult.OK;
        //判断是否存在图层
        if (this.cboLayer.Items.Count <= 0)
        {
            MessageBox.Show("当前MapControl没有添加图层!","提示");
            return;
        }
        //获取选中的查询方式和图层索引
        this.mLayerIndex = this.cboLayer.SelectedIndex;
        this.mQueryMode = this.cboMode.SelectedIndex;

这样我们就完成了空间查询窗体的设计。查询结果的显示我们将放到下一节实现。

1.7.7 主窗体功能实现
至此,程序的框架已经搭建完毕,我们来依次完成每个功能。在这个项目中,我们需要添加ArcEngine中如下的命名空间:

using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.SystemUI;
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.Geodatabase;

我们可以直接添加,也可以在编写代码的过程中,根据需要在帮助文档中查找对应的接口所在的命名空间进行添加。另外,在该项目中,我们在菜单栏的“视图”选项中添加了跟工具栏一样的地图浏览的功能,菜单栏里面的相关功能我们使用我们自己设计的类来进行实现,工具栏中我们采用ArcEngine的封装类来实现。下面开始动手。
首先我们需要定义一个成员变量,用于标记当前选中的工具类型。

    private string mTool;

1.7.7.1 文件操作实现
菜单的“文件”操作,包含“打开mxd”、“添加数据”和“退出”三个选项。我们依次实现。

  1. 打开mxd。单击菜单控件上的“文件”选项,并选择二级菜单中的“打开mxd”,双击“打开mxd”,进入代码编写界面。向其中添加如下代码:

        //文件路径名称,包含文件名称和路径名称
        string strName = null;
    
        //定义OpenFileDialog,获取并打开地图文档
        OpenFileDialog openFileDialog = new OpenFileDialog();
        openFileDialog.Title = "打开MXD";
        openFileDialog.Filter = "MXD文件(*.mxd)|*.mxd";
        if (openFileDialog.ShowDialog() == DialogResult.OK)
        {
            strName = openFileDialog.FileName;
            if (strName != "")
            {
                this.axMapControl1.LoadMxFile(strName);
            }
        }
        //地图文档全图显示
        this.axMapControl1.Extent = this.axMapControl1.FullExtent;
    
  2. 添加数据。我们采用ArcEngine的封装类实现。单击菜单控件上的“文件”选项,并选择二级菜单中的“添加数据”,双击“添加数据”,进入代码编写界面。代码如下:

        ICommand cmd = new ControlsAddDataCommandClass();
        cmd.OnCreate(this.axMapControl1.Object);
        cmd.OnClick();
    
  3. 退出。双击菜单中“退出”选项,添加代码如下:

        this.Dispose();
    

1.7.7.2 图层控制实现
在视图操作过程中,需要对图层的显示顺序和可见性进行控制,这里主要通过axTOCControl控件来进行实现,在该控件的属性中有Enable Layer Drag and Drop的复选框,如下图所示:

图 54图层拖拽控制设置
将该复选框选中,即可以在该控件中对图层进行拖拽来调整图层的位置,如图所示:

图 55图层顺序调整前 图 56图层顺序调整后
1.7.7.3 视图操作实现
视图操作中,居中放大、居中缩小和全图显示是Command类型的功能,用户点击该功能后,地图视图直接进行响应,无需鼠标和地图的交互。这类操作的实现方法比较简单,我们以全图显示为例,其他两个大家自行完成。

首先我们需要在代码编辑界面添加我们自定义程序集的引用。

using MyGIS.Classes;

点击菜单中“视图”“全图显示”按钮,进入代码编辑界面,添加代码如下:

        //初始化FullExtent对象
        FullExtent fullExtent = new FullExtent();
        //FullExtent对象与MapControl关联
        fullExtent.OnCreate(this.axMapControl1.Object);
        fullExtent.OnClick();

放大,缩小和漫游功能是Tool类型功能,用户点击该类型功能按钮相当于开启一定类型的操作,然后通过鼠标与MapControl的交互来完成这个功能。我们以“漫游”的实现为例,其他两个大家自行完成。
首先我们需要将ZoomIn定义为一个成员变量,方便不同事件响应中的参数传递。

    private ZoomIn mZoomIn;

点击菜单中“视图”“全图显示”按钮,进入代码编辑界面,添加代码如下:

        //初始化Pan对象
        mPan = new Pan();
        //Pan对象与MapControl关联
        mPan.OnCreate(this.axMapControl1.Object);
        //设置鼠标形状
        this.axMapControl1.MousePointer = esriControlsMousePointer.esriPointerPan;
        //标记操作为“Pan”
        this.mTool = "Pan";

然后双击MapControl进入到MapControl的OnMouseDown事件,因为多个工具操作均需要该事件的响应,我们采用switch…case…语句判断工具的类型,添加代码如下:

        switch (mTool)
        {
            case "ZoomIn":
                break;
            case "ZoomOut":
                break;
            case "Pan":
                //设置鼠标形状
                this.axMapControl1.MousePointer = esriControlsMousePointer.esriPointerPanning;
                this.mPan.OnMouseDown(e.button, e.shift, e.x, e.y);
                break;
            case "SpaceQuery":
                break;
            default:
                break;
        }

这里我们仅添加了Pan的代码,后面陆续添加其他功能的代码。
同样,在MapControl的OnMouseMove事件中添加代码如下:

        switch (mTool)
        {
            case "ZoomIn":
                break;
            case "ZoomOut":
                break;
            case "Pan":
                this.mPan.OnMouseMove(e.button, e.shift, e.x, e.y);
                break;
            default:
                break;
        }

MapControl的OnMouseUp事件中添加代码如下:

        switch (mTool)
        {
            case "ZoomIn":
                break;
            case "ZoomOut":
                break;
            case "Pan":
                this.mPan.OnMouseUp(e.button, e.shift, e.x, e.y);
                //设置鼠标形状
                this.axMapControl1.MousePointer = esriControlsMousePointer.esriPointerPan;
                break;
            default:
                break;
        }

至此,我们完成了漫游功能的实现。

1.7.7.4 查询操作实现
查询操作包含属性查询和空间查询两个部分。我们需要在MainForm中添加我们自定义的窗体的命名空间。

using MyGIS.Forms;

  1. 属性查询
    属性查询功能的实现已经在属性查询窗体中进行了编写,这里创建属性查询窗体并与当前的MapControl关联即可。双击菜单中“属性查询”选项,添加代码如下:

        //初始化属性查询窗体
        AttributeQueryForm attributeQueryForm = new AttributeQueryForm(this.axMapControl1);
    attributeQueryForm.Show();
    

运行程序,通过“打开mxd”添加数据Chian.mxd,点击属性查询,图层选择“bou2_4p”,选择字段“NAME”,输入“湖北省”,点击查询,效果如下:

图 57属性查询效果

  1. 空间查询
    由于空间查询的结果需要借助于DataGridView进行显示,我们首先需要添加一个方法LoadQueryResult(AxMapControl mapControl, IFeatureLayer featureLayer, IGeometry geometry),用于获取空间查询得到的要素的属性。在这个方法的参数中,IGeometry是用于空间查询的几何对象,IFeatureLayer是查询要素所在的要素图层,AxMapControl是当前MapControl。我们使用DataTable来存储要素的属性,然后将DataTable中的数据添加到DataGridView进行显示。在这个方法实现过程中,首先利用IFeatureClass的属性字段初始化DataTable,然后利用IGeometry对IFeatureLayer图层进行空间查询返回到要素游标IFeatureCursor中,然后逐个变量要素,将值添加到DataTable。代码如下:

    private DataTable LoadQueryResult(AxMapControl mapControl, IFeatureLayer featureLayer, IGeometry geometry)
    {
        IFeatureClass pFeatureClass = featureLayer.FeatureClass;
    
        //根据图层属性字段初始化DataTable
        IFields pFields = pFeatureClass.Fields;
        DataTable pDataTable = new DataTable();
        for (int i = 0; i < pFields.FieldCount; i++)
        {
            string strFldName;
            strFldName = pFields.get_Field(i).AliasName;
            pDataTable.Columns.Add(strFldName);
        }
    
        //空间过滤器
        ISpatialFilter pSpatialFilter = new SpatialFilterClass();
        pSpatialFilter.Geometry = geometry;
    
        //根据图层类型选择缓冲方式
        switch (pFeatureClass.ShapeType)
        {
            case esriGeometryType.esriGeometryPoint:
                pSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelContains;
                break;
            case esriGeometryType.esriGeometryPolyline:
                pSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelCrosses;
                break;
            case esriGeometryType.esriGeometryPolygon:
                pSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects;
                break;
        }
        //定义空间过滤器的空间字段
        pSpatialFilter.GeometryField = pFeatureClass.ShapeFieldName;
    
        IQueryFilter pQueryFilter;
        IFeatureCursor pFeatureCursor;
        IFeature pFeature;
        //利用要素过滤器查询要素
        pQueryFilter = pSpatialFilter as IQueryFilter;
        pFeatureCursor = featureLayer.Search(pQueryFilter, true);
        pFeature = pFeatureCursor.NextFeature();
    
        while (pFeature != null)
        {
            string strFldValue = null;
            DataRow dr = pDataTable.NewRow();
            //遍历图层属性表字段值,并加入pDataTable
            for (int i = 0; i < pFields.FieldCount; i++)
            {
                string strFldName = pFields.get_Field(i).Name;
                if (strFldName == "Shape")
                {
                    strFldValue = Convert.ToString(pFeature.Shape.GeometryType);
                }
                else
                    strFldValue = Convert.ToString(pFeature.get_Value(i));
                dr[i] = strFldValue;
            }
            pDataTable.Rows.Add(dr);
            //高亮选择要素
            mapControl.Map.SelectFeature((ILayer)featureLayer, pFeature);
            mapControl.ActiveView.Refresh();
            pFeature = pFeatureCursor.NextFeature();
        }
        return pDataTable;
    

}

定义两个成员变量,分别用于标记空间查询的查询方式和选中图层的索引(Index)。

    //空间查询的查询方式
    private int mQueryMode;
    //图层索引
    private int mLayerIndex;

然后单击菜单中的“查询”选项,选择“空间查询”,双击进入代码编辑界面,添加代码如下:

        //初始化空间查询窗体
        SpatialQueryForm spatialQueryForm = new SpatialQueryForm(this.axMapControl1);
        if (spatialQueryForm.ShowDialog() == DialogResult.OK)
        {
            //标记为“空间查询”
            this.mTool = "SpaceQuery";
            //获取查询方式和图层
            this.mQueryMode = spatialQueryForm.mQueryMode;
            this.mLayerIndex = spatialQueryForm.mLayerIndex;
            //定义鼠标形状
            this.axMapControl1.MousePointer = ESRI.ArcGIS.Controls.esriControlsMousePointer.esriPointerCrosshair;
        }

然后进入MapControl的OnMouseDown事件,添加代码如下:

        this.axMapControl1.Map.ClearSelection();
        //获取当前视图
        IActiveView pActiveView = this.axMapControl1.ActiveView;
        //获取鼠标点
        IPoint pPoint = pActiveView.ScreenDisplay.DisplayTransformation.ToMapPoint(e.x, e.y);
        switch (mTool)
        {
            case "ZoomIn":
                this.mZoomIn.OnMouseDown(e.button, e.shift, e.x, e.y);
                break;
            case "ZoomOut":
                this.mZoomOut.OnMouseDown(e.button, e.shift, e.x, e.y);
                break;
            case "Pan":
                //设置鼠标形状
                this.axMapControl1.MousePointer = esriControlsMousePointer.esriPointerPanning;
                this.mPan.OnMouseDown(e.button, e.shift, e.x, e.y);
                break;
            case "SpaceQuery":
                IGeometry pGeometry = null;
                if (this.mQueryMode == 0)//矩形查询
                {
                    pGeometry = this.axMapControl1.TrackRectangle();
                }
                else if (this.mQueryMode == 1)//线查询
                {
                    pGeometry = this.axMapControl1.TrackLine();
                }
                else if (this.mQueryMode == 2)//点查询
                {
                    ITopologicalOperator pTopo;
                    IGeometry pBuffer;
                    pGeometry=pPoint;
                    pTopo = pGeometry as ITopologicalOperator;
                    //根据点位创建缓冲区,缓冲半径为0.1,可修改
                    pBuffer = pTopo.Buffer(0.1);
                    pGeometry = pBuffer.Envelope;
                }
                else if (this.mQueryMode == 3)//圆查询
                {
                    pGeometry = this.axMapControl1.TrackCircle();
                }
                IFeatureLayer pFeatureLayer=this.axMapControl1.get_Layer(this.mLayerIndex) as IFeatureLayer;
                DataTable pDataTable = this.LoadQueryResult(this.axMapControl1,pFeatureLayer, pGeometry);
                this.dataGridView1.DataSource = pDataTable.DefaultView;

                this.dataGridView1.Refresh();
                break;
            default:
                break;
        }
至此,我们完成了空间查询代码的编写,运行程序,通过“打开mxd”添加数据China.mxd,点击属性查询,图层选择“bou2_4p”,查询方式选择“矩形查询”,点击“确定”,在MapControl用鼠标进行拉框,我们可以看到选中的要素高亮显示,对应的属性在下面属性表中显示。效果如下图。

图 58空间查询效果

1.7.7.5 工具栏和状态栏的实现

  1. 工具栏的实现
    工具栏的实现我们采用ArcEngine的封装类来实现,Tool类型和Command类型的实现方式略有不同。Command类型的实现,在实例化对象后,对象通过OnCreate()方法与MapControl建立关联,然后调用OnClick()函数即可。以“全图显示”为例,双击工具栏上“全图显示”的对应按钮,进入到代码编辑界面,添加代码如下:

        //初始化FullExtent对象
        ICommand cmd = new ControlsMapFullExtentCommandClass();
        //FullExtent与MapControl的关联
        cmd.OnCreate(this.axMapControl1.Object);
        cmd.OnClick();
    

Tool类型的实现过程中,需要通过ICommand实现与MapControl的关联,Tool初始化完成后,通过查询接口的方式获取Command的类型,利用Command的OnCreate()函数实现与MapControl的关联,然后将当前MapControl的CurrentTool设为该工具即可。以“漫游”的实现为例,双击工具栏“漫游”按钮,进入代码编辑界面。添加代码如下:

        //初始化Pan对象
        ITool tool = new ControlsMapPanToolClass();
        //查询接口,初试化Command类型
        ICommand cmd = tool as ICommand;
        //Command与MapControl的关联
        cmd.OnCreate(this.axMapControl1.Object);
        cmd.OnClick();
        //当前MapControl的工具设为Pan
        this.axMapControl1.CurrentTool = tool;
  1. 状态栏的实现
    状态栏主要用于显示MapControl当前视图的比例尺和鼠标当前位置的坐标信息。在MapControl的OnMouseMove事件中添加代码如下:

        // 显示当前比例尺
        this.statusScale.Text = " 比例尺 1:" + ((long)this.axMapControl1.MapScale).ToString();
        // 显示当前坐标
        this.statueCoordinate.Text = " 当前坐标 X = " + e.mapX.ToString() + " Y = " + e.mapY.ToString() + " " + this.axMapControl1.MapUnits;
    

1.7.8 小结
在这一小节中,我们将前几节的知识整合起来,完成了一个小型GIS应用程序的制作。这个程序能够完成地图载入,地图浏览以及基本图层控制和查询功能。地图的载入和地图浏览在前面几节已经进行了讲解,属性查询和空间查询我们进行了改进。
本节在实现Tool类型的功能时,将Tool工具重写的三个函数OnMouseDown,OnMouseMove和OnMouseUp分别在MapControl中添加响应。在实际的继承BaseTool进行开发的过程中,我们在完成Tool类型的初试化后,可以使用MapControl的CurrentTool方法设定为当前工具即可,我们在这里采用了比较繁琐的方法是为了给您展示一个功能的完整的实现过程,清楚实现的来龙去脉。以“漫游”为例,我们可以在Pan的Click事件中添加代码如下:

        //初始化Pan对象
        mPan = new Pan();
        //Pan对象与MapControl关联
        mPan.OnCreate(this.axMapControl1.Object);
        //设置鼠标形状
        this.axMapControl1.MousePointer = esriControlsMousePointer.esriPointerPan;
        this.axMapControl1.CurrentTool = mPan;

推荐同学们认真分析本节的实现过程和代码,如果需要获得进一步的信息,请查看帮助系统。

  • 22
    点赞
  • 123
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值