ArcGIS Pro 二次開發
- 2020 年 9 月 2 日
- 筆記
- ArcGIS Pro
本文基於 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 本來不在我的近期學習計劃中,只有由於朋友讓幫忙做一個小插件所以稍微學了一點,使用上面介紹的東西就搞定了。因此,本系列下一次更新可能遙遙無期……
本文參考:
- 部落格 //blog.csdn.net/xiangqiang2015/article/details/81741689
- 部落格 //blog.csdn.net/DynastyRumble/article/details/104683339
- Esri官方wiki //github.com/esri/arcgis-pro-sdk/wiki
- Pro API reference //pro.arcgis.com/en/pro-app/sdk/api-reference/index.html#topic10500.html
- ArcGIS Pro DAML ID Reference //github.com/Esri/arcgis-pro-sdk/wiki/ArcGIS-Pro-DAML-ID-Reference
- ArcGIS Pro Addin guide //github.com/Esri/arcgis-pro-sdk/wiki/ProGuide-Build-Your-First-Add-in
- ArcGIS Pro SDK 社區示例 //github.com/Esri/arcgis-pro-sdk-community-samples