ArcGIS Engine10.0轻松入门级教程(2)——创建一个ArcGIS Engine桌面应用程序

 这个例子将引导您创建第一个简单的地图显示程序,并添加基本的缩放和漫游功能。如果您之前没有接触过ArcGIS Engine的开发,那么这个例子是您迈入ArcGIS Engine二次开发大门的极好例子,如果您之前没有接触过C#.NET,也无需担心,这个例子将从零开始引导您一步一步完成任务。

1创建一个新的工程

       首先打开Microsoft Visual Studio 2008,点击菜单栏中的文件”—>“新建”—>“项目,在弹出的对话框中选择新建一个Visual C#Windows应用程序,之后更改项目名称为“ZZUMap”,更改文件的路径为个人实习文件夹,点击确定即可。

       选中项目“ZZUMap”中的窗体“Form1”,修改其Name属性为“MainForm”,Text属性为“ZZUMap”。

2添加控件

      选择工具箱中的“菜单和工具栏|MenuStrip”,将其拖入窗体。
     选择工具箱中的“ArcGIS Windows Forms”节,将“ToolbarControl”控件拖入窗体,并将其属性中的Dock设置为Top。
     选择工具箱中的“菜单和工具栏|StatusStrip”,将其拖入到窗体。
     选择工具箱中的“容器|SplitContainer”容器拖入窗体,并将其属性中的Dock设置为Fill。

     再将“容器|SplitContainer”容器拖入Panel1,设置为水平拆分方向,并将其属性中的Dock设置为Fill。

     将TabControl控件拖入Panel1,将Alignment属性设置为Bottom,Dock属性设置为Fill。点击TabPages属性右边的按钮,弹出TabPage集合编辑器,将tabPage1的Name设置为tabPageLayer,Text设置为图层,将tabPage2的Name设置为tabPageProperty,Text设置为属性。

     选择“图层”选项卡,拖入TOCControl控件,设置Dock属性为Fill。
     选择“属性”选项卡,拖入PropertyGrid设置Dock属性为Fill。
     拖入TabControl控件到第一个SplitContainer容器的Panel2,设置Dock属性为Fill。并上述类似的方法,将两个选项卡的Name和Text分别设置为:(tabPageMap、地图),(tabPageLayout,制版)。
     选择“地图”选项卡,拖入MapControl控件,设置Dock属性为Fill。
     选择“制版”选项卡,拖入PageLayoutControl控件,设置Dock属性为Fill。

     将MapControl控件拖入第二个SplitContainer容器的Panel2,设置Dock属性为Fill。
     最后将LicenseControl控件拖入到窗体的任意地方。
    按F5编译运行还不能运行,因就很可能是你的ArcEngine10.0没有许可,因为我的就是这个情况,所以会报错,重新授权下就OK了,解决方法:打开program.cs

    把ESRI.ArcGIS.RuntimeManager.Bind(ESRI.ArcGIS.ProductCode.EngineOrDesktop);

    这句放到Application.SetCompatibleTextRenderingDefault(false);和Application.Run(new Form1());之间

3控件绑定

       通过以上步骤添加的控件还只是单独存在,而我们的程序需要各控件间协同工作,因此要进行控件绑定。
分别右击ToolbarControl、TOCControl控件,将Buddy设置为axMapControl1,如下图所示。
 


      这样,工具条和图层控件就与地图控件关联了。

4添加工具

     此时,工具条中还没有任何工具,添加的方法也很简单。右击ToolbarControl,选择“属性|Items”,点击Add,选择Commands选项卡中的Generic,双击Open、SaveAs、Redo、Undo即可将相应工具添加到工具条。
     常见的工具有:
     Map Navigation中的导航工具,Map Inquiry中的查询工具,Feature Selection中的选择工具,你可以根据需要酌情添加工具,如下图。

      目前为止运行程序界面如下:

