Part 6: Converting VBA ArcObjects to CS

Tutorial Navigation | Previous: Part 5: Adding the Utility’s Dialog Form | Next: Part 7: Deploying the Add-in

Continuing with the Visual Studio project from Part 5, we will now perform a mostly line-by-line conversion of the ArcObjects code used by the VBA utility.

Note: At this stage, no attempt will be made to conform to all best practices for C# coding, such as class and method extraction, and robust and specific exception handling.

Resources

Files

See Part 4.

Video Demonstrations

The following videos are also linked from the relevant sections below. (opens in a new browser window or tab)

Paste VBA code into C# code editor (duration 1:04)
Start converting variable declarations (duration 0:43)
Adding references and using statements while converting (duration 0:53)
Declaring a variable and assigning it a new object (duration 0:35)

Convert the VBA Code to C#

Paste the VBA code into Visual Studio

Video Demonstation: Paste VBA code into C# code editor (duration 1:04)

During the ArcObjects VBA to C# conversion process, it is helpful to have the VBA code in the Visual Studio code editor, formatted as comments.

We could place the operational code in the OnClick() method, but to keep that method simple and cut down on indent levels, we will create a separate method for our ArcObjects code.

Select the StaggerOffsetButton.cs file in the code editor, locate the OnClick() method, scroll down and click to the right of the method’s closing brace, press Enter twice, and create a new method named Perform:

56
57
58
59
private void Perform()
{
    // Paste VBA Code here, and then comment it out.
}

For those of you new to C#… (+/-)

Copy and paste the entire contents of the VBA_OffsetFeatures.txt file provided with this tutorial into the new method. Select the pasted-in code and comment it out by clicking the Comment Selection button (or View | Advanced | Comment Selection). With the commented-out VBA code still selected, press the TAB key three times to indent it correctly.

Expand this to view the entire Perform() method after pasting in the code and commenting it out. (+/-)

Convert the variable declarations

Video Demonstation: Start converting variable declarations (duration 0:43)

In most VBA code, variables are declared at the top of subs and functions. In C#, the convention is to declare variables on first use. To keep this tutorial as simple as possible, a line-by-line conversion will be performed, keeping variable declarations at the top before operational code.

Variable names, however, will be changed to the C# convention of lower-case first letter and camel case, fully spelled-out names (e.g. pMxDoc -> mxDocument).

During this process, references to assemblies and their corresponding using statements will be added as needed.

How do I add references and using statements? (+/-)

Video Demonstation: Declaring a variable and assigning it a new object (duration 0:35)

Working in the Perform() method in the StaggerOffsetButton.cs file in the code editor.

Following is the line-by-line conversion of the VBA code variable declarations to C#.

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
...
//    Dim sToolName As String
//    sToolName = "Stagger Offset Features"
string toolName = "Stagger Offset Features";
 
//    Dim pMxDoc As IMxDocument
IMxDocument mxDocument;
 
//    Dim pLayer As ILayer
ILayer layer;
 
//    Dim pFeatureLayer As IFeatureLayer
IFeatureLayer featureLayer;
 
//    Dim pCursor As IFeatureCursor
IFeatureCursor featureCursor;
 
//    Dim pID As New UID
UID editorUID = new UIDClass();
 
//    Dim pEditor As IEditor
IEditor editor;
 
//    Dim pFeature As IFeature
IFeature feature;
 
//    Dim pFeatureEdit As IFeatureEdit
IFeatureEdit featureEdit;
 
//    Dim pLine As ILine
ILine line;
 
//    Dim pMoveSet As ISet
ISet moveSet;
 
//    Dim pSpatialReference As ISpatialReference
ISpatialReference spatialReference;
 
//    Dim pStartPoint As IPoint
IPoint startPoint = null;
 
//    Dim pEndPoint As IPoint
IPoint endPoint = null;
 
//    Dim origX As Double
double originX;
 
//    Dim origY As Double
double originY;
 
//    Dim bInOperation As Boolean
// Not needed.
 
//    Dim Dlg_Result As String
// Not needed.
 
//    Dim Offset_SizeX As Integer
// Change to double.
double offsetSizeX;
 
//    Dim Offset_SizeY As Integer
// Change to double.
double offsetSizeY;
 
//    Dim DistanceX As Integer
// Change to double.
double distanceX;
 
//    Dim DistanceY As Integer
// Change to double.
double distanceY;
 
//    Dim StaggerIndex As Integer
int staggerIndex;
 
//    Dim F_Count As Long
int featureCount;
 
//    On Error GoTo ErrorHandler
// Use C# exception handling.
...

The above declared ArcObjects required the following using statements and references to the assemblies added to the project.

