Using Visual LISP to Improve VBA30 Jun, 2001 By: Bill Kramer
Those that have used AutoCAD for some time know there are several tools for programming custom commands and features for the CAD environment. The most popular ones are Visual LISP and VBA (Visual Basic for Applications). Both are provided in AutoCAD 2000 (and better), and both provide a powerful platform for developing tools to improve your productivity. But the problem is each takes time to master. There are also times when you need to use one to improve the other. This month we're going to take a look at some Visual LISP functions that will assist VBA programmers. You won't need a lot of Visual LISP knowledge to begin to exploit that language for VBA development, as you will see.
First, let's set the stage. VBA excels at dialog boxes and integration with other applications supporting Active X Automation. Since it is derived from the popular BASIC programming language, it's a first choice for many learning how to program AutoCAD for the first time. But VBA programmers will quickly find that the approaches available to running a macro do not always satisfy their needs. Many options are available, such as event reactors and the VBARUN command. But what about a command from the keyboard or a menu macro? VBA does not provide an easy tool for accomplishing these tasks, while Visual LISP does.
Creating Visual LISP Commands
Creating a new command for AutoCAD in Visual LISP is easy. You just need to define a function where the first two characters of the function name start with C: (the letter C and a colon). The code needed inside a command function to load and launch a VBA module is straightforward.
There are two Visual LISP subrs (internal functions) used to load and run VBA projects. They are (VL-VBALOAD) and (VL-VBARUN). Both provide the same functionality as the VBALOAD and VBARUN commands at the AutoCAD command line. The only difference is that they can be run inside a Visual LISP program. You must keep some other considerations in mind when using these functions. For example, after you invoke the (VL-VBARUN) function, the Visual LISP function will continue to run and can (will) interfere with the VBA interface if you try to do too much. On the other hand, there are some distinct advantages to using the Visual LISP approach to loading and launching VBA macros instead of the command-line versions when programming a menu- or toolbar-based interface.
The function (VL-VBALOAD) behaves much like the command VBALOAD. You need to supply the file name of a project or DVB file. The complete file name should be provided along with the path and DVB extension. For example, if you want to load a project named MyProject.dvb in the C:\MyWork\ folder, the (VL-VBALOAD) function call would appear as follows.
VBA programmers should note a couple of things right away. Visual LISP makes use of forward slashes when separating folder or directory names. Also, the parentheses are required and the extension DVB is needed for the project to be properly located. Otherwise, VBA programmers should have no trouble reading and understanding this expression.
Unlike the VBALOAD command, this function will not generate an error if the project has already been loaded into the current drawing environment. Thus, programs can proceed smoothly by just calling the load function and then calling the run function without concern about the project already being loaded. Another interesting feature is that the Enable Macros/Virus Warning message does not appear when you use the Visual LISP approach. At least my testing revealed these aspects of using the subr instead of the command approach in a menu macro. Both of these features are excellent for advanced applications development using VBA and Visual LISP.
Have you ever written menu macros to load your VBA project and run it, only to be frustrated the next time you hit the menu button as an error appears, indicating that the project is already in memory? If so, then you should consider modifying your "-VBALOAD" entry to read the same as the example below and take advantage of a little Visual LISP. As I've said before, you don't need to know a lot of Visual LISPjust the right stuff if you want to take advantage of it. Suppose you have a project named TEST.DVB in the default AutoCAD folder. The menu line shown below:
[Test]^C^C^C^P-vbaload test.dvb -vbarun MyTest
can be replaced with the following one:
[Test]^C^C^C^P(vl-vbaload "test.dvb")(vl-vbarun "MyTest")
This change will remove the problem associated with already having a project loaded and selecting the menu entry a second time.
As just seen, coupled with the (VL-VBALOAD) function is (VL-VBARUN). After a project has been loaded you can start any public macros just as you would with the VBARUN command. One thing to note is that the VBARUN is not a subroutine. That is, program execution will not be handed to the VBA macro and the Visual LISP routine suspended as if it were running a function. Instead, the Visual LISP function will continue to run as the VBA macro starts. The best thing to do is simply finish the Visual LISP function as quickly as possible and let the VBA macro run the command interface from that point forward. If you want to return to a Visual LISP function after running the VBA code, then use the SendCommand method attached to the Document object in VBA. When you are ready to hand control back to Visual LISP, call the function directly (remember to wrap parentheses around the command start up for direct launches of Visual LISP functions). When you use this approach, the VBA program should end and allow the Visual LISP function to proceed without interference. Similar to starting the VBA macro in the first place, when you send commands to the AutoCAD document from VBA, they will be run along with the VBA and sometimes this can result in confusion at the user level as the two try to take turns. Note that you can pass parameters from VBA to the Visual LISP function by sending them as part of the command stream. They will need to be converted to strings first, then sent to the Visual LISP function as part of the function start up from the Send Command method.The program listings for this article are at the bottom of the page, or you can download cdnc7-01.lsp.
Listing 1 contains a simple Visual LISP function that can be used to load and start up a VBA macro. There are two string arguments to the function, the project name and the macro name. The project name needs to be the complete file name, including the path and extension using the format described above with forward slashes instead of back slashes in the text. The function in Listing 1 can be used in a menu or in another Visual LISP function to load and execute a VBA macro. For example, suppose we have a macro named MyMacro in a project stored in C:\MyProjects\Test1.Dvb. To run MyMacro from a menu, the entry would appear as follows.
[MyMacro]^C^C^C^P(vba-doit "C:/MyProjects/Test1.Dvb" "MyMacro")
The same line of code can also be used in a function to define a command that loads and starts the VBA macro. As mentioned earlier, a function defined with the characters C: at the start will create a new command for AutoCAD users to type at the command line or use in a menu macro. The function definition for the example MyMacro would simply be the following sequence of enclosed parentheses.
(defun C:MyMacro () (vba-doit "C:/MyProjects/Test1.Dvb" "MyMacro"))
Loading Into Memory
For Visual LISP functions to become available, they must be loaded into memory. This can be accomplished using the (LOAD) expression in Visual LISP inside a menu or as part of the loading process in AutoCAD. Another alternative is to use the APPLOAD command. The APPLOAD command provides a tool called the start up suite that lets you cause Visual LISP (and other) custom programs to load automatically when you start a drawing edit session.
After the Visual LISP functions (stored in an LSP file) are loaded into memory, they can be used at the command line or in menus. This simple set of Visual LISP function programming does lead to some other interesting questions though. For Visual LISP programmers wanting to exploit Visual BASIC, there are the issues of how to transfer data between the two environments. (Parameter passing is one technique; however, you may find others better suited to your needs.) And in an even more advanced applications development environment, there are programmers concerned with memory management and efficiency when running large applications in a crowded environment.
In fact, one of those concerns came up during a discussion amongst the CAD-Cruise programming group of John Gibb, Randy Kintzley, and myself. The basic query was how to determine if a given VBA project was loaded or not from within Visual LISP. Randy played with this concept and quickly produced a simple example that looped through the VBA project set objectan undocumented feature found by exploring the object model. That example fell into my hands and then evolved into the function presented in Listing 2, in which the names of the projects along with the file names are returned in a list. This utility can be used to unload projects or load them in a particular order, if needed, by a given application. The tool can be used in the memory management of VBA projects. It can also provide a method by which you can sanitize the VBA environment for your own macros and then restore what you found when your application is completed.
The function (GET-VBA-PROJECT-LIST) shown in Listing 2 is not long, but it does involve the use of AutoCAD automation objects. As such, the first thing the function does when starting is invoke the (VL-LOAD-COM) expression to make sure the component objects module is available to Visual LISP. Let's begin by accessing the AutoCAD object tree. The first stop is the AutoCAD application object itself (vl-get-acad-object). This function returns an object reference that can be used to get to the various components of the AutoCAD program. The VBA environment is one of them. The next step is to make a request to AutoCAD via Active X automation to obtain the object reference for the VBA IDE object (known as VBE). The function (VLA-GET-VBE) returns an object reference that you can drill deeper into. However, you must use the generic (VLA-GET) function to retrieve the values since you have now left the area where (VLA-GET-xxx) type functions have been provided. In the case of our application, you want the Projects list. After all, you are looking for the names of the VBA projects already loaded into memory.
Now you may be wondering just how did I learn about all these undocumented names and object links and so forth. Is there some secret place to learn about these things? Turns out there are several locations you can use. Online Help files provide a lot of information about the functions available and the object tree in general. There is also printed documentation available from various publishers. But realize that the online help and printed documentation are not the only place to search for interesting tools in a programming environment as robust as AutoCAD's Visual LISP and VBA. In VBA you have the object browser; in Visual LISP the apropos window.
But when you want to learn about object contents in Visual LISP, the subr to turn to is named (VLAX-DUMP-OBJECT). The (VLAX-DUMP-OBJECT) subr is perhaps the single most valuable tool in the quest for new objects and their contents. It will show the names and values of an object referenced by a symbol in Visual LISP. Listing 3 shows a quick session using the dump object, which will help you learn about an object as seen from Visual LISP. I am confident you will want to spend some time exploring this approach once you get started with it. New windows into AutoCAD will open before your eyes!
Getting back to the function in Listing 2, the object returned by (VLA-GET-VBE) is used to obtain the projects object reference, and, if successful, a repeat loop is started for the number of projects stored in the object set or collection. The projects object is a collection of projects. The count property tells you how many projects are in the collection, and you can retrieve each one using the item method and an index. When the (VLA-ITEM) subr is used with the Projects object reference and an index (starting at one), a Project object is returned. The data our function is interested in can be found in the Project objects themselves. Specifically, we are interested in the Name and FileName properties. These values are retrieved using the (VLAX-GET) subr from the project object reference and added into a list for return to the calling function.
For example, the utility in Listing 4 will unload all the VBA projects currently loaded in memory. It first obtains a list of the projects by calling the (GET-VBA-PROJECT-LIST). A for each loop iterates through each of the list elements and sends the file name to the VBAUNLOAD command. There is currently no (VL-UNLOADVBA) or anything like it that I have found. Of course, with objects that could change with the next update! That is one of the most beautiful aspects of object-oriented systems such as AutoCAD, they update, evolve, and improve with ease.
VBA and Visual LISP
The use of both VBA and Visual LISP is a powerful combination in the programming of AutoCAD applications. You can use the best features of each to achieve your desired results more quickly. The draw back is that you must learn both to a sufficient level in order to achieve this payback. Thus, even if you already know VBA, you can gain a lot by obtaining a working knowledge of Visual LISP. And Visual LISP programmers, who want a much better mechanism for writing dialog boxes and tasks that work with other applications in the computersuch as Excel and Accesswill want to learn a lot about VBA. More about the integration of the two language environments is something I can explore in future columns. Until next time, keep on Programmin'!
Listing 1. VBA Menu Macro Utility
(defun VBA-DOIT (ProjName Macro) (if (findfile ProjName) (progn (vl-vbaload ProjName) (vl-vbarun Macro) ) ) )
Listing 2. VBA Project List
;;Returns a nested list of project names ;; and file names for the current VBA ;; projects. (defun Get-VBA-Project-list ( / oApp ;Acad Application Object oVBE ;VBA object oProjs ;Projects object N ;Loop counter through projects Nams ;List of project names oProj ;Project object ) (vl-load-com) ;requires automation links (if (and ;;Drill down to the Projects object (setq oApp (vlax-get-acad-object)) (setq oVBE (vla-get-vbe oApp)) (setq oProjs (vlax-get oVBE "VBProjects")) ) ;Loop through Projects object (repeat (setq N (vla-get-count oProjs)) ;get the item at position N (setq oProj (vla-item oProjs N) ;get the name property, ;add it to the list. Nams (cons (list (vlax-get oProj "Name") (vlax-get oProj "FileName") ) Nams) N (1- N) ) ) ) ; return list of names Nams ) }
Listing 3. Command Line When Dumping Objects
Command: (setq oApp (vlax-get-acad-object)) #<VLA-OBJECT IAcadApplication 00a7b334> Command: (setq oVbe (vlax-get oapp "VBE")) #<VLA-OBJECT VBE 080d15c4> Command: (vlax-dump-object oVBE) ; VBE: nil ; Property values: ; ActiveCodePane = #<VLA-OBJECT _CodePane 03243e04> ; ActiveVBProject = #<VLA-OBJECT _VBProject 080d8524> ; ActiveWindow (RO) = nil ; Addins (RO) = #<VLA-OBJECT _AddIns 080d1714> ; CodePanes (RO) = #<VLA-OBJECT _CodePanes 00c00250> ; CommandBars (RO) = #<VLA-OBJECT _CommandBars 0851be88> ; Events (RO) = #<VLA-OBJECT Events 080d1654> ; MainWindow (RO) = #<VLA-OBJECT Window 080d1854> ; SelectedVBComponent (RO) = #<VLA-OBJECT _VBComponent 080e56f4> ; VBProjects (RO) = #<VLA-OBJECT _VBProjects 080d1604> ; Version (RO) = "6.00" ; Windows (RO) = #<VLA-OBJECT _Windows 080d16d4> T
Listing 4. Unload All VBA Projects
(defun C:UNLOADALLVBA ( / VBAProjs VBAProj) (setq VBAProjs (Get-VBA-Project-List)) (foreach VBAProj VBAProjs (command "_VBAUNLOAD" (cadr VBAProj))) )