5添加菜单

       在设计视图中,单击菜单栏,会出现“请在此处键入”的提示,单击提示就可以键入菜单名称,如“文件”,再单击“文件”,即可输入其下拉子菜单。           

       每创建一个菜单,请在其属性面板中设置 Name 属性,而且不要为中文,因此 Name 值将是此菜单响应函数的函数名的一部分,带中文的函数名,总是不好吧。我们将添加新建( New )、打开( Open )、添加数据( AddData )、保存( Save )、另存为( SaveAs )、退出( Exit )这些菜单,()内为相应的 Name 属性值。

      你可以在属性面板中的 Text 属性中,把菜单名设置为中英文形式,如“打开 O pen ”,带下划线的 O 表示此项菜单的快捷键是字母 O ,设置方法是在相应字母前加上“ & ”字符,如“打开 &Open ”。但这种快捷键只在打开此下拉菜单时才有效,即当你单击“文件”菜单弹出下拉菜单时,按下字母 O 就可以定位到“打开”菜单。

      还有一种在程序运行时都有效的全局快捷键,可以在属性面板中的“ ShortCutKeys ”中设置。

      你还可以在属性面板中的 Image 属性中设置你喜欢的菜单图标。单击 Image 那一行右边的按钮,弹出如下菜单。选择“项目资源文件”,再单击导入就可以选择你的图标了。

       最终效果如下所示。


       注意,在解决方案面板中,选中刚才添加的所有图标,在其属性面板中将生成操作设置为“嵌入的资源”,这一点很重要!菜单的实现参考ArcEngine自带的模板MapControl Application中的菜单实现。先新建一个类,命名为CreateNewDocument.cs,添加引用:

       using ESRI.ArcGIS.ADF.BaseClasses;
       using ESRI.ArcGIS.ADF.CATIDs;
       using ESRI.ArcGIS.Controls;
       using ESRI.ArcGIS.Carto;
      using ESRI.ArcGIS.SystemUI;
      using System.Windows.Forms;

      在构造函数中继承类:

       public class CreateNewDocument : BaseCommand;

       {

                           ……

       }

       然后将以下代码复制覆盖到类CreateNewDocument中

        private IHookHelper m_hookHelper = null;

        //constructor
        public CreateNewDocument()
        {
            //update the base properties
            base.m_category = ".NET Application";
            base.m_caption = "NewDocument";
            base.m_message = "Create a new map";
            base.m_toolTip = "Create a new map";
            base.m_name = "DotNetTemplate_NewDocumentCommand";
        }

        #region Overridden Class Methods

        /// <summary>
        /// Occurs when this command is created
        /// </summary>
        /// <param name="hook">Instance of the application</param>
        public override void OnCreate(object hook)
        {
            if (m_hookHelper == null)
                m_hookHelper = new HookHelperClass();

            m_hookHelper.Hook = hook;
        }

        /// <summary>
        /// Occurs when this command is clicked
        /// </summary>
        public override void OnClick()
        {
            IMapControl3 mapControl = null;

            //get the MapControl from the hook in case the container is a ToolbarControl
            if (m_hookHelper.Hook is IToolbarControl)
            {
                mapControl = (IMapControl3)((IToolbarControl)m_hookHelper.Hook).Buddy;
            }
            //In case the container is MapControl
            else if (m_hookHelper.Hook is IMapControl3)
            {
                mapControl = (IMapControl3)m_hookHelper.Hook;
            }
            else
            {
                MessageBox.Show("Active control must be MapControl!", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }

            //check to see if there is an active edit session and whether edits have been made
            DialogResult result;
            IEngineEditor engineEditor = new EngineEditorClass();

            if ((engineEditor.EditState == esriEngineEditState.esriEngineStateEditing) && (engineEditor.HasEdits() == true))
            {
                result = MessageBox.Show("Would you like to save your edits", "Save Edits", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);

                switch (result)
                {

                    case DialogResult.Cancel:
                        return;

                    case DialogResult.No:
                        engineEditor.StopEditing(false);
                        break;

                    case DialogResult.Yes:
                        engineEditor.StopEditing(true);
                        break;

                }
            }

            //allow the user to save the current document
            DialogResult res = MessageBox.Show("Would you like to save the current document?", "AoView", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if (res == DialogResult.Yes)
            {
                //launch the save command
                ICommand command = new ControlsSaveAsDocCommandClass();
                command.OnCreate(m_hookHelper.Hook);
                command.OnClick();
            }

            //create a new Map
            IMap map = new MapClass();
            map.Name = "Map";

            //assign the new map to the MapControl
            mapControl.DocumentFilename = string.Empty;
            mapControl.Map = map;
        }

        #endregion

      然后定义指针和变量:

         privEmpty; ate ESRI.ArcGIS.Controls.IMapControl4 m_mapControl = null; 
      private ESRI.ArcGIS.Controls.IPageLayoutControl3 m_pageLayoutControl = null; 
      private string m_mapDocumentName = string.      

      最后在菜单按钮的事件中填入以下代码:

       private void AddData_Click(object sender, EventArgs e)
        {
            int currentLayerCount = this.axMapControl1.LayerCount;

            ICommand pCommand = new ControlsAddDataCommandClass();

            pCommand.OnCreate(this.axMapControl1.Object);

            pCommand.OnClick();

        }

        private void Save_Click(object sender, EventArgs e)
        {
            //execute Save Document command
            if (m_mapControl.CheckMxFile(m_mapDocumentName))
            {
                //create a new instance of a MapDocument
                IMapDocument mapDoc = new MapDocumentClass();
                mapDoc.Open(m_mapDocumentName, string.Empty);

                //Make sure that the MapDocument is not readonly
                if (mapDoc.get_IsReadOnly(m_mapDocumentName))
                {
                    MessageBox.Show("Map document is read only!");
                    mapDoc.Close();
                    return;
                }

            }

        }

        private void SavaAs_Click(object sender, EventArgs e)
        {
            //execute SaveAs Document command
            ICommand command = new ControlsSaveAsDocCommandClass();
            command.OnCreate(m_mapControl.Object);
            command.OnClick();

        }

        private void Open_Click(object sender, EventArgs e)
        {
            //execute Open Document command
            ICommand command = new ControlsOpenDocCommandClass();
            command.OnCreate(m_mapControl.Object);
            command.OnClick();
        }

        private void Exit_Click(object sender, EventArgs e)
        {
            //exit the application
            Application.Exit();
        }

        private void New_Click(object sender, EventArgs e)
        {
            //execute New Document command
            ICommand command = new CreateNewDocument();
            command.OnCreate(m_mapControl.Object);
            command.OnClick();

        }

6PageLayout与MapControl联动

        ArcMap程序中,数据视图和布局视图中的数据改变是实时互动的,那是因为它们本来就是在处理同一份数据。将axMapControl1中的Map拷贝到axPageLayoutControl1代码如下:

private voidCopyAndOverwriteMap()

{

//Get IObjectCopy interface

IObjectCopy objectCopy =newObjectCopyClass();

//Get IUnknown interface (map to copy)

object toCopyMap = axMapControl1.Map;

//Get IUnknown interface (copied map)

object copiedMap = objectCopy.Copy(toCopyMap);

//Get IUnknown interface (map to overwrite)

object toOverwriteMap = axPageLayoutControl1.ActiveView.FocusMap;

//Overwrite the PageLayoutControl's map

objectCopy.Overwrite(copiedMap,reftoOverwriteMap);

}

要实现axPageLayoutControl1中的数据与axMapControl1中的数据同步,需要在axMapControl1的以下事件中写入相应的代码:

private voidaxMapControl1_OnMapReplaced(objectsender,

ESRI.ArcGIS.MapControl.IMapControlEvents2_OnMapReplacedEvent e)

{

       CopyAndOverwriteMap();

}

private voidaxMapControl1_OnAfterScreenDraw(objectsender,

ESRI.ArcGIS.MapControl.IMapControlEvents2_OnAfterScreenDrawEvent e)

{

//Get IActiveView interface

IActiveView activeView = (IActiveView)

axPageLayoutControl1.ActiveView.FocusMap;

//Get IDisplayTransformation interface

IDisplayTransformation displayTransformation =

activeView.ScreenDisplay.DisplayTransformation;

//Set the visible extent of the focus map

displayTransformation.VisibleBounds = axMapControl1.Extent;                    

axPageLayoutControl1.ActiveView.Refresh(); //根据MapControl的视图范围,确定PageLayoutControl的视图范围

CopyAndOverwriteMap();

}

private voidaxMapControl1_OnViewRefreshed(objectsender,

ESRI.ArcGIS.MapControl.IMapControlEvents2_OnViewRefreshedEvent e)

{

         //axTOCControl1.Update ();

        CopyAndOverwriteMap();

}

         运行但是axTOCControl控件图层不显示,这是因为还要设置SetBuddyControl,ToolBarControl控件中同样要设置,只需要在在主窗口Load函数中添加下面一句话就可以了

        private void MainForm_Load(object sender, EventArgs e)
        {
            axTOCControl1.SetBuddyControl(axMapControl1);//ADD
            axToolbarControl1.SetBuddyControl(m_mapControl);
        }  

7鹰眼的实现

         鹰眼用来显示主窗体当前视图范围在全景视图中的位置,在ArcMap中使用一个线框在鹰眼视图中标识。当主视图中的视图范围改变时,鹰眼中的线框随之改变,当拖动鹰眼视图中的红线框时,主视图中的视图范围也随之改变。

下面开始实现鹰眼功能,添加using ESRI.ArcGIS.Carto、using ESRI.ArcGIS.Geometry、

using ESRI.ArcGIS.Display三个引用。首先在axMapControl1中视图范围改变时鹰眼窗体要做出对应的响应,即绘制线框并显示,在OnExtentUpdated事件中添加代码如下:

         private void axMapControl1_OnExtentUpdated       (object sender, ESRI.ArcGIS.Controls.IMapControlEvents2_OnExtentUpdatedEvent e)

        {

            //创建鹰眼中线框

            IEnvelope pEnv = (IEnvelope)e.newEnvelope;

            IRectangleElement pRectangleEle = new RectangleElementClass();

            IElement pEle = pRectangleEle as IElement;

            pEle.Geometry = pEnv;

            //设置线框的边线对象,包括颜色和线宽

            IRgbColor pColor = new RgbColorClass();

            pColor.Red = 255;

            pColor.Green = 0;

            pColor.Blue = 0;

            pColor.Transparency = 255;

            // 产生一个线符号对象 

            ILineSymbol pOutline = new SimpleLineSymbolClass();

            pOutline.Width = 2;

            pOutline.Color = pColor;

            // 设置颜色属性 

            pColor.Red = 255;

            pColor.Green = 0;

            pColor.Blue = 0;

            pColor.Transparency = 0;

            // 设置线框填充符号的属性 

            IFillSymbol pFillSymbol = new SimpleFillSymbolClass();

            pFillSymbol.Color = pColor;

            pFillSymbol.Outline = pOutline;

            IFillShapeElement pFillShapeEle = pEle as IFillShapeElement;

            pFillShapeEle.Symbol = pFillSymbol;

            // 得到鹰眼视图中的图形元素容器

            IGraphicsContainer pGra = axMapControl2.Map as IGraphicsContainer;

            IActiveView pAv = pGra as IActiveView;

            // 在绘制前,清除 axMapControl2 中的任何图形元素 

            pGra.DeleteAllElements();

            // 鹰眼视图中添加线框

            pGra.AddElement((IElement)pFillShapeEle, 0);

            // 刷新鹰眼

            pAv.PartialRefresh(esriViewDrawPhase.esriViewGraphics, null, null);    

       }

       当鼠标点击鹰眼窗体时,主窗体Extent随之改变。在axMapControl2OnMouseDown事件中添加代码如下:

        private void axMapControl2_OnMouseDown(object sender, ESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseDownEvent e)

        {

            if (this.axMapControl2.Map.LayerCount != 0)

            {

                // 按下鼠标左键移动矩形框 

                if (e.button == 1)

                {

                    IPoint pPoint = new PointClass();

                    pPoint.PutCoords(e.mapX, e.mapY);

                    IEnvelope pEnvelope = this.axMapControl1.Extent;

                    pEnvelope.CenterAt(pPoint);

                    this.axMapControl1.Extent = pEnvelope;

                    this.axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);

                }

                // 按下鼠标右键绘制矩形框 

                else if (e.button == 2)

                {

                    IEnvelope pEnvelop = this.axMapControl2.TrackRectangle();

                    this.axMapControl1.Extent = pEnvelop;

                    this.axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);

                }

            }

        }

       当鼠标在鹰眼窗体移动时,主窗体Extent随之改变。在axMapControl2OnMouseMove事件中添加代码如下:

        private void axMapControl2_OnMouseMove(object sender, ESRI.ArcGIS.Controls.IMapControlEvents2_OnMouseMoveEvent e)

        {

            // 如果不是左键按下就直接返回 

            if (e.button != 1) return;

            IPoint pPoint = new PointClass();

            pPoint.PutCoords(e.mapX, e.mapY);

            this.axMapControl1.CenterAt(pPoint);

            this.axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);

        }

       下面代码用于实现axMapControl2axMapControl1的数据的同步更新,获取主视图中视图范围最大的图层作为鹰眼中的视图。这个更新由两部分组成,一个是对axMapControl1添加地图文档(mxd文件)的响应,通过axMapControl1OnMapReplace事件实现,一个是对axMapControl1添加单个图层的响应,通过axMapControl1OnFullExtentUpdated事件实现。我们获取主视图中的视图范围最大的图层写成一个独立的函数,方便调用。

        private ILayer GetOverviewLayer(IMap map)

        {

            //获取主视图的第一个图层

            ILayer pLayer = map.get_Layer(0);

            //遍历其他图层,并比较视图范围的宽度,返回宽度最大的图层

            ILayer pTempLayer = null;

            for (int i = 1; i < map.LayerCount;i++ )

            {

                pTempLayer = map.get_Layer(i);

                if (pLayer.AreaOfInterest.Width < pTempLayer.AreaOfInterest.Width)

                    pLayer = pTempLayer;

            }

            return pLayer;

        }

        然后在axMapControl1OnMapReplaced事件中调用。

        private void axMapControl1_OnMapReplaced(object sender, IMapControlEvents2_OnMapReplacedEvent e)

        {

            //获取鹰眼图层

            this.axMapControl2.AddLayer(this.GetOverviewLayer(this.axMapControl1.Map));

            // 设置 MapControl 显示范围至数据的全局范围

            this.axMapControl2.Extent = this.axMapControl1.FullExtent;

            // 刷新鹰眼控件地图

            this.axMapControl2.Refresh();

        }

        在axMapControl1OnFullExtentUpdated添加代码,用于实现在主视图添加图层时,实现对鹰眼视图的更新。代码如下:

        private void axMapControl1_OnFullExtentUpdated(object sender, ESRI.ArcGIS.Controls.IMapControlEvents2_OnFullExtentUpdatedEvent e)

        {

            //获取鹰眼图层

            this.axMapControl2.AddLayer(this.GetOverviewLayer(this.axMapControl1.Map));

            // 设置 MapControl 显示范围至数据的全局范围

            this.axMapControl2.Extent = this.axMapControl1.FullExtent;

            // 刷新鹰眼控件地图

            this.axMapControl2.Refresh();

this.axMapControl2.Map = new MapClass();

            // 添加主地图控件中的所有图层到鹰眼控件中 

            for (int i = 1; i <= this.axMapControl1.LayerCount; i++)

            {

                this.axMapControl2.AddLayer(this.axMapControl1.get_Layer(this.axMapControl1.LayerCount -

                i));

            }

            // 设置 MapControl 显示范围至数据的全局范围 

            this.axMapControl2.Extent = this.axMapControl1.FullExtent;

            // 刷新鹰眼控件地图 

            this.axMapControl2.Refresh(); 

      }

     运行程序,添加地图数据,可以在主视图进行相关操作,鹰眼视图同步响应,在鹰眼视图可以移动红线框可以同步更新主视图的视图范围,在鹰眼视图单击右键拉框可以重新绘制红线框,效果如下:

