ArcGIS Pro 二次开发

2023-02-22,

<!--
* @Description:
* @Author: DJ
* @Date: 2020-08-19 15:15:58
* @LastEditTime: 2020-09-02 16:57:53
* @LastEditors: DJ
-->

本文基于 Windows7 + VS2019 + .NET Framework 4.8 + ArcGIS Pro 2.5.22081 开发和撰写。

目录
开发环境配置
获取ArcGIS Pro
安装VS2019
安装ArcGIS Pro SDK
关闭VS拓展自动更新
正式安装插件
创建第一个Pro Add-in
使用模板创建Pro Add-in项目
添加一个button
手动添加button
添加button至显示
为button添加逻辑代码
自动添加button
生成Addin文件
调试插件
开发小tip
DAML配置
DAML介绍
根节点
AddInInfo节点
modules节点
Pro 控件级别
将control在新tab中显示
常用DAML元素
控件control
控件常用属性
控件常用方法
常用控件
停靠窗dockpane
自定义DockPane停靠位置
地理处理GeoProcess
功能实现
在Addin中直接打开自建的工具箱
点击弹出浮动窗口
使用Python脚本
创建要属类
为线要素类添加记录
待续

开发环境配置

获取ArcGIS Pro

ArcGIS Pro可在Esri官网申请21天试用。

安装VS2019

VS2019的安装十分简单,在微软官网下载VS2019社区版安装程序,双击安装即可,具体可参考该博客

安装ArcGIS Pro SDK

关闭VS拓展自动更新

首先打开安装好的VS2019,点击“菜单栏-工具-选项”,在选项中找到“环境-拓展”,关闭拓展自动更新,如下图所示。关闭拓展自动更新可以防止拓展自动更新后与当前环境不匹配,如我的环境为ArcGIS Pro2.5,如果不关闭自动更新,则下次打开VS时ArcGIS Pro SDK插件将自动更新为2.6版本,与Pro版本不匹配,无法使用。

正式安装插件

接下来开始安装ArcGIS Pro SDK for .NET,需要点击“菜单栏-拓展-管理拓展”,在弹出的窗口中切换至联机,搜索“ArcGIS Pro”,找到“ArcGIS Pro SDK for .NET”和“ArcGIS Pro SDK for .NET(Utilities)”两个插件,安装并禁用自动更新。点击安装后,重启VS插件即安装完毕,至此,开发环境配置完成。

创建第一个Pro Add-in

使用模板创建Pro Add-in项目

打开VS2019,选择“创建新项目”,将“项目类型筛选”设置为“ArcGIS Pro SDK”,找到“ArcGIS Pro 模块加载项”创建项目即可,注意选择语言为C#而非VB。

添加一个button

Pro的插件及配置使用DAML文件,即项目下的“config.daml”进行声明。

手动添加button

添加button至显示

首先,我们来尝试手动添加一个button。打开“config.daml”文件,在controls标签下添加一个button标签。

<controls>
<button id="AddOneButton" caption="Add one button" className="AddOneButton" loadOnClick="false" smallImage="Images\AddInDesktop16.png" largeImage="Images\AddInDesktop32.png" keytip="AOB">
<tooltip>Add one button</tooltip>
</button>
</controls>

添加完button标签后,该控件并不会显示,只有当控件被某个group引用时,才会显示在菜单中,应用方式如下,其中refID为创建button标签时的id。

<groups>
<group id="DJ_SuspectTrackingSystem_Group1" caption="Group 1" appearsOnAddInTab="true">
<button refID="AddOneButton"/>
</group>
</groups>

至此,当Pro加载时即会在“菜单栏-加载项-Group”中显示该控件。修改完成后的“config.daml”文件和Pro中显示效果如下图所示。

为button添加逻辑代码

修改daml后,仅实现了在Pro中显示button,下面来为button添加逻辑代码。

在项目中新建一个类,类名为刚才daml文件中对应button的className属性,并使其继承自Pro SDK中的Button类。然后重写Button的相关方法,如点击时触发的OnClick方法,在其中实现逻辑代码即可,如下图。

自动添加button

在我们熟悉了daml文件之后,日常开发即可直接使用VS提供的快捷添加控件的方式。

在项目上“右击-添加-新建项”,在弹出的窗口左侧选择“ArcGIS Pro Add-ins”中进行筛选后,选择“ArcGIS Pro 按钮”,点击添加,VS即会自动添加一个button类至项目中,在daml文件的controls中添加button定义,并在默认group中引用该button。

生成Addin文件

插件配置完成后,在解决方案上“右键-重新生成解决方案”,待解决方案生成完毕后,在解决方案文件夹\bin\Debug目录下找到*.esriAddinX文件,该文件即为插件安装文件,双击即可为Pro安装该插件。

