What separates a good programmer from a great programmer is often the ability to implement error handling that catches an error and exits the program cleanly, thus avoiding system crashes and unwanted changes to a drawing.
The ability to predict where something might go wrong in your program can help you locate potential problems—errors or bugs, as programmers commonly refer to them. If you hang around any programmers, you might have heard the term debugging; it is the industry-standard term used for the process of locating and resolving problems in a program. Conditional statements can be used to identify and work around potential problems by validating values and data types used in a program.
Once you have tested a program for potential problems and handled the errors generated, you are ready to deploy the program for use.
The VBA programming language supports two statements that are designed to assist in handling errors. The On Error
and Resume
statements allow you to execute your code and specify the code statements that should be executed if an error occurs. Along with these two statements, the Err
object can be used to get information about the error that was generated. You can use this information for evaluation and error handling or, when necessary, to pass an error forward from a custom function for the calling program to evaluate and handle.
For example, you might have a procedure that works with a text file and accepts a string that contains the file it should work with. If the procedure is passed a string but it doesn't represent a proper filename, your procedure should handle the error but also raise the error so that the calling procedure can use the error handling of the VBA programming language to continue.
There is nothing more frustrating to end users than a program that misbehaves or terminates without warning and provides them with no information about what went wrong. No program is ever perfect, but you should make every attempt to ensure the users of your custom program the best possible experience by adding proper error handling.
The On Error
statement is what you will be using to catch and handle any errors that occur during the execution of a custom procedure. There are two variants of the On Error
statement:
On Error Resume Next
instructs VBA to ignore any error it encounters and continue execution with the next code statement.On Error GoTo
<Line Number or Label>
instructs VBA to move execution to a specific label or line number in the current procedure when an error occurs.The On Error Resume Next
statement is the most frequently used variant of the On Error
statement, as you typically want to execute the next code statement in a procedure to try to recover from the error.
The following is an example of a procedure that tries to get a layer named ACP-Doors
:
Private Function GetLayerACP_Doors() As AcadLayer
Set GetLayer = ThisDrawing.Layers("ACP-Doors")
End Function
In this procedure, if the layer doesn't exist, the function suddenly terminates and VBA displays an error message. This isn't ideal because the VBA program doesn't perform the expected task and the default error message displayed isn't helpful (see Figure 13.1).
If the On Error Resume Next
statement is added to a procedure and inserted before a code statement that could generate an error, no error message is displayed. The procedure that calls the GetLayerACP_Doors
procedure would need to check the value that is returned for a valid AcadLayer
object or a value of Nothing
. Here is what the function would look like with an On Error Resume Next
statement added:
Private Function GetLayerACP_Doors() As AcadLayer
On Error Resume Next
Set GetLayer = ThisDrawing.Layers("ACP-Doors")
End Function
The On Error GoTo
<Line Number or Label>
statement can be helpful when you don't want execution to continue to the next code statement in a procedure after an error occurs. The statement On Error GoTo
<Line Number>
is used to move execution to a code statement within the procedure that starts with a specified line number. VBA does not automatically assign line numbers, and not all code statements need to have a line number. To use this statement, you must manually enter the line number in front of each code statement that should have a line number. The lines don't need to be numbered sequentially, but the numbers specified must be greater than 0; a line number of 0 indicates that error handling should be disabled in the procedure.
The following shows an example of the On Error GoTo
<Line Number>
statement. An error is generated in the procedure when the Add
method of the AcadLayers
collection object is executed as a result of the < and > characters, which are not valid, in the layer name. When the error occurs, execution is moved to the code statement with line number 9 to its left.
Public Sub CreateLayer()
1 On Error GoTo 9
Dim sName As String
3 sName = "<Bad Name>"
5 ThisDrawing.Layers.Add sName
MsgBox "Layer " & sName & " was added."
Exit Sub
9 Err.Clear
ThisDrawing.Utility.Prompt _
vbLf + "Error: Layer couldn't be created."
GoTo 1
End Sub
As an alternative to using line numbers to specify a location within a procedure, you can use labels that have more meaningful names than 1, 5, 9, and so on. A label starts on a new line in a procedure and ends with a colon. For example, you could create labels with the names LayerNotFound
, BadName
, and ErrHandler
. LayerNotFound
might contain code statements that should be executed if a layer wasn't found, BadName
might contain code statements to handle an invalid name passed to the Add
method of the AcadLayers
collection, and ErrHandler
might be a generic label to handle all other errors that are generated. Although multiple labels can be used in a procedure, only one can be used with the On Error GoTo <Label>
statement in a procedure.
The following shows an example of the On Error GoTo
<Label>
statement. In this case, an error could be generated by the code statement Set oLayer = ThisDrawing.Layers(sName)
as a result of the layer ACP-Door
not being found in the drawing. When the error occurs, execution is moved to the label LayerNotFound
in the procedure where the layer is added to the drawing. The newly added layer is assigned to the oLayer
variable. Once the layer is added, execution is returned to the oLayer.color = acBlue
statement using the Resume Next
statement.
Public Sub GetLayer()
On Error Resume Next
Dim sName As String
sName = "ACP-Door"
On Error GoTo LayerNotFound
Dim oLayer As AcadLayer
Set oLayer = ThisDrawing.Layers(sName)
oLayer.color = acBlue
Exit Sub
LayerNotFound:
Set oLayer = ThisDrawing.Layers.Add(sName)
Resume Next
End Sub
The Err
object is part of the VBA programming language, and it holds information about the most recent error that was generated during the execution of a procedure. If you want to learn more about the Err
object, you can look up ErrObject
(not Err
) in the VBA Help system or the Object Browser of the VBA Editor. You can use a combination of the On Error
and If
statements to determine whether an error occurred. The value of the Err
object's Number
property is 0 by default, and is changed to a nonzero number when an error occurs.
The value of the Number
property isn't always very helpful or decipherable by us humans. For example, the value of 5 could mean “Invalid procedure call or argument” for one software vendor's object library but have a different meaning for another library from a different vendor. You will want to contact the software vendor or use your favorite Internet search engine to see if you can obtain a listing of error values and their meaning. For humans, the Err
object also has a Description
property. The Description
property of the Err
object provides a basic explanation of the error that occurred, but even this can be a bit cryptic if you don't understand the terminology used.
The following example first tries to get the layer 10101 in the AcadLayers
collection object of the current drawing. If the layer exists, no error is generated and nothing happens. If the layer doesn't exist, an error is returned and the code statements in the If
statement are executed.
Private Sub CreateLayer10101()
On Error Resume Next
Dim obj As AcadLayer
Set obj = ThisDrawing.Layers("10101")
If Err.Number <> 0 Then
MsgBox "Number: " & CStr(Err.Number) & vbLf & _
"Description: " & Err.Description
ThisDrawing.Layers.Add "10101"
End If
End Sub
The first time the procedure is executed, an error occurs and a message, shown in Figure 13.2, is displayed, indicating that the key (the layer in this case) wasn't found. As part of the If
statement, the layer is added to the drawing, and executing the procedure a second time results in no error or message being displayed because the layer already exists.
Table 13.1 lists the other properties of the Err
object that can be used to get information about the most recent error.
Table 13.1 Err
object–related properties
Property | Description |
HelpContext |
Specifies a long value that represents the context ID of a help topic in the help file specified by the HelpFile property related to the error. |
HelpFile |
Specifies a string value that represents the help file in which information can be found about the error. |
LastDLLError |
Returns a long value that contains an error code if the error was generated at the operating system level. This property is read-only. |
Source |
Specifies a string value that represents the application or object library in which the error occurred. |
The Err
object supports two methods: Clear
and Raise
. The Clear
method allows you to reset the Err
object to its default state so that you can continue execution of your procedure and handle additional errors. Though not used as frequently, the Raise
method can be used to generate an error from a custom procedure within a program. The error that is generated can then be caught with the On Error
statement by the calling procedure.
The following example shows two custom procedures, a subroutine and a function, that are used to create a new layer. The On Error
and Err
objects are used to handle the errors that might occur.
Public Sub AddLayer()
On Error Resume Next
' Call the CreateLayer function with a bad layer name
CreateLayer "<BadName>", acBlue
' If an error occurs in the CreateLayer function,
' display a message
If Err.Number <> 0 Then
MsgBox "Number: " & CStr(Err.Number) & vbLf & _
"Description: " & Err.Description
End If
End Sub
' Creates a new layer and returns the AcadLayer object
Private Function CreateLayer(sName As String, _
nClr As ACAD_COLOR) As AcadLayer
On Error Resume Next
' Try to get the layer first and return it if it exists
Set CreateLayer = ThisDrawing.Layers(sName)
' If layer doesn't exist create it
If Err.Number <> 0 Then
Err.Clear
On Error GoTo ErrHandler
Set CreateLayer = ThisDrawing.Layers.Add(sName)
CreateLayer.color = nClr
End If
' Exit the function if it gets this far
Exit Function
' If an error occurs when the layer is created, raise an error
ErrHandler:
Err.Raise Err.Number, Err.Source, Err.Description, _
Err.HelpFile, Err.HelpContext
End Function
In the previous example, the AddLayer
procedure passes the CreateLayer
procedure a name and color. The layer name that is passed is invalid and causes an error to occur in the Add
method of the AcadLayers
collection object. The On Error Resume Next
statements are used to keep execution going, whereas the On Error GoTo
<Label>
statement allows execution to be moved to the general error handler. The general error handler in the CreateLayer
procedure uses the Raise
method of the Err
object to pass an error up to the AddLayer
procedure so that it can handle what should be done next.
Debugging is a process that steps through a program and inspects either each code statement—one at a time—or an entire procedure and looks for problems in the code. Maybe your procedure expects a string and instead it is passed a numeric value that generates an error; debugging can be helpful in figuring out just where the problematic value is coming from in your program.
The tools that you can use to debug your code range from simply displaying a message to employing the more integrated solutions found in the VBA Editor. Displaying messages at the Command prompt or in a message box can be a low-tech solution and allow nondevelopers to provide you with troubleshooting information as they use your custom programs. The debugging tools that the VBA Editor offers are more efficient at debugging problems when compared to putting code statements in your program to display messages to the user.
One of the simplest forms of debugging a program is to display messages at the AutoCAD® Command prompt or in a message box during execution. These messages are displayed periodically as your program executes to let you know which code statements are about to be executed next. You can think of this form of debugging much like the children's game “Red Light, Green Light.” Every so often you use a unique message in your program so you have an understanding of progress during the execution of the program. In the game “Red Light, Green Light,” you randomly shout out “Red Light” or “Green Light” to ensure people are paying attention and to keep the game moving.
To debug through messages, you place a messaging function every 5 to 10 statements in a custom procedure; place the debugging messages too frequently (or infrequently), and they become less useful. The Prompt
method of the AcadUtility
object and the VBA MsgBox
functions are the most commonly used techniques for displaying a message; I tend to lean away from the MsgBox
function as it adds unnecessary extra steps to executing a procedure. Once debugging is completed, you can comment out the messaging code statements so they are not displayed to the end user.
The following is an example of a custom procedure that contains two errors and demonstrates how messaging functions can be used to help identify the bad statements. You will step through this code as part of the exercise later in this chapter under the “Stepping Through the BadCode VBA Project” section.
Sub BadCode()
' Prompt for string
Dim sVal As String
sVal = ThisDrawing.Utility.GetString(True, vbLf & "Enter a string: ")
' If str is not empty, continue
If IsEmpty(sVal) = False Then
ThisDrawing.Utility.Prompt vbLf & "DEBUG: Inside IF"
' Error 1, - should be &
ThisDrawing.Utility.Prompt vbLf & "Value entered: " - sVal
' Prompt for integer
Dim nVal As Integer
nVal = ThisDrawing.Utility.GetInteger(vbLf & "Enter an integer: ")
ThisDrawing.Utility.Prompt vbLf & "DEBUG: Ready to divide"
' Error 2, if the user enters 0, cannot divide by 0
ThisDrawing.Utility.Prompt vbLf & "Divisor: " & CStr(2 / nVal)
Else
ThisDrawing.Utility.Prompt vbLf & "DEBUG: If…Else"
End If
ThisDrawing.Utility.Prompt vbLf & "DEBUG: Outside IF"
End Sub
If you execute the previous example, the following prompts are displayed the first time you execute the procedure:
Enter a string: Hello World!
DEBUG: Inside IF
If you change "Value entered: " - sVal
to "Value entered: " & sVal
and execute the procedure again, the following messages are displayed at the AutoCAD Command prompt if 0 is entered when prompted for an integer:
DEBUG: Inside IF
Value entered: Hello World!
Enter an integer: 0
DEBUG: Ready to divide
If a value other than 0 is entered, the following messages are displayed:
DEBUG: Inside IF
Value entered: Hello World!
Enter an integer: 2
DEBUG: Ready to divide
Divisor: 1
DEBUG: Outside IF
Although the On Error
statement and Err
object are useful in catching and handling errors during execution, the VBA Editor offers several tools that can be helpful in determining what happened during the execution of a procedure that led to an error. The VBA Editor offers the following features that can be used to debug a program:
The Immediate window of the VBA Editor allows you to view the debug output from a program. You can display the Immediate window by pressing Ctrl+G or by clicking Immediate Window in the View menu or Debug toolbar. Although the Immediate window is used primarily to output debug information, it can also be used to execute a single code statement. To execute a code statement in the Immediate window, type a code statement such as MsgBox ThisDrawing.WindowTitle
and press Enter.
When you use the VBA Debug
object, the resulting values and messages are output to the Immediate window, where they aren't visible to the user running the VBA project from the AutoCAD user interface. The Debug
object supports two output methods: Print
and Assert
. The Print
method accepts most data types with the exception of an object and displays the value to the Immediate window.
The following shows an example of using the Print
method to output values and messages to the Immediate window:
Sub DivByZeroDebug()
Debug.Print "Start DivByZeroDebug"
Dim nVal As Integer, nDiv As Integer
nVal = ThisDrawing.Utility.GetInteger(vbLf & "Enter an integer: ")
Debug.Print "User entered: " & nVal
If nVal <> 0 Then
nDiv = nVal / 2
MsgBox nDiv
Debug.Print "Calculated value: " & nDiv
End If
Debug.Print "End DivByZeroDebug"
End Sub
Figure 13.3 shows the results of the Debug.Print
statements in the Immediate window before the error occurred. Using this technique, you can then deduce that the error occurred after the last message output in the Immediate window and before the next Debug.Print
statement.
The VBA Editor enables you to step through a program while it is being executed with the use of a feature known as breakpoints. Breakpoints allow you to specify a position in a VBA program at which execution should be suspended. While the program is suspended, you can check the current values of a variable and move execution forward one code statement at a time using the code stepping tools, where execution is currently suspended.
While you are in the code editor window, you can set breakpoints quickly by doing any of the following:
When a breakpoint is placed, a circle is displayed in the left margin and the code statement is highlighted; see Figure 13.4. (By default, the circle and highlight are maroon colored; you can change the color using the Options dialog of the VBA Editor.) Click a breakpoint that is set in the left margin to remove it.
Once one or more breakpoints have been set, you can execute the procedure from the VBA Editor or the AutoCAD user interface with the vbarun
command. Execution starts and is suspended when the first breakpoint is encountered. The VBA Editor moves to the foreground and the code editor receives focus when execution is suspended. A yellow arrow—known as the execution point—and highlighted code indicate the code statement that will be executed next (see Figure 13.4).
While execution is suspended, you can position the cursor over a variable to see its current value in a tooltip (see Figure 13.4). You can also see the current values of all variables in the current procedure using the Locals window or those that are being watched in the Watches window. I discuss the Locals and Watches windows in the next section.
Execution of the procedure can be continued by stepping into, over, or out of a code statement. To step through the code statements of a procedure, you choose one of the following options on the Debug menu or toolbar.
Many people like to watch birds or go whale watching. As a programmer, I have often found watching variable values enjoyable. The VBA Editor allows you to view the current value of one or more variables or see the result of a code statement while a program is executing. It can also let you know when the value of a variable changes or evaluates to True
.
In the VBA Editor, either the Locals window or the Watches window can be used to view the current value of the variables in a procedure while execution is suspended using a breakpoint or when an assertion occurs:
True
. Display the Watches window by choosing Watch Window from the View menu or by clicking the Watches Window icon on the Debug toolbar.True
or changes while executing the program.You can modify a watch by selecting it in the Watches window and right-clicking. When the context menu opens, choose Edit Watch (to make changes to the watch entry) or Delete Watch (to remove the watch).
After you have spent countless hours, days, or even weeks writing a program, handling errors, and debugging a VBA program, it all comes down to deploying the program for others to use. When you are ready to deploy a VBA project, you should consider the following:
VBA programs are stored in DVB project files that must be loaded into AutoCAD before they can be used. A number of methods can be used to load a DVB file. These methods fall into one of two categories: manual or automatic. Most DVB files are loaded using one of the manual techniques.
AutoCAD is a graphics- and resource-intensive application, and it loads components into memory only as each is needed. DVB files are typically rather small in size, but loading a large number (or even several that include complex user forms) into the AutoCAD drawing environment can impact performance. For this reason, you should load a DVB file only as it is needed and then unload the file once it is no longer needed. I don't suggest loading a DVB file, executing a macro, and then unloading the DVB file immediately because that can affect the user's experience with your custom programs and even with AutoCAD. All DVB files are unloaded from AutoCAD when the current session is terminated, but you can use the vbaunload
command to unload a specific VBA project while AutoCAD is still running.
Use the following techniques to manually load a DVB file into AutoCAD:
vbaload
Command) The Open VBA Project dialog box allows you to browse to where your DVB files are stored and select which file to load. After selecting a DVB file, click Open to load the file into memory. I discussed how to use this command in Chapter 1, “Understanding the AutoCAD VBA Environment.”appload
Command) The Load/Unload Applications dialog box allows you to browse to where your DVB files are stored and select which files to load. After selecting a DVB file, click Load to load the file into memory. I explain how to load a DVB file with the Load/Unload Applications dialog box in the “Using the Load/Unload Applications Dialog Box to Load a DVB File” section later in this chapter.vl-vbaload
Function
The AutoLISP function vl-vbaload
allows you to load a DVB file from a script file, from a command macro defined in a CUI/CUIx file, at the AutoCAD Command prompt, or even from a LSP file. When you use the vl-vbaload
function, it searches the paths that are listed under the Support File Search Path node in the Options dialog box. You should avoid using absolute file paths with the vl-vbaload
function; if your drive mappings or folder structure changes, the DVB file will fail to load.
The following is an example of loading a DVB file named drawplate.dvb
with the vl-vbaload
function:
(vl-vbaload "drawplate.dvb")
Manually loading DVB files doesn't always create the best user experience. Keep in mind, though, that you don't want all your DVB files to be loaded at startup because it takes away some of the computing resources from the operating system and the AutoCAD program.
You will recall that in Chapter 1, I introduced the following techniques for automatically loading a DVB file into the AutoCAD drawing environment:
appload
Command) The Startup Suite is part of the Load/Unload Applications dialog box (appload
command). When a DVB file is added to the Startup Suite, the file is loaded when the first drawing of a session is opened. Removing a file from the Startup Suite causes the file not to be loaded in any future AutoCAD sessions. If you want to use the Startup Suite to load DVB files, you must add the files to the Startup Suite on each workstation and AutoCAD user profile. I discuss how to add DVB files to the Startup Suite in the “Using the Load/Unload Applications Dialog Box to Load a DVB File” section later in this chapter.acad.dvb
FileEach time you start AutoCAD, it looks for a file named acad.dvb
and loads it automatically if found in the AutoCAD support file search paths. In addition to loading the file, if the VBA project contains a public procedure of the subroutine type named AcadStartup
, the macro is executed at startup.
The Load/Unload Applications dialog box (which you open with the appload
command) is the easiest way to load a DVB file into the AutoCAD drawing environment. Some of the other methods for loading a DVB file provide better integration into an end user's workflow, but they require you to define where the DVB files are located. I describe how to set up and identify the folders where the AutoCAD program should look for custom files in the “Specifying the Location of and Trusting a Project” section later in this chapter.
The following steps provide an overview of how to load a DVB file with the Load/Unload Applications dialog box.
appload
and press Enter).You can use the following steps to add a DVB file to the Startup Suite:
appload
and press Enter).A plug-in bundle, as I previously mentioned, is one of the methods that can be used to deploy your DVB files. Fundamentally, a bundle is simply a folder structure with its topmost folder having .bundle
appended to its name and a manifest file with the filename PackageContents.xml
located in the topmost folder.
You can use Windows Explorer or File Explorer to define and name the folder structure of a bundle. You can create the PackageContents.xml
file with a plain ASCII text editor such as Notepad. You will also need a bit of assistance from AutoLISP to load a DVB file into the AutoCAD drawing environment with the bundle.
The following is a sample PackageContents.xml
file that defines the contents of a bundle named DrawPlate_VBA.bundle
that contains three files: a help file named DrawPlate_VBA.htm
, a LSP file named DrawPlateLoader.lsp
, and the VBA project file named DrawPlate.dvb
:
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE component PUBLIC "-//JWS//DTD WileyML 20110801 Vers 3Gv2.0//EN" "Wileyml3gv20-flat.dtd"><Components Description="Windows OSs">
<ComponentEntry Description="Loader file"
AppName="DrawPlateMainLoader"
Version="1.0"
ModuleName="./Contents/DrawPlateLoader.lsp">
</ComponentEntry>
<ComponentEntry Description="Main file"
AppName="DrawPlateMain"
Version="1.0"
ModuleName="./Contents/DrawPlate.dvb">
</ComponentEntry>
</Components>
</ApplicationPackage>
The folder structure of the bundle that the PackageContents.xml
file refers to looks like this:
DrawPlate_VBA.bundle
PackageContents.xml
Contents
DrawPlate.dvb
DrawPlate_VBA.htm
DrawPlateLoader.dvb
I have provided the DrawPlate_VBA.bundle
as part of the sample files for this book, but you will also learn how to create the DrawPlate_VBA.bundle
yourself later in this chapter. To use the bundle with AutoCAD, copy the DrawPlate_VBA.bundle
folder and all of its contents to one of the following locations so that all users can access the files:
ALLUSERSPROFILE%Application DataAutodeskApplicationPlugIns
(Windows XP)ALLUSERSPROFILE%AutodeskApplicationPlugIns
(Windows 7 or Windows 8)If you want a bundle to be accessible only by specific users, place the bundle into the following location under each user's profile:
APPDATA%AutodeskApplicationPlugIns
For additional information on the elements used to define a PackageContents.xml
file, perform a search in the AutoCAD Help system on the keyword “PackageContents.xml.”
The DVB files that you create or download from the Internet can be placed in any folder on a local or network drive. I recommend placing all your custom files in a single folder on a network drive so they can be accessed by anyone in your company who might need them. Placing the files in a network location makes rolling out changes easier as well. You might consider using the name DVB Files
or VBA Project Files
for the folder that contains your DVB files.
I also recommend marking any folder(s) that contain custom files on the network as read-only for everyone except for those designated to make updates to the files. Marking the folders as read-only helps prevent undesired or accidental changes.
Regardless of the folder name you use or where you choose to place your DVB files, you need to let AutoCAD know where these files are located. To do so, add each folder that contains DVB files to the Support File Search Path and the Trusted Locations settings accessible through the Options dialog box.
The support file search paths are used by AutoCAD to locate custom files, such as those that contain block definitions, linetype patterns, AutoLISP programs, and VBA projects. Use the Options dialog box to add the folders that contain DVB files to the support file search paths of AutoCAD.
If you are using AutoCAD 2013 SP1 or later, when you try to load a DVB file AutoCAD checks to see if the DVB file being loaded is from a trusted location. A folder that you identify as a trusted location contains DVB files that are safe to be loaded without user interaction. The Trusted Locations setting in the Options dialog box or the trustedpaths
system variable are used to specify trusted locations. Any DVB file that isn't loaded from a trusted location results in the File Loading - Security Concern message box (see Figure 13.8) being displayed.
The File Loading - Security Concern message box indicates why it might not be a good idea to load the file if its origins aren't known. Loading files with an unknown origins could introduce malicious code. The end user then must decide to load (or not load) the file before the AutoCAD program can complete the load. When adding new trusted locations, make sure you limit the number of folders you trust. Further, trusted folders should be marked as read-only to avoid the introduction of unknown DVB files or other custom programs to the folders. For more information on trusted paths, see the trustedpaths
system variable in the AutoCAD Help system.
The following steps explain how to add a folder to the support file search paths and trusted locations used by AutoCAD:
options
and press Enter).The following steps explain how to add a folder to the AutoCAD support file search paths:
The following steps explain how to add a folder to the AutoCAD trusted locations:
If the location of your custom programs changes, you can replace an existing folder in the Options dialog box by expanding the Support File Search Path or Trusted Paths node and selecting the folder you want to replace. After selecting the folder you want to replace, click Browse and then select the new folder.
Executing a VBA macro from the Macros dialog box can be a bit overwhelming to an end user since the dialog box lists all the available macros from each of the VBA projects that are currently loaded into the AutoCAD drawing environment. Most end users are accustomed to starting a command from the user interface or even typing a command at the AutoCAD Command prompt.
A VBA macro can be executed from a command macro in the user interface or at the Command prompt using the AutoLISP vl-vbarun
function or the -vbarun
command. Both methods achieve the same result and can be used interchangeably.
The following examples show how to execute the CLI_DrawPlate
procedure defined in the basDrawPlate
code module of the DrawPlate.dvb
file with the vl-vbarun
function and the -vbarun
command using the AutoLISP command
function:
; Execute macro with vl-vbarun
(vl-vbarun "DrawPlate.dvb!basDrawPlate.CLI_DrawPlate")
; Execute macro with command function and -vbarun command
(command "._-vbarun" "DrawPlate.dvb!basDrawPlate.CLI_DrawPlate")
The following shows how to execute the CLI_DrawPlate
procedure defined in the basDrawPlate
code module of the DrawPlate.dvb
file with the -vbarun
command at the AutoCAD Command prompt:
Command: -VBARUN
Macro name: DrawPlate.dvb!basDrawPlate.CLI_DrawPlate
(vl-vbarun "c:\users\lee\documents\mycustomfiles\DrawPlate.dvb!basDrawPlate.CLI_DrawPlate")
instead of (vl-vbarun "DrawPlate.dvb!basDrawPlate.CLI_DrawPlate")
Although VBA doesn't allow you to create a custom command that end users can enter at the Command prompt like AutoLISP, ObjectARX®, or Managed .NET does, you can use AutoLISP as a wrapper to execute a VBA procedure.
The following shows how to use AutoLISP to define a custom command named -drawplate_vba
that an end user could use to execute the CLI_DrawPlate
macro:
(defun c:-drawplate_vba ( )
(vl-vbarun "DrawPlate.dvb!basDrawPlate.CLI_DrawPlate")
)
When a VBA macro is executed, users tend to expect certain things to occur before or after the use of any custom program. Users expect that any changes to system variables will be restored if those variables affect drawings, and they expect that when they type u
and press Enter any changes to the drawing will be rolled back. A single undo record isn't always created when a VBA program is executed, especially when the SendCommand
or PostCommand
method of the AcadDocument
object is used. I discussed these methods in Chapter 3, “Interacting with the Application and Documents Objects.”
It is good practice to call the StartUndoMark
and EndUndoMark
methods of the AcadDocument
object when a VBA program makes changes to a drawing. The StartUndoMark
method should be called before the first change is made, and the EndUndoMark
method should be called after the last change is made. The methods instruct AutoCAD to group the operations between the two methods into a single undo record, making it easier for the user to roll back any changes made by a VBA program.
A lot of time and effort can go into developing a VBA project, and a VBA project may include information about proprietary processes or intellectual property. The VBA Editor offers a way to password-protect the code and components of a VBA project. When a VBA project is password-protected, the VBA project can be loaded and macros can be executed without entering a password. But when anyone wishes to edit the code and components, the password must be entered.
The following steps explain how to password-protect a VBA project:
In this section, you will continue to work with the DrawPlate project that was introduced in Chapter 4, “Creating and Modifying Drawing Objects.” If you completed the exercises, you also worked with the DrawPlate project throughout this book by adding annotations, getting input from the user at the Command prompt, and even implementing a user interface to get input from the user.
The key concepts I cover in this exercise are as follows:
On Error GoTo
statements and labels to implement error handling can help reduce problems that an end user might encounter when using a custom program.In this exercise, you'll work with the badcode.dvb
file that came with this book and was shown in the “Debugging Through Messages” section. Stepping through a program code statement by code statement allows you to identify what is happening in your code, determine whether it is executing as expected or if an error occurs, and see which branches of a program are being followed based on the results of the logical tests. Additionally, you can view the current values of the variables in the program at specific times to ensure they have the correct data before they are passed to a function.
The following steps explain how to set a breakpoint and add watches to the Watches window:
badcode.dvb
file.basBadCode
component.' Error 1, - should be &
. Click in the left margin adjacent to the code statement to set a breakpoint.
The code editor window should now look like Figure 13.9.
BadCode
procedure locate the variable sVal
.sVal
text to select it and then right-click the selected text. Choose Add Watch from the context menu.CStr(2 / nVal)
and click OK.
The Watches window should now look like Figure 13.10.
The following steps explain how to step through the code statements of the BadCode
procedure:
vbarun
and press Enter.BadCode
procedure from the Macros list. Click Run.Enter a string:
prompt, type Hello World!
and press Enter.
Execution of the BadCode
procedure is suspended as a result of the watch setup for the sVal
variable. The If IsEmpty(sVal) = False Then
code statement is also highlighted, indicating which code statement will be executed next when execution resumes.
sVal
variable in the Watches window. The value of the sVal
variable in the Watches window should now be listed as “Hello World!”
Execution is suspended again when the breakpoint is reached.
The type mismatch error is the result of the text - sVal
in the code statement.
"Value entered: " - sVal
to "Value entered: " & sVal
.Enter an integer:
prompt, type 4
and press Enter.The Command Line history shows the following messages:
DEBUG: Inside IF
Value entered: Hello World!
Enter an integer: 4
Command:
DEBUG: Ready to divide
Divisor: 0.5
DEBUG: Outside IF
As you make changes to the procedures in the clsUtilities
class module, notice how easy it can be to implement error handling for your utility functions.
The following steps explain how to update the CreateLayer
procedure to handle general problems and pass the error to the calling procedure:
drawplate.dvb
file that you last updated in the exercises for Chapter 11, or rename the file ch13_drawplate.dvb
to drawplate.dvb
and then load the renamed file.clsUtilities
component.CreateLayer
procedure and add the bold text:Public Function CreateLayer(sName As String, _
nClr As ACAD_COLOR) As AcadLayer
On Error Resume Next
' Try to get the layer first and return it if it exists
Set CreateLayer = ThisDrawing.Layers(sName)
' If layer doesn't exist create it
If Err Then
Err.Clear
On Error GoTo ErrHandler
Set CreateLayer = ThisDrawing.Layers.Add(sName)
CreateLayer.color = nClr
End If
' Exit the function if it gets this far
Exit Function
' If an error occurs, raise an error
ErrHandler:
Err.Raise Err.Number, Err.Source, Err.Description, _
Err.HelpFile, Err.HelpContext
End Function
The following steps explain how to update the CreateRectangle
, CreateText
, and CreateCircle
procedures to handle general problems and pass the error to the calling procedure:
Public Function CreateRectangle(ptList As Variant) As AcadLWPolyline
On Error GoTo ErrHandler
Set CreateRectangle = ThisDrawing.ActiveLayout.Block. _
AddLightWeightPolyline(ptList)
CreateRectangle.Closed = True
' Exit the function if it gets this far
Exit Function
' If an error occurs, raise an error
ErrHandler:
Err.Raise Err.Number, Err.Source, Err.Description, _
Err.HelpFile, Err.HelpContext
End Function
Public Function CreateText(insPoint As Variant, _
attachmentPt As AcAttachmentPoint, _
textHeight As Double, _
textRotation As Double, _
textString As String) As AcadMText
On Error GoTo ErrHandler
Set CreateText = ThisDrawing.ActiveLayout.Block. _
AddMText(insPoint, 0, textString)
' Sets the text height, attachment point, and rotation of the MText object
CreateText.height = textHeight
CreateText.AttachmentPoint = attachmentPt
CreateText.insertionPoint = insPoint
CreateText.rotation = textRotation
' Exit the function if it gets this far
Exit Function
' If an error occurs, raise an error
ErrHandler:
Err.Raise Err.Number, Err.Source, Err.Description, _
Err.HelpFile, Err.HelpContext
End Function
Public Function CreateCircle(cenPt As Variant, circRadius) As AcadCircle
On Error GoTo ErrHandler
Set CreateCircle = ThisDrawing.ActiveLayout.Block. _
AddCircle(cenPt, circRadius)
' Exit the function if it gets this far
Exit Function
' If an error occurs, raise an error
ErrHandler:
Err.Raise Err.Number, Err.Source, Err.Description, _
Err.HelpFile, Err.HelpContext
End Function
The following steps explain how to update the CLI_DrawPlate
procedure to handle general problems when drawing the objects that form the plate and use undo grouping to make rolling back changes easier:
basDrawPlate
component.CLI_DrawPlate
and add the text in bold:Public Sub CLI_DrawPlate()
Dim oLyr As AcadLayer
On Error Resume Next
' Start an undo mark here
ThisDrawing.StartUndoMark
Dim sysvarNames As Variant, sysvarVals As Variant
sysvarNames = Array("nomutt", "clayer", "textstyle")
If IsNull(basePt) = False Then
statement and add the text in bold:' If a base point was specified, then draw the plate
If IsNull(basePt) = False Then
On Error GoTo ErrHandler
' Create the layer named Plate or set it current
Set oLyr = myUtilities.CreateLayer("Plate", acBlue)
ThisDrawing.ActiveLayer = oLyr
Dim insPt As Variant
statement and add the text in bold:myUtilities.CreateCircle cenPt4, 0.1875
On Error Resume Next
' Get the insertion point for the text label
Dim insPt As Variant
insPt = Null
insPt = ThisDrawing.Utility.GetPoint(, _
removeCmdPrompt & "Specify label insertion point " & _
"<or press Enter to cancel placement>: ")
' If a point was specified, placed the label
If IsNull(insPt) = False Then
On Error GoTo ErrHandler
' Define the label to add
Dim sTextVal As String
Loop Until IsNull(basePt) = True And sKeyword = ""
statement and add the text in bold: myUtilities.CreateText insPt, acAttachmentPointMiddleCenter, _
0.5, 0#, sTextVal
End If
End If
On Error Resume Next
Loop Until IsNull(basePt) = True And sKeyword = ""
' Restore the saved system variable values
myUtilities.SetSysvars sysvarNames, sysvarVals
End Sub
statement and add the text in bold: ' Save previous values to global variables
g_drawplate_width = width
g_drawplate_height = height
' End an undo mark here
ThisDrawing.EndUndoMark
Exit Sub
ErrHandler:
' End an undo mark here
ThisDrawing.EndUndoMark
' Rollback changes
ThisDrawing.SendCommand "._u "
End Sub
The following steps explain how to update the cmdCreate_Click
procedure to handle general problems when drawing the objects that form the plate and use undo grouping to make rolling back changes easier:
frmDrawPlate
component and choose View Code.cmdCreate_Click procedure
, or select cmdCreate
from the Object drop-down list and then choose Click from the Procedure drop-down list to the right of the Object drop-down list at the top of the code editor window.Private Sub cmdCreate_Click()
Dim oLyr As AcadLayer
' Hide the dialog so you can interact with the drawing area
Me.Hide
On Error Resume Next
' Start an undo mark here
ThisDrawing.StartUndoMark
Dim sysvarNames As Variant, sysvarVals As Variant
sysvarNames = Array("nomutt", "clayer", "textstyle")
If IsNull(basePt) = False Then
statement and add the text in bold:' If a base point was specified, then draw the plate
If IsNull(basePt) = False Then
On Error GoTo ErrHandler
' Create the layer named Plate or set it current
Set oLyr = myUtilities.CreateLayer("Plate", acBlue)
ThisDrawing.ActiveLayer = oLyr
If Me.chkAddLabel.Value = True Then
statement and add the text in bold:myUtilities.CreateCircle cenPt4, 0.1875
If Me.chkAddLabel.Value = True Then
On Error Resume Next
' Get the insertion point for the text label
Dim insPt As Variant
insPt = Null
insPt = ThisDrawing.Utility.GetPoint(, _
removeCmdPrompt & "Specify label insertion point " & _
"<or press Enter to cancel placement>: ")
' If a point was specified, placed the label
If IsNull(insPt) = False Then
On Error GoTo ErrHandler
' Define the label to add
Dim sTextVal As String
End Sub
statement and add the text in bold: ' Save previous values to global variables
g_drawplate_width = width
Me.txtWidth.Text = Format(g_drawplate_width, "0.0000")
g_drawplate_height = height
Me.txtHeight.Text = Format(g_drawplate_height, "0.0000")
g_drawplate_label = Me.chkAddLabel.Value
' End an undo mark here
ThisDrawing.EndUndoMark
' Show the dialog box once done
Me.show
Exit Sub
ErrHandler:
' End an undo mark here
ThisDrawing.EndUndoMark
' Rollback changes
ThisDrawing.SendCommand "._u "
' Show the dialog box once done
Me.show
End Sub
If you can't or don't plan to use a bundle to deploy your custom programs, you must let AutoCAD know where your DVB files are stored and whether they can be trusted. Without the trusted file designation, AutoCAD will display the File Loading - Security Concern message box each time a custom program is loaded in AutoCAD 2013 SP1 or later. And consider this: How can AutoCAD run a program it can't find?
The following steps explain how to add the folder named MyCustomFiles
to the support file search paths and trusted locations used by AutoCAD:
options
and press Enter).MyCustomFiles
folder that you created for this book in the Documents
(or My Documents
) folder, or browse to the folder that contains your DVB files.DrawPlate_VBA.bundle
Plug-in bundles are a relatively new concept in AutoCAD, but they make deploying your custom programs much easier. After all, a bundle is simply a folder structure that you can copy between machines no matter which operating system you are using. Bundles are supported in AutoCAD 2013–based products and later.
The following steps explain how to create a bundle named DrawPlate_VBA.bundle
:
MyCustomFiles
folder under the Documents
(or My Documents
) folder. Right-click in an empty area and choose New Folder.DrawPlate_VBA.bundle
and press Enter.DrawPlate_VBA.bundle
folder.DrawPlate_VBA.bundle
folder and name the new folder Contents
.Table 13.2 Files for DrawPlate_VBA.bundle
Filename | Folder |
packagecontents.xml |
DrawPlate.bundle |
drawplateloader.lsp |
Contents |
drawplate.dvb |
Contents |
drawplate_vba.htm |
Contents |
The drawplateloader.lsp
file loads the drawplate.dvb
file and then defines two custom functions named -drawplate_vba
and drawplate_vba
. The -drawplate_vba
function also supports contextual help; when the function is active, you can press F1 to display the drawplate_vba.htm
file.
DrawPlate_VBA.bundle
Plug-in bundles must be placed within a specific folder before they can be used. You learned which folders a bundle can be placed in earlier in the section “Loading a Project with a Plug-in Bundle.”
The following steps explain how to deploy a bundle named DrawPlate_VBA.bundle
:
DrawPlate_VBA.bundle
folder you created in the previous exercise.DrawPlate_VBA.bundle
folder and right-click. Choose Copy.%ALLUSERSPROFILE%Application DataAutodeskApplicationPlugIns
.%ALLUSERSPROFILE%AutodeskApplicationPlugIns
.The following steps explain how to test DrawPlate.bundle
:
-drawplate_vba
and press Enter.
You should see the familiar Specify base point for plate or [Width/Height]:
prompt. Before you created the bundle, you had to load the drawplate.dvb
file and then start the macro with the vbarun
command to access the functionality. As a reminder, the -drawplate_vba
function is defined as part of the drawplateloader.lsp
file that is used to also load the DrawPlate.dvb
file.
-drawplate_vba
function starts, press Esc to end the function.drawplate_vba
and press Enter.
You should see the Draw Plate UserForm
that you defined in Chapter 11, “Creating and Displaying User Forms.”
3.15.237.164