8状态栏的实现

        应用程序的状态栏一般用来显示程序的当前状态,当前所使用的工具。 GIS 应用程序一般也在状态栏显示当前光标的坐标、比例尺等信息。学习完本讲内容,您将学会状态栏编程的基本方法,并且能够在我们的程序的状态栏中添加且显示以下信息:

       当前所用工具信息 
       当前比例尺 
      当前坐标 
      在设计视图中,点击窗体中的状态栏,在其属性面板中找到“ Items ”项,单击其右边的按钮,在下拉框中选择“ StatusLabel ”,单击“添加按钮”,依次添加四个 StatusLabel ,依次修改属性参数如下表所示:

       序号 
       Name 属性 
      Text 属性 
      Spring 属性 
     说明 
 
      1 
       MessageLabel 
       就绪 
       False 
      当前所用工具信息 
 
       2 
      Blank 
      True 
     占位 
 
     3 
     ScaleLabel 
      比例尺 
      False 
     当前比例尺 
 
     4 
     CoordinateLabel 
     当前坐标 
     False 
    当前坐标 
   设置好之后如下图所示:

 

        Spring 属性表示可以按状态栏剩余空间自动伸缩。所以加入 Blank 项目,只是为了占个位子,以达到 ScaleLabel 和 CoordinateLabel 项目右对齐而 MessageLabel 项目左对齐的目的。

         首先添加 axToolbarControl1 的 OnMouseMove 事件 ( 相信大家看了以上的教程,已经知道怎么添加事件了吧,还不知道的建议再温习下前几讲的内容 ) 。在其事件响应函数代码如下:

 private void axToolbarControl1_OnMouseMove(object sender, IToolbarControlEvents_OnMouseMoveEvent e)
{

// 取得鼠标所在工具的索引号

int index = axToolbarControl1.HitTest(e.x, e.y, false);

if (index != -1)

{

// 取得鼠标所在工具的 ToolbarItem

IToolbarItem toolbarItem = axToolbarControl1.GetItem(index);

// 设置状态栏信息

MessageLabel.Text = toolbarItem.Command.Message;

      }

      else

      {

           MessageLabel.Text = " 就绪 ";

      }


添加 axMapControl1 的 OnMouseMove 事件,其代码如下:

        private void axMapControl1_OnMouseMove(object sender, IMapControlEvents2_OnMouseMoveEvent e)
       {

               // 显示当前比例尺

              ScaleLabel.Text = " 比例尺 1:" + ((long)this.axMapControl1.MapScale).ToString();

       } 
       显示当前坐标也是 axMapControl1 的 OnMouseMove 事件中响应,故只要在 axMapControl1_OnMouseMove 函数中添加如下代码即可:

         // 显示当前坐标 
        CoordinateLabel.Text = " 当前坐标 X = " + e.mapX.ToString() + " Y = " + e.mapY.ToString() + " " + this.axMapControl1.MapUnits;
 
        按 F5 编译运行,可以看到,我们的程序已经能够正常工作了。但是细心的你可能会发现,当前坐标的后面的坐标单位为“ esriUnknownUnits ”或“ esriMeters ”之类,即系统在正常单位的前面加上了“ esri ”,追求完美的我们自然看得不舒服。那就进行简单的替换吧。

      首先定义个全局坐标单位变量 sMapUnits ,如下所示:

       private string sMapUnits; 

       再 Form1_Load 函数中进行初始化:

        sMapUnits = "Unknown";  

        添加 axMapControl1 控件的  OnMouseMove  事件,在事件响应函数中进行坐标单位替换,并将this.axMapControl1.MapUnits替换为sMapUnits,代码如下:

esriUnits mapUnits = axMapControl1.MapUnits;

switch (mapUnits)

{

case esriUnits.esriCentimeters:

sMapUnits = "Centimeters";

break;

case esriUnits.esriDecimalDegrees:

sMapUnits = "Decimal Degrees";

break;

case esriUnits.esriDecimeters:

sMapUnits = "Decimeters";

break;

case esriUnits.esriFeet:

sMapUnits = "Feet";

break;

case esriUnits.esriInches:

sMapUnits = "Inches";

break;

case esriUnits.esriKilometers:

sMapUnits = "Kilometers";

break;

case esriUnits.esriMeters:

sMapUnits = "Meters";

break;

case esriUnits.esriMiles:

sMapUnits = "Miles";

break;

case esriUnits.esriMillimeters:

sMapUnits = "Millimeters";

break;

case esriUnits.esriNauticalMiles:

sMapUnits = "NauticalMiles";

break;

case esriUnits.esriPoints:

sMapUnits = "Points";

break;

case esriUnits.esriUnknownUnits:

sMapUnits = "Unknown";

break;

case esriUnits.esriYards:

sMapUnits = "Yards";

break;

}

 
按 F5 编译运行程序。如果你足够细心的话,相信你已经成功了!
 

9右键的实现

        在AE开发中,右键菜单有两种实现方式,一是使用VS2005自带的ContextMenuStrip控件,二是用AE封装的IToolbarMenu接口。相比较而言,后者更为简单实用,本文采用后者的实现方法。由于在TOCControl控件右键菜单上要查询属性表,所以先新建一个普通类,命名为FrmAttribute,并添加以下代码:

        public DataGridView GetDataGrid
        {
            get
            {
                return dataGridView1;
            }
        }

      在Form1类里面添加如下变量的定义:

       //右键菜单
        private ITOCControl2 m_tocControl;
        private IToolbarMenu m_menuMap;
        private IToolbarMenu m_menuLayer;
        private FrmAttribute frmOpenAttributeTable;
        ILayer pmovelayer;

          当然,添加菜单项之前,必须实现相应命令或工具。这里的命令或工具可以AE内置的也可以是自定义的。AE内置了许多可以直接调用的常用命令和工具,如ControlsAddDataCommandClass,在ESRI.ArcGIS.Controls命名空间中,大家可以对象浏览器中查看。当然,这里也可以直接调用AE内置的菜单,如ControlsFeatureSelectionMenu。另外,本讲也实现三自定义命令,以做示范。它们分别为图层可视控制命令(用于控制图层显示与否)、移除图层和放大到整个图层命令。实现方法也很简单,就是右击ZZUMap项目,选择“添加|类”,选择C#普通的类模板,用以下代码覆盖系统自己生成的所有代码。

        LayerSelectable类代码:

using System;
using System.Collections.Generic;
using System.Text;
using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.SystemUI;
namespace ZZUMap
{
    public sealed class LayerSelectable : BaseCommand, ICommandSubType
    {
        private IMapControl3 m_mapControl;
        private long m_subType;

        public LayerSelectable()
        {
        }

        public override void OnClick()
        {
            IFeatureLayer layer = (IFeatureLayer)m_mapControl.CustomProperty;
            if (m_subType == 1) layer.Selectable = true;
            if (m_subType == 2) layer.Selectable = false;
        }

        public override void OnCreate(object hook)
        {
            m_mapControl = (IMapControl3)hook;
        }

        public override bool Enabled
        {
            get
            {
                ILayer layer = (ILayer)m_mapControl.CustomProperty;
                if (layer is IFeatureLayer)
                {
                    IFeatureLayer featureLayer = (IFeatureLayer)layer;
                    if (m_subType == 1) return !featureLayer.Selectable;
                    else return featureLayer.Selectable;
                }
                else
                {
                    return false;
                }
            }
        }

        public int GetCount()
        {
            return 2;
        }

        public void SetSubType(int SubType)
        {
            m_subType = SubType;
        }

        public override string Caption
        {
            get
            {
                if (m_subType == 1) return "图层可选";
                else return "图层不可选";
            }
        }
    }
}

LayerVisibility类代码

using System;
using System.Collections.Generic;
using System.Text;
using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.SystemUI;

namespace ZZUMap
{
    public sealed class LayerVisibility : BaseCommand, ICommandSubType
    {
        private IHookHelper m_hookHelper = new HookHelperClass();
        private long m_subType;

        public LayerVisibility()
        {
        }

        public override void OnClick()
        {
            for (int i = 0; i <= m_hookHelper.FocusMap.LayerCount - 1; i++)
            {
                if (m_subType == 1) m_hookHelper.FocusMap.get_Layer(i).Visible = true;
                if (m_subType == 2) m_hookHelper.FocusMap.get_Layer(i).Visible = false;
            }
            m_hookHelper.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);
        }

        public override void OnCreate(object hook)
        {
            m_hookHelper.Hook = hook;
        }

        public int GetCount()
        {
            return 2;
        }

        public void SetSubType(int SubType)
        {
            m_subType = SubType;
        }

        public override string Caption
        {
            get
            {
                if (m_subType == 1) return "Turn All Layers On";
                else return "Turn All Layers Off";
            }
        }

        public override bool Enabled
        {
            get
            {
                bool enabled = false; int i;
                if (m_subType == 1)
                {
                    for (i = 0; i <= m_hookHelper.FocusMap.LayerCount - 1; i++)
                    {
                        if (m_hookHelper.ActiveView.FocusMap.get_Layer(i).Visible == false)
                        {
                            enabled = true;
                            break;
                        }
                    }
                }
                else
                {
                    for (i = 0; i <= m_hookHelper.FocusMap.LayerCount - 1; i++)
                    {
                        if (m_hookHelper.ActiveView.FocusMap.get_Layer(i).Visible == true)
                        {
                            enabled = true;
                            break;
                        }
                    }
                }
                return enabled;
            }
        }
    }
}