调试插件

如果不希望直接为Pro安装插件,而是在开发过程中需要测试插件效果,直接按快捷键F5或点击运行,VS将启动Pro,并在Pro中加载修改后的插件,可以在其中对插件进行测试。

开发小tip

    当项目是从其他电脑拷贝而来时,引用地址可能不正确,此时,可以在解决方案上右击,找到“修复 Pro 引用”选项,点击后,插件会自动更新引用。若要手动更新引用,dll文件通常存放在Pro安装路径\binPro安装路径\bin\Extensions目录下。
    提示"未能解析主引用***,因为它是针对“.NETFramework,Version=v4.8”框架生成的。该框架版本高于当前目标框架"错误信息,则说明生成使用的Framework版本与当前项目应该使用的Framework版本不匹配,在项目上“右击-属性-生成程序”,将“目标框架”切换为当前项目版本即可。

    不知道什么原因,对dockpane UI的修改,需要进行以下操作才能生效:

      Pro中卸载该加载项
      重新打开Pro,发现加载项没有再加载(必须重新再打开一次)
      再解决方案上“右击-重新生成解决方案”
      再次打开Pro,修改即生效

DAML配置

DAML是Desktop Application Markup Language的缩写。是ESRI基于XML标准自定义的UI配置文件。插件和配置的声明性部分是在DAML文件中定义的,其中包含了框架元素(主要是插件)的集合,还包括声明性部分(框架所需的信息,以便在适当的时候激活或创建相关的对象)。通过这种方式实现界面和功能的分离。

DAML介绍

具体的DAML介绍请查看DAML ID reference。以下仅介绍常用节点。

根节点

即ArcGIS标签下的节点,大多数情况下无需修改,使用默认即可,常用的有:

    defaultAssembly:该配置代表的插件所在的程序集名称。
    defaultNamespace:该配置代表的插件所在的命名空间名称。

AddInInfo节点

AddInInfo节点用于声明插件的相关信息,如名称、描述、图标等

modules节点

modules节点是最长访问的节点,插件元素的添加、描述等均在该节点下。modules节点可以包括的元素包括ribbon按钮、工具、画廊、组合框、编辑框、调色板和其他控件,以及应用程序窗格和对接窗格。

Pro 控件级别

Pro的控件级别从上到下分为Module-Tab-Group-Menu-Control,相互关系如下图所示。具体关系控件层级的概念请参考ProGuide。

    Tab
    Group
    Menu
    Control

一个原始的DAML文件如下图所示,开发时,先在controls标签下创建所有要使用的控件及其描述,然后在需要显示控件的group标签中引用对应的control即可。

将control在新tab中显示

默认情况下,control将添加至“菜单栏-加载项”中,如果需要在单独的Tab中显示,则需要在daml文件tabs标签下新建tab标签,并在其中添加对需要显示的group的引用,如下图。

常用DAML元素

下面只介绍常用的元素及其属性、方法等,各元素具体使用情景、属性、方法等请参见官方API文档。DAML中通常可以包括controls、categories、dockpanes、groups、menus、minitoolbars、panes、toolbars等等各种元素,具体元素可参见官方DAML ID Reference

控件control

控件常用属性

控件常用方法

控件常见方法有:

    OnClick():单击控件时触发

使用时,在控件类中重写对应的方法即可,如:

internal class AddOneButton : ArcGIS.Desktop.Framework.Contracts.Button
{
protected override void OnClick()
{
MessageBox.Show("Hello World");
}
}

常用控件

    button
    comboBox
    checkBox
    labelControl
    tool

停靠窗dockpane

dockpane类为停靠窗口类,其UI由对应的.xaml文件确定,逻辑代码由对应的.cs类文件实现。关于DockPane,具体可以参见ProGuide-Dockpanes。

双击xaml文件即可进入设计窗口,可以通过拖拽的方式将各种控件添加至dockpane界面中,可以通过可视化或代码方式设置控件和dockpane的各种属性。

自定义DockPane停靠位置

使用dock,dockWith,autoHide属性可以设置DockPane的初始停靠状态。

    Dock:该属性用于设置初始停靠状态,可选值包括:

      left
      right
      top
      bottom
      float
      group

    dockWith:该属性可以指定要相对停靠的DockPane。

地理处理GeoProcess

可参考ProSnippets-Geoprocessing和ProConcepts-Geoprocessing

功能实现

很多功能实现可参考ArcGIS Pro SDK community samples

在Addin中直接打开自建的工具箱

若想要实现,点击插件上的按钮,自动打开对应的自建工具箱或系统工具箱,需要重写按钮的OnClick()方法,在其中使用ArcGIS.Desktop.Core.Geoprocessing.OpenToolDialog(toolPath, param_values)方法打开工具箱,示例如下。若想要不打开工具箱而直接执行工具,则使用ArcGIS.Desktop.Core.Geoprocessing.ExecuteToolAsync(toolPath,param_values)方法异步执行工具。

