本文基于 Windows7 + VS2019 + .NET Framework 4.8 + ArcGIS Pro 2.5.22081 开发和撰写。
开发环境配置
获取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安装路径\bin
和Pro安装路径\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