OpenAttributeTable类代码:
 

using System;
using System.Collections.Generic;
using System.Text;
using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.CartoUI;
using System.Data;
using System.Collections;
using ESRI.ArcGIS.Geodatabase;
using System.IO;
using ESRI.ArcGIS.GeoDatabaseUI;
using ESRI.ArcGIS.Display;
using System.Windows.Forms;
namespace ZZUMap
{
    public class OpenAttributeTable : BaseCommand
    {

        private IMapControl3 m_mapControl;
        private IHookHelper m_hookHelper = new HookHelperClass();
        private FrmAttribute frmOpenAttributeTable;

        public OpenAttributeTable()
        {
            base.m_category = ""; //localizable text
            base.m_caption = " 打开属性表";  //localizable text 
            base.m_message = "This should work in ArcMap/MapControl/PageLayoutControl";  //localizable text
            base.m_toolTip = "";  //localizable text
            base.m_name = "";   //unique id, non-localizable (e.g. "MyCategory_MyCommand")

        }
        #region Overriden Class Methods

        public override void OnClick()
        {

            frmOpenAttributeTable = new FrmAttribute();
            DataGridView dataGrid;
            dataGrid = frmOpenAttributeTable.GetDataGrid;

            frmOpenAttributeTable.Show();
            frmOpenAttributeTable.TopMost = true;

            base.OnClick();
            IFeatureLayer pFeatLyr = (IFeatureLayer)m_mapControl.CustomProperty;
            //查询ILayerFields接口
            ILayerFields pFeatLyrFields = (ILayerFields)pFeatLyr;
            IFeatureClass pFeatCls = pFeatLyr.FeatureClass;
            frmOpenAttributeTable.Text = pFeatLyr.Name + "--属性表";
            DataSet myDataset = new DataSet("Test");
            DataTable pTable = new DataTable(pFeatLyr.Name);
            for (int n = 0; n < pFeatLyrFields.FieldCount - 1; n++)
            {
                DataColumn pTableCol = new DataColumn(pFeatLyrFields.get_Field(n).Name);
                pTable.Columns.Add(pTableCol);
                pTableCol = null;
            }
            IFeatureCursor pFeatCursor = pFeatCls.Search(null, false);
            IFeature pFeat = pFeatCursor.NextFeature();
            while (pFeat != null)
            {
                DataRow pTableRow = pTable.NewRow();
                for (int i = 0; i < pFeatLyrFields.FieldCount - 1; i++)
                {
                    if (pFeatLyrFields.FindField(pFeatCls.ShapeFieldName) == i)
                    {
                        pTableRow[i] = pFeatCls.ShapeType.ToString();
                    }
                    else
                    {
                        pTableRow[i] = pFeat.get_Value(i).ToString();
                    }
                }
                pTable.Rows.Add(pTableRow);
                pFeat = pFeatCursor.NextFeature();
            }
            myDataset.Tables.Add(pTable);
            dataGrid.DataSource = myDataset;

            dataGrid.DataMember = pFeatLyr.Name;

 

        }
        #endregion

        public override void OnCreate(object hook)
        {
            m_mapControl = (IMapControl3)hook;
            throw new NotImplementedException();

        }

 

    }
}

RemoveLayer类代码:

using System;
using System.Collections.Generic;
using System.Text;
using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Controls;

namespace ZZUMap
{

    public sealed class RemoveLayer : BaseCommand
    {
        private IMapControl3 m_mapcontrol;
        public RemoveLayer()
        {
            base.m_caption = "移除图层";
        }
        public override void OnCreate(object hook)
        {
            m_mapcontrol = (IMapControl3)hook;
            throw new NotImplementedException();
        }
        public override void OnClick()
        {

            base.OnClick();
            ILayer layer = (ILayer)m_mapcontrol.CustomProperty;
            m_mapcontrol.Map.DeleteLayer(layer);
        }

    }
}

ScaleThresholds类代码:

using System;
using System.Collections.Generic;
using System.Text;
using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.SystemUI;
namespace ZZUMap
{

    public class ScaleThresholds : BaseCommand, ICommandSubType
    {
        private IMapControl3 m_mapControl;
        private long m_subType;

        public ScaleThresholds()
        {

        }

        public override void OnClick()
        {
            ILayer layer = (ILayer)m_mapControl.CustomProperty;
            if (m_subType == 1) layer.MaximumScale = m_mapControl.MapScale;
            if (m_subType == 2) layer.MinimumScale = m_mapControl.MapScale;
            if (m_subType == 3)
            {
                layer.MaximumScale = 0;
                layer.MinimumScale = 0;
            }
            m_mapControl.Refresh(esriViewDrawPhase.esriViewGeography, null, null);
        }

        public override void OnCreate(object hook)
        {
            m_mapControl = (IMapControl3)hook;
        }

        public int GetCount()
        {
            return 3;
        }

        public void SetSubType(int SubType)
        {
            m_subType = SubType;
        }

        public override string Caption
        {
            get
            {
                if (m_subType == 1) return "图层最大尺寸";
                else if (m_subType == 2) return "图层最小尺寸";
                else return "删除图层比例阈值";
            }
        }

        public override bool Enabled
        {
            get
            {
                bool enabled = true;
                ILayer layer = (ILayer)m_mapControl.CustomProperty;

                if (m_subType == 3)
                {
                    if ((layer.MaximumScale == 0) & (layer.MinimumScale == 0)) enabled = false;
                }
                return enabled;
            }
        }
    }
}

ZoomToLayer类代码:

using System;
using System.Collections.Generic;
using System.Text;

using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Controls;
namespace ZZUMap
{
    public sealed class ZoomToLayer : BaseCommand
    {
        private IMapControl3 m_mapControl;

        public ZoomToLayer()
        {
            base.m_caption = "全屏缩放";
        }

        public override void OnClick()
        {
            ILayer layer = (ILayer)m_mapControl.CustomProperty;
            m_mapControl.Extent = layer.AreaOfInterest;
        }

        public override void OnCreate(object hook)
        {
            m_mapControl = (IMapControl3)hook;
        }
    }
}

在Form1_Load函数进行初始化,即菜单的创建:

