Programming Reactors31 Aug, 2000 By: Bill Kramer
Have you ever thought of an application idea that required you to know what's going on inside AutoCAD on a real-time basis? How about one that ran only if a specific command in AutoCAD was being run? Or, one that needs to know when entities are added or removed from a drawing? If you answered yes to any of these questions, then you should look at a programming feature in AutoCAD called reactors.
Reactors are functions (program modules) that run in reaction to something going on within the system. Using reactors can make your program aware of the "goings on" within AutoCAD. Each of the major programming systems in AutoCAD 2000 (Visual LISP, VBA and ObjectARX) support reactors to one degree or another, and the concepts behind the reactors are basically the same in each of the programming environments. You start by defining a function that will run in reaction to an event in the system. You then inform the system of the function's name so AutoCAD knows to call it when a specific event happens. Lastly, your application waits to be called by AutoCAD to react when needed. Although these concepts are shared between all three major developer methods, implementation varies based on the language in use.
From the AutoCAD system point of view, there are lists or tables of functions that need to be notified when an event takes place. This form of programming is sometimes called event-driven programming; that is, you write routines that respond to the events taking place. Windows programmers are used to this concept as it is an integral part of writing applications.
For example, when you switch the Windows display to another program and then go back to AutoCAD, you cause a series of routines to run which paint or redraw the display graphics window, menu area, command area, icon menus and so forth. These routines are responding to an event. In Windows programmer terminology, the event taking place is AutoCAD regaining the focus. If you were to try and write your own software for Windows in C++, you would create a set of routines to take care of any display or redisplay situations. That includes changing the size of the window or regenerating it on the screen. Using Wizard programs you can create the skeletons of these functions quickly in C++. But there may be other areas of the computer you want to react to as well, depending on what you are programming the computer to do.
Fortunately, when writing applications that run inside AutoCAD, the programmer doesn't have to do all the system-level reactions such as repainting the display or selecting icons for display. In fact, advanced applications can be written without any reactors in them. But note: if you are programming dialog boxes, you are programming in an event-driven style. That is, you're writing functions that are basically callback functions for the dialog box. As things happen in the dialog box under the control of the user, these functions are called on to respond. They are acting exactly as reactors act inside AutoCAD.
Reactors go way beyond dialog boxes, though. You can think of them as AutoCAD callback functions; that is, AutoCAD is calling your functions when something of interest happens. As a result, your functions will appear to react to these events. As in Windows programming, there are specific types of AutoCAD events that your application can react to. These events include adding or removing objects from the drawing, command starts and stops, drawing saves and so forth.
Before exploring the implementation specifics that relate to the various programming tools, let's review some of the basics involved. Some simple rules need to be followed in order to make sure your application and reactors run properly inside AutoCAD. You can think of them as safety guidelines because-if you choose not to follow these simple rules-your application will cause AutoCAD to behave in a manner not expected by the operators or yourself.
The first rule of programming reactors is to keep them as short and sweet as possible. Remember that your reactor is running when the user does something that causes the event to take place. In most cases, the user is not aware that they've initiated your program. For example, if you write a reactor that responds when an object is added to the drawing, then any object the user draws will cause your reactor to run. If the application is interested in specific types of objects being added, then it should test the entity type first and quickly exit if the object being added is not one of them. Related to this concept are routines that react to the running of a command. When the user enters the LINE command, he or she does not want to see a new dialog box appear or see the layers change automatically. You will always have exceptions to these basic rules-and this month's example code set is one of them-but I'll return to that later.
The second important rule of programming reactors is that your program can't stop what has been started. If an entity is going to be removed from the drawing, you can't prevent it from happening. The same situation exists with the addition of a new entity or the start-up of a command sequence; you can't stop it.
Suppose you wrote a reactor that ran each time a drawing was saved. Your reactor routine couldn't prevent the save activity from taking place. But you could add additional information to the drawing file or remove temporary entities before the save happens. These actions would be considered proper in terms of reacting to the drawing events.
Now, you could get creative and write a reactor that appeared to not allow the deletion of certain objects from the drawing. Suppose you wrote a reactor that is looking for the removal of a specific line object in the drawing. When the request is made, your reactor runs and tests the object about to be deleted to see if it is the one of interest. If it is, then your reactor adds a new object to the drawing exactly the same as the one being deleted. From the user point of view, it looks like you've got an object that simply won't go away. Every time the user tries to erase it, it reappears back in the drawing.
The third important rule of reactor programming is to watch out for recursion. Your program should not do anything that will cause another reactor to run. This could result in your application not returning from the reactor. From the user point of view, your application has locked up AutoCAD. For example, if you were to have a reactor that responds to the addition of an entity to the drawing by adding another entity, you could send the system into a never-ending spiral. There are ways around this, but you need to be aware of the potential so you make proper use of alternative methods.
To avoid having a reactor call itself or another related reactor, you must program accordingly. The approach you'll take to "turn off" a reactor varies depending on the application environment.
Visual LISP has a function named (VLR-REMOVE) that disables a reactor by name or group. This function puts the reactor to sleep until it's added back to the reactor set. You can keep track of the running of a reactor using variable assignments in your own programs. This is not as fluid as disabling and enabling the reactors as needed, however.
In ObjectARX you can keep track of various reactors within your own program and manage them accordingly. Or, you can use the addReactor() and removeReactor() methods associated with all reactor classes. ObjectARX provides a greater level of control over the reactors, along with a much larger set of available reactor situations and conditions. Reactors are powerful and pervasive in ObjectARX as they form the foundation from which custom objects are built and maintained.
VBA applications using reactors can use variables indicating that a reactor is running, thereby skipping past recursive calling situations. For example, you might have a reactor that's responding to an entity being added to the drawing. In the reactor a global variable is tested to see if the process has already been activated. If not, the variable is set to "reactor busy," and the desired action takes place. After the action concludes, the variable is set back to the "reactor idle" value. Of course, for speed of operation I recommend using integers or Boolean values instead of the string names just mentioned.
The key in all cases is to understand how reactors happen. Once you understand this concept, it's relatively easy to keep reactors from causing too much interference in normal operations. Experimentation is the key.
Making Reactors in VBA
Now, let's take a look at creating a reactor in VBA. The process is easy. Start the VBA editor (VBAIDE) and select the AutoCAD Objects/ThisDrawing option in the project window. Using the Object pulldown option, select the AcadDocument option, as shown in Figure
Figure 1. You can select the Acad Document option using the Object pulldown menu.
Figure 2. After you've selected the Acad Document option, shown in Figure 1, the Procedure pulldown, partially shown here, will now contain all the reactors you can program.
It is recommended that your reactor functions do not display dialog boxes when activated. The possibility exists that things won't work the way you intend when multiple reactors are active. But, if you have complete control over the operations environment, you can get away with simple operations such as those performed in the example application.
The Command Started reactor can be used for the purpose of intercepting the plot-command request. You can also use the Begin Plot reactor to achieve the same results in this case. Listing 1 shows the source code for the AcadDocument_BeginCommand () procedure. This function is passed to the command name as a string in the parameter list. In the code, check to see if it is PLOT, and, if so, start the dialog-box interface.
|Listing 1. Begin Command Reactor|
Private Sub AcadDocument_BeginCommand (ByVal CommandName As String) If CommandName = "PLOT" Then TitleBlock.Show End If End Sub
|Listing 2. Start Dialog Box|
Private TB As New TitleAttribs Private Sub UserForm_Activate() TB.GetTitleBlock CheckBy.Value = TB.ChkBy DesignBy.Value = TB.DesBy DrawnBy.Value = TB.DrwBy ProjectName.Value = TB.ProjID TodayDate.Value = TB.DateIs End Sub
When run, the application example will seek out the title block and display the contents of the attributes found on the dialog box. The user is then given the opportunity to change these values. The values are written back to the drawing when the OK button is pressed, moving the process on to the actual PLOT command dialog box.
This simple example solves requests from a number of readers, but it involves more work to be fully functional for a given user environment. The attributes in the custom object are specific and will need to be changed to match your own internal standards. There may also be more attributes you'll want to add to the dialog box. You might also consider changing options (from text boxes to pulldown lists) which contain specific options to choose from when updating fields. It might also be nice to have the date information automatically get the current system date. These extensions are left as exercises for the student of VBA.
Using reactors is one way to leverage commands and activities inside AutoCAD to perform tasks related to the application itself. Instead of waiting for the user to start a command, you can keep an eye on what's going on in the drawing as changes occur. We've only scratched at the surface of reactors. There is much more to discover and explore as you make your applications more integrated with the operations of AutoCAD. As always, until next time, keep on programmin'.