如果要使用系统工具箱,则直接用工具箱名\工具名调用即可,如 。如需使用自建工具箱,则使用工具箱路径\工具箱名(包括后缀)\工具名调用,如@"E:\ArcGIS\MyProject\sixTool\sixTool.pyt\OpenHDFTool"

此外,不论是打开工具还是执行工具,都必须输入参数,且创建参数时必须至少有一个输入,否则虽然不会抛出语法错误,但是工具不能正常执行,原因未明。

internal class FactorsCorrelationAnalysis : Button
{
protected override void OnClick()
{
//创建工具参数
//工具必须有参数
string input_points = "";
var param_values = Geoprocessing.MakeValueArray(input_points); //创建参数时必须至少有一个输入,否则工具不会正常显示 //获取sixTools的绝对路径
string nowPath = System.Windows.Forms.Application.StartupPath; //获取启动程序的可执行文件所在的目录
string ArcGISPath = System.IO.Directory.GetParent(nowPath).FullName; //获取当前目录的父目录的绝对路径
string toolRelativePath = @"Resources\ArcToolBox\Scripts\sixTool.pyt\FactorsCorrelationAnalysisTool";
string toolPath = System.IO.Path.Combine(ArcGISPath, toolRelativePath);
//MessageBox.Show(toolPath); //打开工具箱中的工具
Geoprocessing.OpenToolDialog(toolPath, param_values);//必须输入工具参数
}
}

点击弹出浮动窗口

若要实现点击按钮后弹出一个浮动窗口,如ArcGIS自带的查询工具,则在项目中添加一个Dockpane停靠窗类,并在“config.daml”文件中设置其“Dock”属性为“float”即可。

使用Python脚本

Pro中使用Python脚本,本质上就是通过cmd的方式,调用Pro对应的python解释器来执行脚本,并获取脚本执行结果。详细可参考官方sample CallScriptFromNet

//从环境变量中获取Pro对应的Python解释器的绝对路径
var pathProExe = System.IO.Path.GetDirectoryName((new System.Uri(Assembly.GetEntryAssembly().CodeBase)).AbsolutePath);
if (pathProExe == null) return;
pathProExe = Uri.UnescapeDataString(pathProExe);
pathProExe = System.IO.Path.Combine(pathProExe, @"Python\envs\arcgispro-py3");
System.Diagnostics.Debug.WriteLine(pathProExe);
var pathPython = System.IO.Path.GetDirectoryName((new System.Uri(Assembly.GetExecutingAssembly().CodeBase)).AbsolutePath);
if (pathPython == null) return;
pathPython = Uri.UnescapeDataString(pathPython);
System.Diagnostics.Debug.WriteLine(pathPython); //使用cmd调用python解释器来执行python脚本
//创建用于执行cmd的字符串以及获取异常信息的对象
var myCommand = string.Format(@"/c """"{0}"" ""{1}""""",
System.IO.Path.Combine(pathProExe, "python.exe"),
System.IO.Path.Combine(pathPython, "test1.py"));
System.Diagnostics.Debug.WriteLine(myCommand);
var procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", myCommand); procStartInfo.RedirectStandardOutput = true;
procStartInfo.RedirectStandardError = true;
procStartInfo.UseShellExecute = false;
procStartInfo.CreateNoWindow = true; //开始执行脚本
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procStartInfo;
proc.Start(); //获取执行结果并显示
string result = proc.StandardOutput.ReadToEnd();
string error = proc.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(error)) result += string.Format("{0} Error: {1}", result, error);
System.Windows.MessageBox.Show(result);

创建要属类

创建要属类本质上是使用Toolbox中的创建要属类工具(CreateFeatureclass_management)来创建所需的要属类。详细可参考官方sample ConstructingGeometries_CSharp

//根据输入的要属类名称和类型在默认数据库中创建要属类的函数
private static async Task CreateLayer(string featureclassName, string featureclassType)
{
//创建要素时的参数list
List<object> arguments = new List<object>
{
// store the results in the default geodatabase
CoreModule.CurrentProject.DefaultGeodatabasePath,
// name of the feature class
featureclassName,
// type of geometry
featureclassType,
// no template
"",
// no z values
"DISABLED",
// no m values
"DISABLED"
};
//异步,为参数list添加坐标系统参数
await QueuedTask.Run(() =>
{
// spatial reference
arguments.Add(SpatialReferenceBuilder.CreateSpatialReference(3857));
}); //异步执行创建要属类工具,创建要素类
IGPResult result = await Geoprocessing.ExecuteToolAsync("CreateFeatureclass_management", Geoprocessing.MakeValueArray(arguments.ToArray()));
}
}