//右键菜单
            m_tocControl = (ITOCControl2)axTOCControl1.Object;
            m_mapControl = (IMapControl3)axMapControl1.Object;
            frmOpenAttributeTable = new FrmAttribute();
            Add pre-defined control commands to the ToolbarControl
            //axToolbarControl1.AddItem("esriControls.ControlsSelectFeaturesTool", -1, 0, false, 0, esriCommandStyles.esriCommandStyleIconOnly);
            //axToolbarControl1.AddToolbarDef("esriControls.ControlsMapNavigationToolbar", 0, false, 0, esriCommandStyles.esriCommandStyleIconOnly);
            //axToolbarControl1.AddItem("esriControls.ControlsOpenDocCommand", -1, 0, false, 0, esriCommandStyles.esriCommandStyleIconOnly);
            //Add custom commands to the map menu
            m_menuMap = new ToolbarMenuClass();
            m_menuMap.AddItem(new LayerVisibility(), 1, 0, false, esriCommandStyles.esriCommandStyleTextOnly);
            m_menuMap.AddItem(new LayerVisibility(), 2, 1, false, esriCommandStyles.esriCommandStyleTextOnly);
            //Add pre-defined menu to the map menu as a sub menu 
            m_menuMap.AddSubMenu("esriControls.ControlsFeatureSelectionMenu", 2, true);
            //Add custom commands to the map menu
            m_menuLayer = new ToolbarMenuClass();
            m_menuLayer.AddItem(new RemoveLayer(), -1, 0, false, esriCommandStyles.esriCommandStyleTextOnly);
            m_menuLayer.AddItem(new ScaleThresholds(), 1, 1, true, esriCommandStyles.esriCommandStyleTextOnly);
            m_menuLayer.AddItem(new ScaleThresholds(), 2, 2, false, esriCommandStyles.esriCommandStyleTextOnly);
            m_menuLayer.AddItem(new ScaleThresholds(), 3, 3, false, esriCommandStyles.esriCommandStyleTextOnly);
            m_menuLayer.AddItem(new LayerSelectable(), 1, 4, true, esriCommandStyles.esriCommandStyleTextOnly);
            m_menuLayer.AddItem(new LayerSelectable(), 2, 5, false, esriCommandStyles.esriCommandStyleTextOnly);
            m_menuLayer.AddItem(new ZoomToLayer(), -1, 6, true, esriCommandStyles.esriCommandStyleTextOnly);
            m_menuLayer.AddItem(new OpenAttributeTable(), -1, 0, true, esriCommandStyles.esriCommandStyleTextOnly);
            //Set the hook of each menu
            m_menuLayer.SetHook(m_mapControl);
            m_menuMap.SetHook(m_mapControl);

这样就完成了TOCControl控件的右键菜单功能,接下来做地图右键菜单功能的开发。先添加一个contextMenuStrip控件,如下图添加子菜单:

各个菜单函数如下:

private void 全图ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ICommand pCommand = new ControlsMapFullExtentCommandClass();
            pCommand.OnCreate(this.axMapControl1.Object);
            pCommand.OnClick();
        }

        private void 前一视图ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ICommand pCommand = new ControlsMapZoomToLastExtentBackCommandClass();
            pCommand.OnCreate(this.axMapControl1.Object);
            pCommand.OnClick();
        }

        private void 后一视图ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ICommand pCommand = new ControlsMapZoomToLastExtentForwardCommandClass();
            pCommand.OnCreate(this.axMapControl1.Object);
            pCommand.OnClick();
        }

        private void 局部放大ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ICommand pCommand = new ControlsMapZoomInFixedCommandClass();
            pCommand.OnCreate(this.axMapControl1.Object);
            pCommand.OnClick();
        }

        private void 局部缩小ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ICommand pCommand = new ControlsMapZoomOutFixedCommandClass();
            pCommand.OnCreate(this.axMapControl1.Object);
            pCommand.OnClick();
        }

        private void 居中ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.axMapControl1.CenterAt(this.mapRightClickPoint);
        }

        private void 选择要素ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            IMap pMap = this.axMapControl1.Map;
            ISelectionEnvironment pSelectionEnvironment = new SelectionEnvironmentClass();
            pSelectionEnvironment.LinearSearchDistance = this.axMapControl1.ActiveView.Extent.Width / 200;
            pSelectionEnvironment.PointSearchDistance = this.axMapControl1.ActiveView.Extent.Width / 200;
            pMap.SelectByShape(this.mapRightClickPoint as IGeometry, null, false);
            this.axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeoSelection, null, null);
        }

        private void 识别ToolStripMenuItem_Click(object sender, EventArgs e)
        {

        }

 最后在地图的MouseDown事件中添加响应事件,代码如下:

private void axMapControl1_OnMouseDown(object sender, IMapControlEvents2_OnMouseDownEvent e)
        {
            if (e.button == 2)
            {
                if (this.mapRightClickPoint == null)
                {
                    this.mapRightClickPoint = new PointClass();
                }
                this.mapRightClickPoint.PutCoords(e.mapX, e.mapY);
                IGraphicsContainer pGraphicContainer = this.axMapControl1.Map as IGraphicsContainer;
                IEnumElement pEnumElement = pGraphicContainer.LocateElements(this.mapRightClickPoint, this.axMapControl1.ActiveView.Extent.Width / 500);
                if (pEnumElement != null)
                {
                    return;
                }
                else
                {
                    this.contextMenuStrip1.Show(this.axMapControl1 as Control, new System.Drawing.Point(e.x, e.y));
                }
            }
        }

这样就完成了地图右键功能的开发。