5
6
7
8
9
10
11
12
...
using ESRI.ArcGIS.ArcMapUI;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Editor;
using ESRI.ArcGIS.Geometry;
...

Convert the operational code

Continue working in the Perform() method in the StaggerOffsetButton.cs file in the code editor.

Following is the mostly line-by-line conversion of the VBA operational code to C#. Some sections are converted as blocks for clarity.

132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
...
//    Set pMxDoc = ThisDocument
mxDocument = ArcMap.Document;
 
//    Set pLayer = pMxDoc.SelectedLayer
layer = mxDocument.SelectedLayer;
 
//    ' Make sure some type of layer is selected
//    If (pLayer Is Nothing) Then
//        MsgBox "Please Select a feature layer to offset", vbOKOnly, sToolName
//        Exit Sub
//    End If
if (layer == null)
{
    MessageBox.Show("Please Select a feature layer to offset", toolName, MessageBoxButtons.OK);
    return;
}
 
//    ' Make sure a feature layer (not a group layer) is selected
//    If Not (TypeOf pLayer Is IFeatureLayer) Then
//        MsgBox "Please Select a feature layer", vbOKOnly, sToolName
//        Exit Sub
//    End If
if (!(layer is IFeatureLayer))
{
    MessageBox.Show("Please Select a feature layer to offset", toolName, MessageBoxButtons.OK);
    return;
}
 
//    Set pFeatureLayer = pMxDoc.SelectedLayer
//    Set pLayer = pFeatureLayer
featureLayer = (IFeatureLayer)layer;
 
//    ' Get count of features
//    F_Count = pFeatureLayer.FeatureClass.FeatureCount(Nothing)
featureCount = featureLayer.FeatureClass.FeatureCount(null);
 
//    ' Display a form to get offset values
//    form_OffsetFeatures.pPrompt.Caption = "Move " & F_Count & " features in:" & _
//            vbNewLine & pFeatureLayer.Name
//    form_OffsetFeatures.Show
//    If form_OffsetFeatures.Canceled Then
//        Exit Sub
//    Else
//        Offset_SizeX = form_OffsetFeatures.MoveDistX
//        Offset_SizeY = form_OffsetFeatures.MoveDistY
//    End If
 
// Set the Layer Info
string layerInfo = "Move " + featureCount.ToString() + " features in:\n" + featureLayer.Name;
 
// Create the form
frmStaggerOffset staggerOffsetDialog = new frmStaggerOffset(layerInfo);
 
// Display the form as a modal dialog
staggerOffsetDialog.ShowDialog();
 
// Check if form was cancelled
if (staggerOffsetDialog.DialogResult != DialogResult.OK)
{
    // abort
    return;
}
 
// Get the offset values from the form
offsetSizeX = staggerOffsetDialog.OffsetX;
offsetSizeY = staggerOffsetDialog.OffsetY;
 
 
 
//    ' Get a reference to the editor extension
//    pID = "esriEditor.Editor"
//    Set pEditor = Application.FindExtensionByCLSID(pID)
editorUID.Value = "esriEditor.Editor";
editor = ArcMap.Application.FindExtensionByCLSID(editorUID) as IEditor;
 
 
//    ' Create an edit operation enabling undo for the operation
//    pEditor.StartOperation
//    bInOperation = True
editor.StartOperation();
 
