With the advent of AutoCAD 2000, VBA in AutoCAD has come of age. This release removes many of the limitations of the initial implementation of VBA in AutoCAD Release 14, and adds substantial functionality to AutoCAD itself.
To get a sense of what you can now do with VBA, this article presents a simple add-in utility, MRU+. This utility, shown in FIGURE 1, maintains a list of the most recently used drawings, shows you which ones are loaded currently, and lets you load and unload drawings simply by clicking the checkbox next to the drawing name. Although it's a simple utility, MRU+ demonstrates many of the new features of AutoCAD VBA, and should help get your creative juices flowing in this particular VBA environment.
FIGURE 1: The MRU+ utility is a VBA add-in for AutoCAD 2000 that helps track
recently used files.
Welcome to AutoCAD 2000
As you learn about MRU+, you'll see many of the new features of AutoCAD 2000. This article isn't a product review, so I won't try to cover them all; the emphasis is on things that make a difference to VBA developers. However, it's worth highlighting a few of the major improvements in this version of AutoCAD:
This list covers only a few of the improvements in this major release. For full details, see the AutoCAD 2000 Web site at http://www.autodesk.com/products/acad2000.
Two Types of VBA Projects
AutoCAD 2000 allows storing VBA projects either as part of a document (referred to as an embedded project), or as a standalone file (referred to as a global project). Each loaded drawing can have a single embedded project, or not have an embedded project at all. In addition, you can have any number of global projects loaded into AutoCAD. To manage these two types of projects, AutoCAD includes the VBA Manager (select Tools | Macro | VBA Manager). This tool, shown in FIGURE 2, allows you to perform the following operations:
FIGURE 2: The VBA Manager dialog box lets you manipulate embedded and
standalone VBA projects.
Of course, a global project is the obvious choice for a utility designed to capture information about multiple drawings. In the case of the MRU+ utility, the global project is MRUPlus.dvb. The "dvb" extension is used by AutoCAD for global projects stored on disk.
Loading at Startup
AutoCAD 2000 doesn't automatically initialize VBA whenever you start the program. Rather, VBA is loaded on demand. However, there are two ways you can load and run VBA code at startup. First, you can create a global VBA project named acad.dvb, and save it in your AutoCAD folder. Any project with this name will be automatically loaded at startup. Further, if it contains a macro (a Sub procedure) named AcadStartup, that macro will be automatically executed as soon as the acad.dvb file is loaded.
As an alternative, you can place Auto Lisp commands in a file named acad.lsp, which is also in the AutoCAD folder. Any Auto Lisp function named STARTUP in this file is automatically run when the file is loaded. As you'll see throughout this article, Autodesk has done a good job of building connections between the older Auto Lisp language and the newer VBA language, so it's possible for Auto Lisp code to call VBA code and vice versa.
For an add-in, the sensible thing to do is to modify the acad.lsp file to load the add-in at startup. Most AutoCAD users will be familiar with modifying this file and with the Auto Lisp language, so adding a line of code to load your add-in should be a simple matter. You can simply load the add-in, or you can load the add-in and run a macro from it at the same time.
To load the add-in, you can use Auto Lisp code to call the AutoCAD vbaload command:
(defun S::STARTUP()
(command "_vbaload"
"myproject.dvb")
)
Or, to run an initialization procedure in the add-in, you can call the vbarun command:
(defun S::STARTUP()
(command "_-vbarun"
"MRUPlus.dvb!basMRUPlus.LoadMRUPlus")
)
The argument to the vbarun command can be fully qualified in the form:
FileName!ModuleName.MacroName
If the specified file isn't loaded, it will first be loaded, and then the macro will be executed. By default, the vbarun or vbaload command looks only in the main AutoCAD folder. If your utility is saved elsewhere, you'll need to supply a full path and filename in the argument. In this case, you need to "escape" any backslash characters by doubling them. For example, this would be valid in an acad.lsp file:
(defun S::STARTUP()
(command "_vbaload"
"c:\\Utilities\\myproject.dvb")
)
Manipulating the Menu
As part of its startup code, MRU+ adds itself to the File menu for AutoCAD, just above the built-in, most-recently-used file list. In previous versions of AutoCAD, this operation was completely impossible for VBA code to perform. Now a new branch of the AutoCAD object model makes such things trivial.
FIGURE 3 shows the new AutoCAD objects available for menu and toolbar manipulation. The MenuBar object holds all the menus currently displayed on the main AutoCAD menu bar. You can insert and remove menus by calling the InsertInMenuBar and RemoveFromMenuBar methods of the PopupMenu object.
FIGURE 3: Objects for manipulating menus and toolbars.
The PopupMenu object represents a standard pull-down menu, or a right-click shortcut menu. The PopupMenuItem object represents a single menu item.
The MenuGroups collection contains a set of MenuGroup objects. Each MenuGroup can contain menus and toolbars. You can load menu groups from a disk file. After you've loaded a menu group, its contents become available to load into the MenuBar or shortcut menus.
The Toolbars collection contains Toolbar objects, each of which represents a toolbar. Individual toolbar buttons are represented by ToolbarItem objects.
The MRU+ utility does the simplest possible thing with these objects: It adds a menu item that displays its user interface to the file menu. This is done with a single line of code:
Application.MenuBar(0).AddMenuItem 19, "MRU+", _
"_-vbarun
ShowMRUPlus "
This code adds a new item to the 20th position of the (zero-based) collection of PopupMenuItems on the leftmost PopupMenu in the default MenuBar (in other words, to the File menu). The second argument is the text to display on the menu, and the third argument is the AutoCAD menu macro to invoke when this menu item is selected.
AutoCAD menu macros use almost the same syntax as AutoCAD commands, but there are some differences. This one invokes the vbarun command, using the underscore-and-dash syntax to suppress the display of the dialog box it would normally call. Note the space after the name of the VBA macro to execute. In menu macros, the trailing space is interpreted as the end of a piece of input. Without it, the VBA macro name would be entered on the AutoCAD command line, but not executed.
Using ThisDrawing
Although it's possible to have multiple global VBA projects loaded, they all share a single object named ThisDrawing. This object represents the active document in the AutoCAD user interface, and is of the class AcadDocument. (Unlike many other object models, the AutoCAD model prefixes the names of all objects with "Acad" to ensure uniqueness in multiple-host operations). If multiple VBA projects include handlers for the events of the ThisDrawing object, each one of those event handlers gets called in turn.
Using the events of ThisDrawing is simple. Every global VBA project you create automatically contains an instance of a ThisDrawing object that can't be removed. In the MRU+ application, the ThisDrawing.BeginClose event is used to tell the main form when a drawing is being unloaded from the user interface. This event contains minimal code to call into a public method of the user interface form to locate the appropriate ListItem in the ListView and uncheck it.
The table in FIGURE 4 lists the events supported by ThisDrawing. As you can see, many of these events are new to AutoCAD 2000. In particular, the BeginShortcutMenu, BeginLisp, and Object events allow new ways of interacting with the other programming languages in AutoCAD.
Event Name |
New |
Fires when |
Activate |
X |
A drawing window gets the focus. |
BeginClose |
X |
User or code tries to close the drawing. |
BeginCommand |
|
A command is issued. |
BeginDoubleClick |
X |
The user double-clicks any drawing object. |
BeginLisp |
X |
A Lisp expression is sent to be evaluated. |
BeginPlot |
X |
A print operation is started. |
BeginRightClick |
X |
The user right-clicks any drawing object. |
BeginSave |
|
The user starts to save a drawing. |
BeginShortcutMenuCommand |
X |
The user brings up the command mode shortcut menu. |
BeginShortcutMenuDefault |
X |
The user brings up the default shortcut menu. |
BeginShortcutMenuEdit |
X |
The user brings up the edit shortcut menu |
BeginShortcutMenuGrip |
X |
The user brings up the grip shortcut menu. |
BeginShortcutMenuOsnap |
X |
The user brings up the osnap shortcut menu. |
Deactivate |
X |
A drawing window loses the focus. |
EndCommand |
|
A command finishes executing. |
EndLisp |
X |
A Lisp expression is finished being evaluated. |
EndPlot |
X |
A print operation finishes. |
EndSave |
X |
A save operation finishes. |
EndShortcutMenu |
X |
Any shortcut menu actually appears. |
LayoutSwitched |
X |
The user switches to a different layout. |
LispCancelled |
X |
An evaluation of a Lisp expression is cancelled. |
ObjectAdded |
X |
Any object is added to the drawing. |
ObjectErased |
X |
Any object is removed from the drawing. |
ObjectModified |
X |
Any object is modified. |
SelectionChanged |
X |
The default pick set is changed. |
WindowChanged |
X |
The window is maximized or minimized. |
WindowMovedOrResized |
X |
The window is moved or resized. |
FIGURE 4: Events of the AcadDocument object.
Hooking the Application Object
Besides having ThisDrawing represent the active document, the MRU+ utility also needs to monitor an event of the AcadApplication object (representing AutoCAD itself) to know when a new drawing has been opened. Unlike the ThisDrawing object, however, global VBA projects don't automatically get an instance of the AcadApplication object. Fortunately, you can use the VBA WithEvents keyword to create your own. The sequence of events that MRU+ uses to hook the EndOpen event of the AcadApplication object, which fires whenever a new drawing is opened, is outlined in the following paragraphs.
The acad.lsp file, as you already saw, contains code to call the LoadMRUPlus procedure in basMRUPlus. Along with other startup code, this procedure instantiates an instance of a private class named CApp and sets one of its properties:
' Private class used to grab the
application events.
Private mApp As CApp
Public Sub
LoadMRUPlus()
...
'
Get our event hook into the Application.
Set
mApp = New CApp
Set
mApp.App = ThisDrawing.Application
The CApp class exists solely as a wrapper for the AcadApplication object. The built-in ThisDrawing object contains an Application property that points to the sole instance of the AcadApplication object, so it's easy for the startup code to find it. FIGURE 5 shows all the code for the CApp class. The important thing here is that the private variable of type AcadApplication is declared WithEvents. This special keyword makes the events of the AcadApplication object available to the code in the class module. All source for the MRU+ utility is shown in Listing One.
Option Explicit
' This object will let us intercept
events
' for the Application object.
Private WithEvents
mApp As AcadApplication
>
Private Sub
mapp_EndOpen(ByVal FileName As String)
'
When a drawing is opened, add it to our MRU list.
frmMRUPlus.AddFile
FileName, Now, True
End Sub
Public Property
Set App(NewApp As
AcadApplication)
'
This property must be set to hook things up.
Set
mApp = NewApp
End Property
FIGURE 5: The CApp class module.
Events on the AcadApplication object are entirely new in AutoCAD 2000. The designers have done an excellent job of anticipating things third-party developers might want to hook into. The table in FIGURE 6 lists the new events of the AcadApplication object.
Event Name |
Fires when... |
AppActivate |
AutoCAD receives the focus. |
AppDeactivate |
AutoCAD loses the focus. |
ARXLoaded |
An ObjectARX application is loaded. |
ARXUnloaded |
An ObjectARX application is unloaded. |
BeginCommand |
A command is issued from the AutoCAD command line. |
BeginFileDrop |
A file is dropped in the AutoCAD workspace. |
BeginLisp |
A Lisp expression is evaluated. |
BeginModal |
A modal dialog box is about to be displayed. |
BeginOpen |
A drawing is about to be opened. |
BeginPlot |
A drawing is about to be printed. |
BeginQuit |
The AutoCAD session is about to end. |
BeginSave |
A document is about to be saved. |
EndCommand |
A command finishes executing. |
EndLisp |
A List expression is evaluated. |
EndModal |
A modal dialog is dismissed. |
EndOpen |
A drawing has been opened. |
EndPlot |
A drawing has been printed. |
EndSave |
A drawing has been saved. |
LispCancelled |
An evaluation of a Lisp expression is cancelled. |
NewDrawing |
A new drawing has been created. |
SysVarChanged |
The value of a system variable has been changed. |
WindowChanged |
The window is maximized or minimized. |
WindowMovedOrResized |
The window is moved or resized. |
FIGURE 6: Events of the AcadApplication object.
Conclusion
With the release of AutoCAD 2000, VBA in AutoCAD has gone from being an interesting proof-of-concept, to a truly useful tool. The addition of a granular event model, the ability to load multiple VBA projects and execute code at startup, and the integration with Auto Lisp, the command line, menus, and toolbars, make it possible to write useful utilities integrated with the standard AutoCAD interface.
Although the MRU+ utility presented here is a proof-of-concept, it demonstrates all the connections you'd need to make with any industrial-strength AutoCAD utility. Autodesk has invested a great deal of effort in making VBA a first-class AutoCAD design environment. With this release, there should be enough market niches for third-party VBA developers to make that investment pay off for them.
Mike Gunderloy (mailto:MikeG1@mcwtech.com) is a Senior Consultant with MCW Technologies, a Microsoft Solution Provider. He's also the co-author of SQL Server 7 In Record Time (SYBEX, 1999), a contributor to Mastering AutoCAD 2000 (SYBEX, 1998), and is currently working on a book on using Visual Basic with ADO.
' *********************************************************
' Code attached to ThisDrawing.
' *********************************************************
Option Explicit
Private Sub
AcadDocument_BeginClose()
'
Whenever the user closes a drawing, uncheck
'
its name on the MRU form.
frmMRUPlus.UnCheck
ThisDrawing.FullName
End Sub
' *********************************************************
' Code attached to frmMRUPlus.
*********************************************************
Option Explicit
' Arbitrary counter for generating
unique
' keys for ListItems.
Private mintDoc As Integer
Public Property
Let Entries(NewEntries As
Integer)
'
Set the maximum number of entries to display.
txtEntries.Text =
NewEntries
sbEntries.Value =
NewEntries
End Property
Public Sub
AddFile(strFileName As String,
_
dblTime As Double, fChecked As Boolean)
'
Add a file to the MRU list. Specifies the file name,
' time, and whether it should be checked. If there are
' more than the specified number of entries on the list,
' drops the oldest entry.
Dim
li As ListItem
Dim
intI As Integer
'
Check for a few common errors and fix 'em up.
If
Len(strFileName) = 0 Then
Exit Sub
End
If
If
Val(txtEntries.Text) < 1 Then
txtEntries.Text = 10
End
If
'
Look for an existing item with this name.
For
intI = 1 To lvwMRU.ListItems.Count
If lvwMRU.ListItems(intI).Text = strFileName Then
Set li = lvwMRU.ListItems(intI)
Exit For
End If
Next
intI
'
If there is no such item, we need to add one.
If
li Is Nothing Then
' Check first to see whether we need to drop an item.
If lvwMRU.ListItems.Count >= txtEntries.Text Then
Set li = lvwMRU.ListItems(1)
' Find the oldest item to drop.
For intI = 2 To
lvwMRU.ListItems.Count
If CDate(lvwMRU.ListItems(intI).SubItems(1))
< _
CDate(li.SubItems(1)) Then
Set li = lvwMRU.ListItems(intI)
End If
Next intI
lvwMRU.ListItems.Remove li.Index
End If
' Add the new item with an arbitrary key.
Set li = lvwMRU.ListItems.Add(, "K" &
mintDoc, _
strFileName)
li.SubItems(1) = CDate(dblTime)
mintDoc = mintDoc + 1
End
If
'
Whether the item was new or existed already, check it
'
if it wasn't checked and should be checked.
If
Not li.Checked Then
li.Checked = fChecked
End
If
End Sub
Public Sub
UnCheck(strFileName As String)
'
Clears the checkmark next to an item.
Dim
intI As Integer
Dim
li As ListItem
'
Find the item to clear by comparing names.
For
intI = 1 To lvwMRU.ListItems.Count
If lvwMRU.ListItems(intI).Text = strFileName Then
Set li = lvwMRU.ListItems(intI)
Exit For
End If
Next
intI
'
And if we found it, uncheck it.
If
Not li Is Nothing Then
li.Checked = False
End
If
End Sub
Private Sub
cmdClose_Click()
'
This form stays resident, just hide it.
Me.Hide
End Sub
Private Sub
lvwMRU_ItemCheck( _
ByVal
Item As MSComctlLib.ListItem)
'
When the user checks or unchecks an item, load or
'
unload the corresponding drawing.
Dim
doc As AcadDocument
If
Not Item Is Nothing Then
If Item.Checked Then
' Opening is a method of the Documents collection.
Application.Documents.Open Item.Text
Else
' Closing is a method of the document.
For Each doc In Application.Documents
If doc.FullName = Item.Text Then
doc.Close
Exit For
End If
Next doc
End If
End
If
End Sub
Private Sub
sbEntries_Change()
' Tie together the textbox and the spin button.
txtEntries.Text =
sbEntries.Value
End Sub
Private Sub
txtEntries_Change()
'
Tie together the textbox and the spin button.
sbEntries.Value =
txtEntries.Text
End Sub
Private Sub
UserForm_Terminate()
'
Write changes in settings to the registry.
Dim
li As ListItem
Dim
intI As Integer
SaveSetting
"MRUPlus", "Options", "Entries", _
txtEntries.Text
For
intI = 1 To lvwMRU.ListItems.Count
Set li = lvwMRU.ListItems(intI)
SaveSetting
"MRUPlus", "Files", _
"FileName" & intI, li.Text
SaveSetting
"MRUPlus", "Files", _
"FileTime" & intI,
li.SubItems(1)
Next
intI
End Sub
' *********************************************************
' Code attached to basMRUPlus.
' *********************************************************
Option Explicit
' Private class used to grab the
application events.
Private mApp As CApp
Public Sub
LoadMRUPlus()
'
Startup code, meant to be called from acad.lsp.
Dim
doc As AcadDocument
Dim
intI As Integer
Dim
intEntries As Integer
Dim
strName As String
'
Get our event hook into the Application.
Set
mApp = New CApp
Set
mApp.App = ThisDrawing.Application
'
Add our menu item, ignoring error if it already exists.
On
Error Resume Next
Application.MenuBar(0).AddMenuItem 19, "MRU+", _
"_-vbarun
ShowMRUPlus "
On
Error GoTo 0
'
Load the form.
Load frmMRUPlus
'
Load any open drawings.
For
Each doc In
Application.Documents
frmMRUPlus.AddFile
doc.FullName, Now, True
Next
doc
'
And load any names we saved in the registry from a
'
previous session.
On
Error Resume Next
intEntries =
GetSetting("MRUPlus", "Options", _
"Entries", "10")
If
Err.Number <> 0 Then
intEntries = 10
End
If
On
Error GoTo 0
frmMRUPlus.Entries =
intEntries
For
intI = 1 To intEntries
strName =
GetSetting("MRUPlus", "Files", _
"FileName" & intI)
If Len(strName) > 0 Then
frmMRUPlus.AddFile
strName, CDate(GetSetting( _
"MRUPlus", "Files", "FileTime" &
intI)), False
End If
Next
intI
End Sub
Public Sub
ShowMRUPlus()
'
Make the form visible.
frmMRUPlus.show
End SubCApp
Option Explicit
' This object will let us intercept events
' for the Application object.
Private WithEvents
mApp As AcadApplication
Private Sub
mapp_EndOpen(ByVal FileName As String)
'
When a drawing is opened, add it to our MRU list.
frmMRUPlus.AddFile
FileName, Now, True
End Sub
Public Property
Set App(NewApp As
AcadApplication)
' This property must be set to hook things up.
Set
mApp = NewApp
End Property
End Listing One
Copyright © 1999 Informant Communications Group. All Rights Reserved. • Send feedback to the Webmaster • Important information about privacy