Creating an AutoCAD 2000 Utility

By Mike Gunderloy

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.

Begin Listing One - MRU+ Code


' *********************************************************

' 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