10符号选择器的实现

          新建Winodws窗体,命名为SymbolSelectorFrm,修改窗体的Text属性为“选择符号”。并添加SymboloryControlPictureBoxButtonLabelNumericUpDownGroupBoxColorDialogOpenFileDialogContextMenuStrip控件。控件布局如下所示:

 

        设置相应控件的相关属性,如下表所示(空则不用修改)

        在解决方案资源管理器中添加ArcGIS Engine的ESRI.ArcGIS.Geodatabase引用,在SymbolSelectorFrm.cs文件中添加如下引用代码:

   using ESRI.ArcGIS.Carto; 

   using ESRI.ArcGIS.Display;

   using ESRI.ArcGIS.esriSystem;

   using ESRI.ArcGIS.SystemUI;

  using ESRI.ArcGIS.Controls;

  using ESRI.ArcGIS.Geodatabase;

       修改SymbolSelectorFrm的构造函数,传入图层和图例接口。代码如下:

        /// <summary> 
        /// 构造函数,初始化全局变量
        /// </summary>
        /// <param name="tempLegendClass">TOC图例</param>
        /// <param name="tempLayer">图层</param>
        public SymbolSelectorFrm(ILegendClass tempLegendClass, ILayer tempLayer)
        {
            InitializeComponent();
            this.pLegendClass = tempLegendClass;
            this.pLayer = tempLayer;
        }

    

    添加SymbolControl的SymbologyStyleClass设置函数SetFeatureClassStyle(),代码如下:

        /// <summary>
        /// 初始化SymbologyControl的StyleClass,图层如果已有符号,则把符号添加到SymbologyControl中的第一个符号,并选中
        /// </summary>
        /// <param name="symbologyStyleClass"></param>
        private void SetFeatureClassStyle(esriSymbologyStyleClass symbologyStyleClass)
        {
            this.axSymbologyControl.StyleClass = symbologyStyleClass;
            ISymbologyStyleClass pSymbologyStyleClass = this.axSymbologyControl.GetStyleClass(symbologyStyleClass);
            if (this.pLegendClass != null)
            {
                IStyleGalleryItem currentStyleGalleryItem = new ServerStyleGalleryItem();
                currentStyleGalleryItem.Name = "当前符号";
                currentStyleGalleryItem.Item = pLegendClass.Symbol;
                pSymbologyStyleClass.AddItem(currentStyleGalleryItem,0);
                this.pStyleGalleryItem = currentStyleGalleryItem;
            }
            pSymbologyStyleClass.SelectItem(0);
        }

        添加注册表读取函数ReadRegistry(),此函数从注册表中读取ArcGIS的安装路径,代码如下:

        /// <summary> 
        /// 从注册表中取得指定软件的路径
        /// </summary>
        /// <param name="sKey"></param>
        /// <returns></returns>
        private string ReadRegistry(string sKey)
        {
            //Open the subkey for reading
            Microsoft.Win32.RegistryKey rk = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(sKey);
            if (rk == null) return "";
            // Get the data from a specified item in the key.
            return (string)rk.GetValue("InstallDir");
        }

       添加SymbolSelectorFrm的Load事件。根据图层类型为SymbologyControl导入相应的符号样式文件,如点、线、面,并设置控件的可视性。代码如下:

        private void SymbolSelectorFrm_Load(object sender, EventArgs e)
        {

            //-----------------------------------------------------------------------------------
            //取得ArcGIS安装路径
            //string sInstall = ReadRegistry("SOFTWARE\\ESRI\\ArcGIS License Manager 10.0");
            //载入ESRI.ServerStyle文件到SymbologyControl
            this.axSymbologyControl.LoadStyleFile(sInstall + "
\\Styles\\ESRI.ServerStyle");

            //-----------------------------------------------------------------------------------
            this.axSymbologyControl.LoadStyleFile("D:\\Program Files\\ArcGIS\\Engine10.0\\Styles\\ESRI.ServerStyle");//这个地方有点问题,如果用以上代码也能获取到路径名,但是经调试发现执行会调用mapcontrol的onafterdraw函数(以前用于地图联动),而进入循环,无法调出符号选择器,故改为这段代码。望高手解决。
            //确定图层的类型(点线面),设置好SymbologyControl的StyleClass,设置好各控件的可见性(visible)
            IGeoFeatureLayer pGeoFeatureLayer = (IGeoFeatureLayer)pLayer;
            switch (((IFeatureLayer)pLayer).FeatureClass.ShapeType)
            {
                case ESRI.ArcGIS.Geometry.esriGeometryType.esriGeometryPoint:
                this.SetFeatureClassStyle(esriSymbologyStyleClass.esriStyleClassMarkerSymbols);
                    this.lblAngle.Visible = true;
                    this.nudAngle.Visible = true;
                    this.lblSize.Visible = true;
                    this.nudSize.Visible = true;
                    this.lblWidth.Visible = false;
                    this.nudWidth.Visible = false;
                    this.lblOutlineColor.Visible = false;
                    this.btnOutlineColor.Visible = false;
                    break;
                case ESRI.ArcGIS.Geometry.esriGeometryType.esriGeometryPolyline:
                    this.SetFeatureClassStyle(esriSymbologyStyleClass.esriStyleClassLineSymbols);
                    this.lblAngle.Visible = false;
                    this.nudAngle.Visible = false;
                    this.lblSize.Visible = false;
                    this.nudSize.Visible = false;
                    this.lblWidth.Visible = true;
                    this.nudWidth.Visible = true;
                    this.lblOutlineColor.Visible = false;
                    this.btnOutlineColor.Visible = false;
                    break;
                case ESRI.ArcGIS.Geometry.esriGeometryType.esriGeometryPolygon:
                    this.SetFeatureClassStyle(esriSymbologyStyleClass.esriStyleClassFillSymbols);
                    this.lblAngle.Visible = false;
                    this.nudAngle.Visible = false;
                    this.lblSize.Visible = false;
                    this.nudSize.Visible = false;
                    this.lblWidth.Visible = true;
                    this.nudWidth.Visible = true;
                    this.lblOutlineColor.Visible = true;
                    this.btnOutlineColor.Visible = true;
                    break;
                case ESRI.ArcGIS.Geometry.esriGeometryType.esriGeometryMultiPatch:
                    this.SetFeatureClassStyle(esriSymbologyStyleClass.esriStyleClassFillSymbols);
                    this.lblAngle.Visible = false;
                    this.nudAngle.Visible = false;
                    this.lblSize.Visible = false;
                    this.nudSize.Visible = false;
                    this.lblWidth.Visible = true;
                    this.nudWidth.Visible = true;
                    this.lblOutlineColor.Visible = true;
                    this.btnOutlineColor.Visible = true;
                    break;
                default:
                    this.Close();
                 break;
            }
        }

       双击确定按钮和取消按钮,分别添加如下代码:

        /// <summary> 
        /// 确定按钮
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnOK_Click(object sender, EventArgs e)
        {
            //取得选定的符号
            this.pSymbol = (ISymbol)pStyleGalleryItem.Item;
            //更新预览图像
            this.pSymbolImage = this.ptbPreview.Image;
            //关闭窗体
            this.Close();
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            this.Close();
        }

       为了操作上的方便,我们添加SymbologyControl的DoubleClick事件,当双击符号时同按下确定按钮一样,选定符号并关闭符号选择器窗体。代码如下:

       private void axSymbologyControl_OnDoubleClick(object sender, ISymbologyControlEvents_OnDoubleClickEvent e)
        {
            this.btnOK.PerformClick();
        }

       再添加符号预览函数,当用户选定某一符号时,符号可以显示在PictureBox控件中,方便预览,函数代码如下:

        /// <summary>
        /// 把选中并设置好的符号在picturebox控件中预览
        /// </summary>
        private void PreviewImage()
        {
            stdole.IPictureDisp picture = this.axSymbologyControl.GetStyleClass(this.axSymbologyControl.StyleClass).PreviewItem(pStyleGalleryItem, this.ptbPreview.Width, this.ptbPreview.Height);
            System.Drawing.Image image = System.Drawing.Image.FromHbitmap(new System.IntPtr(picture.Handle));
            this.ptbPreview.Image = image;
        }

        当SymbologyControl的样式改变时,需要重新设置符号参数调整控件的可视性,故要添加SymbologyControl的OnStyleClassChanged事件,事件代码与Load事件类似,如下:

/// <summary> 
        /// 当样式(Style)改变时,重新设置符号类型和控件的可视性
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void axSymbologyControl_OnStyleClassChanged(object sender, ESRI.ArcGIS.Controls.ISymbologyControlEvents_OnStyleClassChangedEvent e)
        {
            switch ((esriSymbologyStyleClass)(e.symbologyStyleClass))
            {
                case esriSymbologyStyleClass.esriStyleClassMarkerSymbols:
                    this.lblAngle.Visible = true;
                    this.nudAngle.Visible = true;
                    this.lblSize.Visible = true;
                    this.nudSize.Visible = true;
                    this.lblWidth.Visible = false;
                    this.nudWidth.Visible = false;
                    this.lblOutlineColor.Visible = false;
                    this.btnOutlineColor.Visible = false;
                    break;
                case esriSymbologyStyleClass.esriStyleClassLineSymbols:
                    this.lblAngle.Visible = false;
                    this.nudAngle.Visible = false;
                    this.lblSize.Visible = false;
                    this.nudSize.Visible = false;
                    this.lblWidth.Visible = true;
                    this.nudWidth.Visible = true;
                    this.lblOutlineColor.Visible = false;
                    this.btnOutlineColor.Visible = false;
                    break;
                case esriSymbologyStyleClass.esriStyleClassFillSymbols:
                    this.lblAngle.Visible = false;
                    this.nudAngle.Visible = false;
                    this.lblSize.Visible = false;
                    this.nudSize.Visible = false;
                    this.lblWidth.Visible = true;
                    this.nudWidth.Visible = true;
                    this.lblOutlineColor.Visible = true;
                    this.btnOutlineColor.Visible = true;
                    break;
            }
        }

       通过以上操作,本符号选择器雏形已经完成,我们可以3sdnMap主窗体中调用并进行测试。如果您已经完成“直接调用ArcMap中的符号选择器”这一节,请注释axTOCControl_OnDoubleClick事件响应函数里的代码,并添加如下代码。如果您是直接学习自定义符号选择器这一节的,请先添加axTOCControl1控件的OnDoubleClick事件,再添加如下事件响应函数代码:

        /// <summary> 
        /// 双击TOCControl控件时触发的事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void axTOCControl1_OnDoubleClick(object sender, ITOCControlEvents_OnDoubleClickEvent e)
        {
            esriTOCControlItem itemType = esriTOCControlItem.esriTOCControlItemNone;
            IBasicMap basicMap = null;
            ILayer layer = null;
            object unk = null;
            object data = null;
            axTOCControl1.HitTest(e.x, e.y, ref itemType, ref basicMap, ref layer, ref unk, ref data);
            if (e.button == 1)
            {
                if (itemType == esriTOCControlItem.esriTOCControlItemLegendClass)
                {
                    //取得图例
                    ILegendClass pLegendClass = ((ILegendGroup)unk).get_Class((int)data);
                    //创建符号选择器SymbolSelector实例
                    SymbolSelectorFrm SymbolSelectorFrm = new SymbolSelectorFrm(pLegendClass, layer);
                    if (SymbolSelectorFrm.ShowDialog() == DialogResult.OK)
                    {
                        //局部更新主Map控件
                        m_mapControl.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeography, null, null);
                        //设置新的符号
                        pLegendClass.Symbol = SymbolSelectorFrm.pSymbol;
                        //更新主Map控件和图层控件
                        this.axMapControl1.ActiveView.Refresh();
                        this.axTOCControl1.Refresh();
                    }
                }
            }
        }

       按F5编译运行,相信你已经看到自己新手打造的符号选择器已经出现在眼前了,但还有些功能没实现。当然,它还比较简陋,下面我们将一起把它做得更完美些。在地图整饰中,符号参数的调整是必须的功能。下面我们将实现符号颜色、外框颜色、线宽、角度等参数的调整。添加SymbologyControl的OnItemSelected事件,此事件在鼠标选中符号时触发,此时显示出选定符号的初始参数,事件响应函数代码如下:

        /// <summary>
        /// 选中符号时触发的事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void axSymbologyControl_OnItemSelected(object sender, ESRI.ArcGIS.Controls.ISymbologyControlEvents_OnItemSelectedEvent e)
        {
            pStyleGalleryItem = (IStyleGalleryItem)e.styleGalleryItem;
            Color color;
            switch (this.axSymbologyControl.StyleClass)
            {
                    //点符号
                case esriSymbologyStyleClass.esriStyleClassMarkerSymbols:
                    color = this.ConvertIRgbColorToColor(((IMarkerSymbol)pStyleGalleryItem.Item).Color as IRgbColor);
                    //设置点符号角度和大小初始值
                    this.nudAngle.Value = (decimal)((IMarkerSymbol)this.pStyleGalleryItem.Item).Angle;
                    this.nudSize.Value = (decimal)((IMarkerSymbol)this.pStyleGalleryItem.Item).Size;
                    break;
                    //线符号
                case esriSymbologyStyleClass.esriStyleClassLineSymbols:
                    color = this.ConvertIRgbColorToColor(((ILineSymbol)pStyleGalleryItem.Item).Color as IRgbColor);
                    //设置线宽初始值
                    this.nudWidth.Value = (decimal)((ILineSymbol)this.pStyleGalleryItem.Item).Width;
                    break;
                    //面符号
                case esriSymbologyStyleClass.esriStyleClassFillSymbols:
                    color = this.ConvertIRgbColorToColor(((IFillSymbol)pStyleGalleryItem.Item).Color as IRgbColor);
                    this.btnOutlineColor.BackColor = this.ConvertIRgbColorToColor(((IFillSymbol)pStyleGalleryItem.Item).Outline.Color as IRgbColor);
                    //设置外框线宽度初始值
                    this.nudWidth.Value = (decimal)((IFillSymbol)this.pStyleGalleryItem.Item).Outline.Width;
                    break;
                default:
                    color = Color.Black;
                    break;
            }
            //设置按钮背景色
            this.btnColor.BackColor = color;
            //预览符号
            this.PreviewImage();
        }

       调整点符号的大小。添加nudSize控件的ValueChanged事件,即在控件的值改变时响应此事件,然后重新设置点符号的大小。代码如下:

       private void nudSize_ValueChanged(object sender, EventArgs e)
        {
            ((IMarkerSymbol)this.pStyleGalleryItem.Item).Size = (double)this.nudSize.Value;
            this.PreviewImage();
        }

        调整点符号的角度,添加nudAngle控件的ValueChanged事件,以重新设置点符号的角度。代码如下:

        private void nudAngle_ValueChanged(object sender, EventArgs e)
        {
            ((IMarkerSymbol)this.pStyleGalleryItem.Item).Angle = (double)this.nudAngle.Value;
            this.PreviewImage();
        }

       调整线符号和面符号的线宽,添加nudWidth控件的ValueChanged事件,以重新设置线符号的线宽和面符号的外框线的线宽。代码如下:

       private void nudWidth_ValueChanged(object sender, EventArgs e)
        {
            switch (this.axSymbologyControl.StyleClass)
            {
                case esriSymbologyStyleClass.esriStyleClassLineSymbols:
                    ((ILineSymbol)this.pStyleGalleryItem.Item).Width = Convert.ToDouble(this.nudWidth.Value);
                    break;
                case esriSymbologyStyleClass.esriStyleClassFillSymbols:
                    //取得面符号的轮廓线符号
                    ILineSymbol pLineSymbol = ((IFillSymbol)this.pStyleGalleryItem.Item).Outline;
                    pLineSymbol.Width = Convert.ToDouble(this.nudWidth.Value);
                    ((IFillSymbol)this.pStyleGalleryItem.Item).Outline = pLineSymbol;
                    break;
            }
            this.PreviewImage();
        }

       颜色转换,在ArcGIS Engine中,颜色由IRgbColor接口实现,而在.NET框架中,颜色则由Color结构表示。故在调整颜色参数之前,我们必须完成以上两种不同颜色表示方式的转换。关于这两种颜色结构的具体信息,请大家自行查阅相关资料。下面添加两个颜色转换函数。

       ArcGIS Engine中的IRgbColor接口转换至.NET中的Color结构的函数:

        /// <summary> 
        /// 将ArcGIS Engine中的IRgbColor接口转换至.NET中的Color结构
        /// </summary>
        /// <param name="pRgbColor">IRgbColor</param>
        /// <returns>.NET中的System.Drawing.Color结构表示ARGB颜色</returns>
        public Color ConvertIRgbColorToColor(IRgbColor pRgbColor)
        {
            return ColorTranslator.FromOle(pRgbColor.RGB);
        }

       .NET中的Color结构转换至于ArcGIS Engine中的IColor接口的函数:

        /// <summary> 
        /// 将.NET中的Color结构转换至于ArcGIS Engine中的IColor接口
        /// </summary>
        /// <param name="color">.NET中的System.Drawing.Color结构表示ARGB颜色</param>
        /// <returns>IColor</returns>
        public IColor ConvertColorToIColor(Color color)
        {
            IColor pColor = new RgbColorClass();
            pColor.RGB = color.B * 65536 + color.G * 256 + color.R;
            return pColor;
        }

        调整所有符号的颜色。选择颜色时,我们调用.NET的颜色对话框ColorDialog,选定颜色后,修改颜色按钮的背景色为选定的颜色,以方便预览。双击btnColor按钮,添加如下代码:

        private void btnColor_Click(object sender, EventArgs e)
        {
            //调用系统颜色对话框
            if (this.colorDialog.ShowDialog() == DialogResult.OK)
            {
                //将颜色按钮的背景颜色设置为用户选定的颜色
                this.btnColor.BackColor = this.colorDialog.Color;
                //设置符号颜色为用户选定的颜色
                switch (this.axSymbologyControl.StyleClass)
                {
                    //点符号
                    case esriSymbologyStyleClass.esriStyleClassMarkerSymbols:
                        ((IMarkerSymbol)this.pStyleGalleryItem.Item).Color = this.ConvertColorToIColor(this.colorDialog.Color);
                        break;
                    //线符号
                    case esriSymbologyStyleClass.esriStyleClassLineSymbols:
                        ((ILineSymbol)this.pStyleGalleryItem.Item).Color = this.ConvertColorToIColor(this.colorDialog.Color);
                        break;
                    //面符号
                    case esriSymbologyStyleClass.esriStyleClassFillSymbols:
                        ((IFillSymbol)this.pStyleGalleryItem.Item).Color = this.ConvertColorToIColor(this.colorDialog.Color);
                        break;
                }
                //更新符号预览
                this.PreviewImage();
            }
        }

      调整面符号的外框线颜色。同上一步类似,双击btnOutlineColor按钮,添加如下代码:

       private void btnOutlineColor_Click(object sender, EventArgs e)
        {
            if (this.colorDialog.ShowDialog() == DialogResult.OK)
            {
                //取得面符号中的外框线符号
                ILineSymbol pLineSymbol = ((IFillSymbol)this.pStyleGalleryItem.Item).Outline;
                //设置外框线颜色
                pLineSymbol.Color = this.ConvertColorToIColor(this.colorDialog.Color);
                //重新设置面符号中的外框线符号
                ((IFillSymbol)this.pStyleGalleryItem.Item).Outline = pLineSymbol;
                //设置按钮背景颜色
                this.btnOutlineColor.BackColor = this.colorDialog.Color;
                //更新符号预览
                this.PreviewImage();
            }
        }

       至此,你可以编译运行程序,看看效果如何,是不是感觉很不错了?我们已经能够修改符号的参数,自定义符号了。但是,SymbologyControl默认加载的是ESRI.ServerStyle文件的样式,用过ArcMap的你可能已经注意到,ArcMap中的Symbol Selector有一个“More Symbols”按钮,可以加载其它的符号和ServerStyle文件。

       还记得我们在开始的时候添加了ContextMenuStrip控件吗?现在它终于派上用场了。我们要实现的功能是:单击“更多符号”弹出菜单(ContextMenu),菜单中列出了ArcGIS自带的其它符号,勾选相应的菜单项就可以在SymbologyControl中增加相应的符号。在菜单的最后一项是“添加符号”,选择这一项时,将弹出打开文件对话框,我们可以由此选择其它的ServerStyle文件,以加载更多的符号。

    定义全局变量,在SymbolSelectorFrm中定义如下全局变量,用于判断菜单是否已经初始化。

       //菜单是否已经初始化标志

      bool contextMenuMoreSymbolInitiated = false;

      双击“更多符号”按钮,添加Click事件。在此事件响应函数中,我们要完成ServerStyle文件的读取,将其文件名作为菜单项名称生成菜单并显示菜单。代码如下:

        /// <summary>
        /// “更多符号”按下时触发的事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnMoreSymbol_Click(object sender, EventArgs e)
        {
            if (this.contextMenuMoreSymbolInitiated == false)
            {
                //string sInstall = ReadRegistry("SOFTWARE\\ESRI\\CoreRuntime");
                //string path = System.IO.Path.Combine(sInstall, "Styles");
                string sInstall = "";
                string path = System.IO.Path.Combine(sInstall, "D:\\Program Files\\ArcGIS\\Engine10.0\\Styles");
                //取得菜单项数量
                string[] styleNames = System.IO.Directory.GetFiles(path, "*.ServerStyle");
                ToolStripMenuItem[] symbolContextMenuItem = new ToolStripMenuItem[styleNames.Length + 1];
                //循环添加其它符号菜单项到菜单
                for (int i = 0; i < styleNames.Length; i++)
                {
                    symbolContextMenuItem[i] = new ToolStripMenuItem();
                    symbolContextMenuItem[i].CheckOnClick = true;
                    symbolContextMenuItem[i].Text = System.IO.Path.GetFileNameWithoutExtension(styleNames[i]);
                    if (symbolContextMenuItem[i].Text == "ESRI")
                    {
                        symbolContextMenuItem[i].Checked = true;
                    }
                    symbolContextMenuItem[i].Name = styleNames[i];
                }
                //添加“更多符号”菜单项到菜单最后一项
                symbolContextMenuItem[styleNames.Length] = new ToolStripMenuItem();
                symbolContextMenuItem[styleNames.Length].Text = "添加符号";
                symbolContextMenuItem[styleNames.Length].Name = "AddMoreSymbol";
                //添加所有的菜单项到菜单
                this.contextMenuStrip.Items.AddRange(symbolContextMenuItem);
                this.contextMenuMoreSymbolInitiated = true;
            }
            //显示菜单
            this.contextMenuStrip.Show(this.btnMoreSymbol.Location);
        }

        添加contextMenuStripMoreSymbol控件的ItemClicked事件。单击某一菜单项时响应ItemClicked事件,将选中的ServerStyle文件导入到SymbologyControl中并刷新。当用户单击“添加符号”菜单项时,弹出打开文件对话框,供用户选择其它的ServerStyle文件。代码如下:

        /// <summary>
        /// “更多符号”按钮弹出的菜单项单击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void contextMenuStrip_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            ToolStripMenuItem pToolStripMenuItem = (ToolStripMenuItem)e.ClickedItem;
            //如果单击的是“添加符号”
            if (pToolStripMenuItem.Name == "AddMoreSymbol")
            {
                //弹出打开文件对话框
                if (this.openFileDialog.ShowDialog() == DialogResult.OK)
                {
                    //导入style file到SymbologyControl
                    this.axSymbologyControl.LoadStyleFile(this.openFileDialog.FileName);
                    //刷新axSymbologyControl控件
                    this.axSymbologyControl.Refresh();
                }
            }
            else//如果是其它选项
            {
                if (pToolStripMenuItem.Checked == false)
                {
                    this.axSymbologyControl.LoadStyleFile(pToolStripMenuItem.Name);
                    this.axSymbologyControl.Refresh();
                }
                else
                {
                    this.axSymbologyControl.RemoveFile(pToolStripMenuItem.Name);
                    this.axSymbologyControl.Refresh();
                }
            }
        }

      相信你已经盼这一步很久了吧,按照惯例,按下F5吧!大功造成。 

 11图层文本标注

       这一讲给大家讲解图层标注的实现方法。图层标注实现起来并不复杂,本例仅做一个简单示范,只加载AE的样式库,标注选定的字段,旨在抛砖引玉。更高级的功能,如自定义样式和修改样式,由读者自己实现。主要思路: 加载图层字段 –> 加载文本样式 -> 设置文本样式。实现过程: 创建标注设置窗体 -> 创建图层标注的Command -> 添加Command到图层右键菜单。

      添加一个Windows窗体,命名为LabelLayerFrm.cs。添加控件如下:

       为LabelLayerFrm类添加两个成员变量:

       public ILayer pLayer;

       private IStyleGalleryItem pStyleGalleryItem; 

      重载一个构造函数:

       public LabelLayerFrm(ILayer layer)

       {

                InitializeComponent();

               pLayer = layer;

      }

      添加成员函数ReadRegistry,用于从注册表中读取ArcGIS的安装路径。

        /// <summary>
        /// 读取注册表中的制定软件的路径
        /// </summary>
        /// <param name="sKey"></param>
        /// <returns></returns>
        private string ReadRegistry(string sKey)
        {
            //Open the subkey for reading
            Microsoft.Win32.RegistryKey rk = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(sKey, true);
            if (rk == null) return "";
            // Get the data from a specified item in the key.
            return (string)rk.GetValue("InstallDir");
        }

       添加LabelLayerFrm窗体的Load事件,以加载图层字段到下拉模型,加载文本样式到SymbologyControl控件。

       private void LabelLayerFrm_Load(object sender, EventArgs e)
        {
            //加载图层字段
            ITable pTable = pLayer as ITable;
            IField pField = null;
            for (int i = 0; i < pTable.Fields.FieldCount; i++)
            {
                pField = pTable.Fields.get_Field(i);
                cbbField.Items.Add(pField.AliasName);
            }
            cbbField.SelectedIndex = 0;
            //获得ArcGIS的安装路径
            //string sInstall = ReadRegistry("SOFTWARE\\ESRI\\CoreRuntime");
            //加载ESRI.ServerStyle 样式文件到SymbologyControl
            this.axSymbologyControl.LoadStyleFile("D:\\Program Files\\ArcGIS\\Engine10.0\\Styles\\ESRI.ServerStyle");
            this.axSymbologyControl.GetStyleClass(esriSymbologyStyleClass.esriStyleClassTextSymbols).SelectItem(0);//这个地方有错误,SelectItem选择后不能调出涂层文本标注框
            //this.axSymbologyControl.GetStyleClass(esriSymbologyStyleClass.esriStyleClassTextSymbols);//改成这句也不行,能调出对话框但是样式选择,后面代码就不能正确执行
            //综上问题还是不能解决,楼主能力有限,姑且认为上一行的代码正确,只是为AE的bug
        }

         添加axSymbologyControl1控件的OnItemSelected事件,以设置选定的样式。

        private void axSymbologyControl1_OnItemSelected   (object sender, ISymbologyControlEvents_OnItemSelectedEvent e)

        {

              pStyleGalleryItem = (IStyleGalleryItem)e.styleGalleryItem;

        } 

   添加确定按扭的Click事件,为选定图层中的选定的字段以选定的样式标注。

 private void btnOK_Click(object sender, EventArgs e)

