ArcGIS command扩展:ArcGIS 9.2/C# 2的实现
Zhuotong Nan (南卓铜) ([email protected])
(上节介绍了如何使用VS 2005 C# express来帮助我们生成ArcGIS Command扩展的代码框架。)
下面我们来介绍框架代码各部分的具体含义。
/// <summary>
/// Command that works in ArcMap/Map/PageLayout
/// </summary>
[Guid("eb9b3424-41d4-44e9-8ab0-c4740339db95")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("MyArcGISClassLibrary.BatchedChangeDatasource")]
public sealed class BatchedChangeDatasource : BaseCommand
ArcGIS是基于COM构建的,它只支持同样符合COM规范的动态链接库(Class library)。Guid必须不与任何一个COM组件相同。默认会生成一个新的全局标识符。我们也可以使用ArcGIS菜单里Developer Tools下面的Guid Generator来获取新的标识符(图10)。
ClassInterface和ProgId仍然是作为将.net类库导出成COM需要的一些属性。seal关键词指定了本命令不能再被继承。需要注意的是此类从BaseCommand上继承过来,BaseCommand实现了ICommand接口。9.2里BaseCommand放到ADF.BaseClasses命名空间下面,在之前版本是放在Utilities.BaseClasses下面。同时一些BaseCommand有关的辅助命令现在也放到了ADF.CATIDs空间。Utilities命名空间仍然存在,但当使用该空间下的BaseCommand在编译时会有deprecated的警告。
COM Registration Function(s)折叠起来的代码用于实现DLL到COM的自动注册和卸载过程。对于一般的应用,使用其生成代码就足够。
public BatchedChangeDatasource()构造函数里初始化了一些Command必要的信息,比如该Command所在的类别,这是以后在使用命令的时候存放的类别(如图11),同时也连接了一个命令表示的图标(m_bitmap)。这些变量是其基类BaseCommand里继承过来的。
string bitmapResourceName = GetType().Name + ".bmp";
如果不使用默认图标,则需要更改这里的名称。
Overriden Class Methods区域是需要开发者具体完成实现的地方。OnCreate在命令附加到ArcGIS时调用,一般这里传入的Object就是具体的Application,这里将Application对象存储到m_hookHelper.Hook属性里。通过Hook属性可以与ArcGIS Application进行必要的互访问。默认的OnCreate功能十分简单,只是判断了Application是否打开,Application是否有活动的View,如果有则使本命令可用。更多的判断则需要开发者来实现。
OnClick()是用户在点击Command后进行的操作。本案例中进行批量更改DataSource等功能便需要在这里实现。
此外,框架代码还将Project Build Properties设置成 Register for COM interop。对于手工生成的项目,必须记得设置此项。
图11,Project Build Properties设置
在具体添加代码前,我们先编译一下,看是否有问题。不幸的是默认的框架编译时会提示缺少引用。我们添加引用后(图13)编译通过。
在类最前面添加私有变量声明。
public sealed class BatchedChangeDatasource : BaseCommand
{
private ESRI.ArcGIS.Framework.IApplication _app;
private string _layerNameToBeReplaced = "nwbi3";
_app接管Hook对象保管Application。_layerNameToBeReplaced指定要被替换Data Source的Layer名。同名的Layer会存在于不同的Data Frame里面。
因为我们需要实现的功能只对ArcMap有用,所以需要判断宿主应用程序是否MxApplication,如否,则禁用本命令。将OnCreate里的下面代码:
if (m_hookHelper == null)
base.m_enabled = false;
else
base.m_enabled = true;
更改成:
if (m_hookHelper == null)
base.m_enabled = false;
else
{
_app = hook as ESRI.ArcGIS.Framework.IApplication;
if (_app is ESRI.ArcGIS.ArcMapUI.IMxApplication)
base.m_enabled = true;
}
现在主要的功能是在OnClick里实现。要完成批量替代Data Source的功能,一般的思路是这样:在当前的Document(一个.Mxd文件即Document)里找到各个Data Frame(在ArcObjects,每个Data Frame就是一个Map),迭代各Map。在每个Map里迭代各Layer,判断此Layer是否具备指定的_layerNameToBeReplaced的名称,如是,将该Layer的Data Source代换成指定的。为了实现时间序列的代换,则还需要先形成目标Dataset Name的List,然后逐一代换。以下代码实现了将一个给定Layer Name的Feature层替换成固定的某个存储在File Geodatabase里的FeatureClass。targetGDBpath指定File Geodatabase的路径,具体的FeatureClass由 fcn.Name指定。如果此FeatureClass是存储在Geodatabase里的某个Feature Dataset里,可由fcn.FeatureDatasetName来指定。最终的Data Source的更换由IDataLayer.Connect来实现。
public override void OnClick()
{
ESRI.ArcGIS.ArcMapUI.IMxDocument pDoc = (ESRI.ArcGIS.ArcMapUI.IMxDocument)_app.Document;
if (pDoc == null) return;//_layerNameToBeReplaced ="nwbi3";
string targetGDBpath = "c:tempfileGDB.gdb";ESRI.ArcGIS.Carto.IMaps maps = pDoc.Maps;
ESRI.ArcGIS.Carto.IMap map;
ESRI.ArcGIS.Carto.ILayer layer;
ESRI.ArcGIS.Carto.IDataLayer dLayer;
ESRI.ArcGIS.Geodatabase.FeatureClassNameClass fcn = new ESRI.ArcGIS.Geodatabase.FeatureClassNameClass();string conTofGDB = @"DATABASE= " + targetGDBpath;
ESRI.ArcGIS.Geodatabase.IDataset ds = fGDBWorkspaceFromString(conTofGDB) as ESRI.ArcGIS.Geodatabase.IDataset;
ESRI.ArcGIS.Geodatabase.IWorkspaceName wsName = ds.FullName as ESRI.ArcGIS.Geodatabase.IWorkspaceName;
fcn.WorkspaceName = wsName;
//IDatasetName dsName = ds.Subsets.Next().FullName as IDatasetName;
//fcn.FeatureDatasetName = dsName;
fcn.Name = "NWBI3";for (int i = 0; i < maps.Count; i++)
{
//each data frame
map = maps.get_Item(i);
for (int j = 0; j < map.LayerCount; j++)
{
layer = map.get_Layer(j);
if (layer.Name == _layerNameToBeReplaced)
{
dLayer = layer as ESRI.ArcGIS.Carto.IDataLayer;
if (dLayer != null)
{bool rev = dLayer.Connect(fcn);
}
}}
}pDoc.ActiveView.Refresh();
}
COM风格的编程经常会看到很多的接口,不同的接口间不停的转换。其实接口一般是按功能进行分类,一个类通常实现了多个接口。比如FeatureLayer会实现不同的例如IDataLayer, IFeatureLayer, IDataset等,IDataLayer负责与数据源有关的操作,IFeatureLayer负责跟具备FeatureClass有关的操作,而IDataset实现了FeatureLayer作为Dataset有关的一些操作。
对Interface的熟悉应用建立在对相关类熟知的基础上。.net help十分全面,查找某接口,可以回溯到应用该接口的全面Class,而每个Class也会给出其实现的各个接口。通过这种方法,我们可以在一定程度上知道哪些接口是可以相互转换的。比如,IMap.get_Layer()查找帮助我们知道是返回ILayer,再结合我们期望是找到一个FeatureLayer,所以寻找FeatureLayerClass的实现的接口,发现其与数据源有关的操作封装在IDataLayer和IDataLayer2里。于是我们将layer cast成IDataLayer,再进行Connect操作。
以上代码中,fGDBWorkspaceFromString方法实现如下,作用是通过构建连接字符串来生成IWorkspace。
public ESRI.ArcGIS.Geodatabase.IWorkspace fGDBWorkspaceFromString(String connectionString)
{
ESRI.ArcGIS.Geodatabase.IWorkspaceFactory2 workspaceFactory;
workspaceFactory = (ESRI.ArcGIS.Geodatabase.IWorkspaceFactory2)new ESRI.ArcGIS.DataSourcesGDB.FileGDBWorkspaceFactoryClass();
return workspaceFactory.OpenFromString(connectionString, 0);
}
至此,我们完成了一个很简单的可运行的Command扩展。回到构造函数,将m_category赋为“tong’s Extensions”。编译后,打开ArcMap,打开tools菜单下的Customize…,弹出窗口如图14。左边的Categories可以找到tong’s Extensions,右边的Commands可以看到我们的BatchedChangeDatasource。选定该命令,可以拖拉到任何工具栏。Close此Customize窗口后,命令就可以如内置一样运行。
剩余的问题
对前面设定的期望目标,我们可以通过以下方法形成一个时间序列Dataset的名称。
private List<string> TargetRasterNames()
{
//DateTime start = new DateTime(1997, 1, 27, 0, 0, 0);
DateTime start = new DateTime(2002, 8, 19, 0, 0, 0);
//DateTime end = new DateTime(1997, 1, 29, 0, 0, 0);
DateTime end = new DateTime(2002, 8, 20, 0, 0, 0);
List<string> rev = new List<string>();
for (DateTime dt = start; dt < end; dt = dt.AddHours(1) )
{
//string dtStr = string.Format("c{0:MMddHH}c_3hm", dt);
string dtStr = string.Format("{0:yyyyMMddHH}.asc", dt);
rev.Add(dtStr);
}
return rev;}
在Connect的时候,根据次序可以赋给不同的RasterName。在更换数据源时,如果确保都在同一Workspace下,则可以通过下面代码简单代换。
if (layer.Name==_layerNameToBeReplaced)
{
dLayer= layer as IDataLayer;
if (dLayer!=null )
{
IRasterDatasetName pName = dLayer.DataSourceName as IRasterDatasetName;
IDatasetName dsName = pName as IDatasetName;
ii++;
dsName.Name = targets[ii];
}
}
如果不在同一Workspace,IName还需要指定IWorkspaceName。
结论
本文结合实际工作的一个案例,演示了如何应用Visual Studio 2005 C# Express来开发扩展ArcGIS Command。以上演示还有很多需要改进的地方,比如要代换的Layer名,不能内置在代码里,我们可以通过生成一个Dialog让使用者来决定替换哪一层,再比如,我们可以设置更漂亮的图标以区别于已有的图标。但这些都已经不是本tutorial的内容。本tutorial的目的是希望大家通过阅读本文可以初步掌握如何使用C#进行ArcGIS扩展,大家更多需要关注的是OnClick里的Business logic。
有什么问题有与我联系或留言。转载请保留帖子全部信息。
此操作一定要安装ARC GIS吗,只装engine9.2为什么找不到ESRI.ArcGIS.Framework这个引用呢?
engine支持的ao只是整个ao的一个子集,所以不完整,需要arcgis desktop的支持