Qtools GIS
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 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.