为线要素类添加记录

执行的操作包括:

    获取需要编辑的要属类
    创建编辑器
    创建一个MapPoint List,用于存储构建线记录的点
    获取点要素类游标,迭代点要素类的所有记录,将构建线记录的点对象添加至MapPoint List对象中
    使用PolylineBuilder.CreatePolyline()方法,基于MapPoint List对象构建Polyline对象
    使用编辑器对象的Create()方法,基于Polyline对象为线要属类添加一条记录
    执行编辑器操作,保存修改
    完成

具体可参考官方sample ConstructingGeometries_CSharp

//为线要素类中添加记录的函数
//polylineLayer为需要添加记录的线要素图层;pointLayer为用于构建线要素的点图层,也可以执行构建点要素
private Task<bool> ConstructSamplePolylines(FeatureLayer polylineLayer, FeatureLayer pointLayer)
{
// ()=>{...}或()=>functionNmae() 为.NET3.0以后的新特性 Lambda表达式
// 也就是说,执行=>后面的函数,把函数返回值作为一个参数传给当前函数 // execute the fine grained API calls on the CIM main thread
return QueuedTask.Run(() =>
{
// get the underlying feature class for each layer
// as 和is都是强制类型转换运算符。作用类似于C中的 (type)data; 一般使用as,具体不深究
// 此处未将 polylineLayer.GetTable() 强制转换为 FeatureClass 类型后,赋值给 polylineFeatureClass 。
var polylineFeatureClass = polylineLayer.GetTable() as FeatureClass;
var pointFeatureClass = pointLayer.GetTable() as FeatureClass; // retrieve the feature class schema information for the feature classes
var polylineDefinition = polylineFeatureClass.GetDefinition() as FeatureClassDefinition;
var pointDefinition = pointFeatureClass.GetDefinition() as FeatureClassDefinition; // construct a cursor for all point features, since we want all feature there is no
// QueryFilter required
var pointCursor = pointFeatureClass.Search(null, false);
var is3D = pointFeatureClass.GetDefinition().HasZ(); // initialize a counter variable
int pointCounter = 0;
// initialize a list to hold 5 coordinates that are used as vertices for the polyline
var lineMapPoints = new List<MapPoint>(5); // set up the edit operation for the feature creation
var createOperation = new EditOperation()
{
Name = "Create polylines",
SelectNewFeatures = false
}; // loop through the point features
while (pointCursor.MoveNext())
{
pointCounter++; var pointFeature = pointCursor.Current as Feature;
// add the feature point geometry as a coordinate into the vertex list of the line
// - ensure that the projection of the point geometry is converted to match the spatial reference of the line
lineMapPoints.Add(((MapPoint)GeometryEngine.Instance.Project(pointFeature.GetShape(), polylineDefinition.GetSpatialReference()))); // for every 5 geometries, construct a new polyline and queue a feature create
if (pointCounter % 5 == 0)
{
// construct a new polyline by using the 5 point coordinate in the current list
var newPolyline = PolylineBuilder.CreatePolyline(lineMapPoints, polylineDefinition.GetSpatialReference());
// queue the create operation as part of the edit operation
createOperation.Create(polylineLayer, newPolyline);
// reset the list of coordinates
lineMapPoints = new List<MapPoint>(5);
}
} // execute the edit (create) operation
return createOperation.ExecuteAsync();
});
}

待续

ArcGIS Pro SDK 本来不在我的近期学习计划中,只有由于朋友让帮忙做一个小插件所以稍微学了一点,使用上面介绍的东西就搞定了。因此,本系列下一次更新可能遥遥无期……(也说不定直接就太监了呢)


本文参考:

    博客 https://blog.csdn.net/xiangqiang2015/article/details/81741689
    博客 https://blog.csdn.net/DynastyRumble/article/details/104683339
    Esri官方wiki https://github.com/esri/arcgis-pro-sdk/wiki
    Pro API reference https://pro.arcgis.com/en/pro-app/sdk/api-reference/index.html#topic10500.html
    ArcGIS Pro DAML ID Reference https://github.com/Esri/arcgis-pro-sdk/wiki/ArcGIS-Pro-DAML-ID-Reference
    ArcGIS Pro Addin guide https://github.com/Esri/arcgis-pro-sdk/wiki/ProGuide-Build-Your-First-Add-in
    ArcGIS Pro SDK 社区示例 https://github.com/Esri/arcgis-pro-sdk-community-samples

ArcGIS Pro 二次开发的相关教程结束。

《ArcGIS Pro 二次开发.doc》

下载本文的Word格式文档,以方便收藏与打印。