// Use C# exception handling to abort edit operation if not completed
try
{
 
    //    Set pCursor = pFeatureLayer.FeatureClass.Update(Nothing, False)
    featureCursor = featureLayer.FeatureClass.Update(null, false);
 
    //    Set pFeature = pCursor.NextFeature
 
    //    ' Cause the staggered offset to be centered on original position
    //    StaggerIndex = F_Count / -2
    staggerIndex = featureCount / -2;
 
    //    While (Not pFeature Is Nothing)
    while ((feature = featureCursor.NextFeature()) != null)
    {
        //        ' Select the feature
        //        pMxDoc.FocusMap.SelectFeature pLayer, pFeature
        mxDocument.FocusMap.SelectFeature(layer, feature);
 
        //        Set pMoveSet = New esriSystem.Set
        moveSet = new SetClass();
 
        //        pMoveSet.Add pFeature
        moveSet.Add(feature);
 
        //        ' Reset the Set
        //        pMoveSet.Reset
        moveSet.Reset();
 
        //        ' MoveSet requires a line to specify the new location
        //        ' Use the selection anchor as a starting point for the line
        //        Set pStartPoint = pEditor.SelectionAnchor.Point
        //        Set pLine = New Line
        //        pStartPoint.QueryCoords origX, origY
        //        Set pEndPoint = New Point
        startPoint = editor.SelectionAnchor.Point;
        line = new LineClass();
        startPoint.QueryCoords(out originX, out originY);
        endPoint = new PointClass();
 
        //        ' Calculate the total offset for the selection
        //        ' Uses linear unit of data frame PCS
        //        DistanceX = Offset_SizeX * StaggerIndex
        //        DistanceY = Offset_SizeY * StaggerIndex
        distanceX = offsetSizeX * staggerIndex;
        distanceY = offsetSizeY * staggerIndex;
 
        //        pEndPoint.PutCoords (origX + DistanceX), (origY + DistanceY)
        //        pLine.PutCoords pStartPoint, pEndPoint
        endPoint.PutCoords((originX + distanceX), (originY + distanceY));
        line.PutCoords(startPoint, endPoint);
 
        //        ' Get the spatial reference from the map and assign it to the new line
        //        Set pSpatialReference = pEditor.Map.SpatialReference
        spatialReference = editor.Map.SpatialReference;
 
        //        ' Set the spatial reference of the new line
        //        Set pLine.SpatialReference = pSpatialReference
        line.SpatialReference = spatialReference;
 
        //        ' Do the move while looping through the set
        //        Set pFeatureEdit = pMoveSet.Next
        //        Do While Not pFeatureEdit Is Nothing
        //          ' Move all the selected features
        //          pFeatureEdit.MoveSet pMoveSet, pLine
        //          Set pFeatureEdit = pMoveSet.Next
        //        Loop
        while ((featureEdit = moveSet.Next() as IFeatureEdit) != null)
        {
            // Move all the selected features
            featureEdit.MoveSet(moveSet, line);
        }
 
        //        pMoveSet.RemoveAll
        moveSet.RemoveAll();
 
        //        pMxDoc.FocusMap.ClearSelection
        mxDocument.FocusMap.ClearSelection();
 
        //        Set pFeature = pCursor.NextFeature
        //        StaggerIndex = StaggerIndex + 1
        staggerIndex += 1;
 
        //    Wend
    }
 
    //    ' Stop the Edit Operation
    //    pEditor.StopOperation "Move Selection"
    editor.StopOperation("Move Selection");
    //    bInOperation = False
}
catch (Exception)
{
    editor.AbortOperation();
    // Re-throw the exception so that the outer handler reports it,
    // and this method is exited.
    throw;
}
 
//    pMxDoc.ActiveView.Refresh
mxDocument.ActiveView.Refresh();
 
//    ' Additionally move the selection anchor
//    pEditor.SelectionAnchor.MoveTo pEndPoint, pEditor.Display
if (endPoint != null)
{
    editor.SelectionAnchor.MoveTo(endPoint, editor.Display);
}
 
//    Exit Sub
 
//ErrorHandler:
//  If bInOperation Then
//    pEditor.AbortOperation
//    MsgBox "Error moving features."
//  End If
//End Sub
...

Test and debug the new code

Continue working with the StaggerOffsetButton.cs file in the code editor, locate the OnClick() method and edit its contents to the following code:

26
27
28
29
30
31
32
33
34
35
36
37
protected override void OnClick()
{
    try
    {
        // Run the converted VBA ArcObjects code.
        Perform(); 
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message + "\n\n" + ex.StackTrace);
    } 
}

Start debugging (press green arrow button or F5).

You will likely receive an error message:
The type ESRI.ArcGIS.Display.IAnchorPoint is defined in an assembly that is not referenced.

Add the ESRI.ArcGIS.Display assembly to the project to correct the error.

Start debugging again.

Wait for ArcMap to load. Once it opens, open the provided VBAtoCS_Tutorial_01_v10.mxd map. Your toolbar and button should be where you previously placed on a tool panel.

Select the Hwy_94_Overlaps layer and start an edit session.

Click the Add-In’s button to display the dialog form. If you have not selected a feature layer you should get a message box from the Add-In.

Enter 350 for the East-West Route value and click the OK button.

You can use ArcMap’s Undo button to return the features to their original position, then run the Add-In again and try different values.

When you are done testing, end the edit session and close ArcMap to return to Visual Studio and stop debugging.

Tutorial Navigation | Previous: Part 5: Adding the Utility’s Dialog Form | Next: Part 7: Deploying the Add-in

  1. #1 by Joe on November 14, 2011 - 6:53 am

    As someone who is just beginning the conversion of in-house vba to c#, this is great stuff! No doubt it will help many more as time ticks away on vba in Arc. I’m wondering if you could post a bit about the best practices in C#, for people like me who have no business programming but who were led to it by GIS. Just a thought. Thanks for the tutorial.

(will not be published)