{

    IGeoFeatureLayer pGeoFeatureLayer = pLayer as IGeoFeatureLayer;

    pGeoFeatureLayer.AnnotationProperties.Clear();//必须执行,因为里面有一个默认的

    IBasicOverposterLayerProperties pBasic = new BasicOverposterLayerPropertiesClass();

    ILabelEngineLayerProperties pLableEngine = new LabelEngineLayerPropertiesClass();

    ITextSymbol pTextSymbol = new TextSymbolClass();            

    pTextSymbol = (ITextSymbol)pStyleGalleryItem.Item;

    //你可以在这里修改样式的颜色和字体等属性,本文从略

    //pTextSymbol.Color

    //pTextSymbol.Font 

    string pLable = "[" + (string)cbbField .SelectedItem + "]";

    pLableEngine.Expression = pLable;

    pLableEngine.IsExpressionSimple = true;

    pBasic.NumLabelsOption = esriBasicNumLabelsOption.esriOneLabelPerShape;

    pLableEngine.BasicOverposterLayerProperties = pBasic;

    pLableEngine.Symbol = pTextSymbol;

    pGeoFeatureLayer.AnnotationProperties.Add(pLableEngine as IAnnotateLayerProperties);

    pGeoFeatureLayer.DisplayAnnotation = true;

        至此,标注设置窗体已经完成,如果你编译通不过,看看是不是忘了添加相关引用了。

    创建一个新类,以ArcGIS的BaseCommand为模板,命名为LabelLayerCmd.cs。注意:在新建Base Command模板时,会弹出一个对话框让我们选择模板适用对象,这时我们要选择MapControl、PageLayoutControl,即选择第二项或者倒数第二项。添加LabelLayerCmd类的成员变量。

       private ILayer pLayer = null;

       IMapControl3 pMap; 

    修改默认构造函数如下:

 public LabelLayerCmd(ILayer lyr,IMapControl3 map)

{

    //

    // TODO: Define values for the public properties

    //

    base.m_category = ""; //localizable text

    base.m_caption = "标注";  //localizable text 

    base.m_message = "标注";  //localizable text

    base.m_toolTip = "标注";  //localizable text

    base.m_name = "标注";   //unique id, non-localizable (e.g. "MyCategory_MyCommand")

    pLayer = lyr;

    pMap = map;

    try

    {

        //

        // TODO: change bitmap name if necessary

        //

        string bitmapResourceName = GetType().Name + ".bmp";

        base.m_bitmap = new Bitmap(GetType(), bitmapResourceName);

    }

    catch (Exception ex)

    {

        System.Diagnostics.Trace.WriteLine(ex.Message, "Invalid Bitmap");

    }

     修改OnClick函数为:

 /// <summary>

/// Occurs when this command is clicked

/// </summary>

public override void OnClick()

{

    // TODO: Add LabelLayerCmd.OnClick implementation

    LabelLayerFrm labelLyrFrm = new LabelLayerFrm(pLayer);

    labelLyrFrm.ShowDialog();

    pMap.Refresh(esriViewDrawPhase.esriViewGraphics, null, null);

    回到主窗体类,找到axTOCControl1_OnMouseDown事件响应函数,修改如下代码片断:

 //弹出右键菜单

if (item == esriTOCControlItem.esriTOCControlItemMap)

    m_menuMap.PopupMenu(e.x, e.y, m_tocControl.hWnd);

if (item == esriTOCControlItem.esriTOCControlItemLayer)

{

m_menuLayer.AddItem(new OpenAttributeTable(layer), -1, 2, true , esriCommandStyles.esriCommandStyleTextOnly);

//动态添加图层标注的Command到图层右键菜单

    m_menuLayer.AddItem(new LabelLayerCmd(layer, m_mapControl), -1, 3, false, esriCommandStyles.esriCommandStyleTextOnly);

    //弹出图层右键菜单 

    m_menuLayer.PopupMenu(e.x, e.y, m_tocControl.hWnd);

 //移除菜单项

    m_menuLayer.Remove(3);

    m_menuLayer.Remove(2);

至此,已经完成图层文本标注,编译运行吧。

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暮紫月升

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值