Reactors
In the previous chapter, we saw how to link entities together through their handles. This naturally raises a question: can we automatically react when a linked entity is modified or deleted? That is exactly what reactors are for.
A reactor is a mechanism that monitors events in AutoCAD and executes a callback function when the event occurs. If you are familiar with events in C# (for example button.Click += OnClick) or event listeners in JavaScript, it is the same concept: you subscribe to an event and provide a function that will be called when it fires.
Types of reactors
AutoLISP offers several categories of reactors through the vlr-* (Visual LISP Reactors) functions:
| Type | Creation function | Monitors... |
|---|---|---|
| Object | vlr-object-reactor |
Modifications to specific entities |
| Database | vlr-dwg-reactor |
Opening, closing, and saving of the drawing |
| Editor | vlr-editor-reactor |
Commands executed by the user |
| Command | vlr-command-reactor |
The start and end of specific commands |
Object reactor: monitoring an entity
The object reactor is the most common. It monitors one or more entities and triggers a function when they are modified:
(defun notify-modification (owner reactor parameter-list)
(princ (strcat "\nEntity " (vl-princ-to-string owner) " has been modified!"))
(princ)
)
;; Create a circle
(entmake
(list
'(0 . "CIRCLE")
'(10 50.0 50.0 0.0)
'(40 . 25.0)
)
)
;; Attach a reactor to this circle
(setq my-reactor
(vlr-object-reactor
(list (vlax-ename->vla-object (entlast))) ; List of entities to monitor
"Circle monitoring" ; Description (user data)
'((:vlr-modified . notify-modification)) ; List of events and callbacks
)
)
From now on, every time the circle is moved, resized, or modified in any way, the message is displayed.
Callback signature
Each callback function receives three arguments:
owner— the VLA object (not an ename) of the entity that triggered the eventreactor— the reactor itself, useful for accessing user data or removing itselfparameter-list— a list of additional information about the event (oftennil)
To convert the VLA object to an ename (required for entget), use vlax-vla-object->ename:
(defun my-callback (owner reactor parameter-list / entity data)
(setq entity (vlax-vla-object->ename owner))
(setq data (entget entity))
;; Process the data...
(princ)
)
Warning: the callback function of an object reactor must never modify the monitored entity directly in the
:vlr-modifiedcallback. This would cause an infinite loop (the modification triggers the reactor, which modifies the entity, which triggers the reactor...). If you need to modify an entity in response to an event, use the:vlr-objectClosedcallback.
Available events for object reactors
| Event | Fires when... |
|---|---|
:vlr-modified |
The entity is modified |
:vlr-erased |
The entity is deleted |
:vlr-copied |
The entity is copied |
:vlr-objectClosed |
The entity is closed after modification (safe time to make changes) |
The distinction between :vlr-modified and :vlr-objectClosed is important. The former fires during the modification (the entity is still "open" for writing), while the latter fires after (the entity is "closed" and its data is up to date). It is in :vlr-objectClosed that you can safely read the new values and modify other entities.
Database reactor: monitoring the drawing
Database reactors monitor events at the DWG file level:
(defun before-save (reactor arguments)
(princ "\nSave in progress — verifying data...")
;; Here you could check the integrity of links between entities
(princ)
)
(defun after-save (reactor arguments)
(princ "\nSave complete.")
(princ)
)
(vlr-dwg-reactor
nil
'((:vlr-beginSave . before-save)
(:vlr-saveComplete . after-save))
)
Available events for drawing reactors
| Event | Fires when... |
|---|---|
:vlr-beginSave |
The drawing is about to be saved |
:vlr-saveComplete |
The save is complete |
:vlr-beginClose |
The drawing is about to be closed |
:vlr-databaseToBeDestroyed |
The database is about to be destroyed |
Command reactor: responding to commands
Command reactors fire when the user (or a program) executes an AutoCAD command:
(defun monitor-erase (reactor arguments)
(if (= (car arguments) "ERASE")
(princ "\nWarning: entities are about to be deleted!")
)
(princ)
)
(vlr-command-reactor
nil
'((:vlr-commandWillStart . monitor-erase))
)
Available events for command reactors
| Event | Fires when... |
|---|---|
:vlr-commandWillStart |
A command is about to start |
:vlr-commandEnded |
A command has just finished |
:vlr-commandCancelled |
A command has been cancelled (Escape) |
:vlr-commandFailed |
A command has failed |
The first element in the arguments list contains the command name in uppercase (for example "ERASE", "MOVE", "COPY").
Managing reactors
Removing a reactor
Reactors consume resources and can slow down AutoCAD if you create too many. Remember to remove them when they are no longer needed:
;; Remove a specific reactor
(vlr-remove my-reactor)
Listing active reactors
;; List all active reactors, grouped by type
(vlr-reactors)
Accessing user data
The second argument of vlr-object-reactor (the string "Circle monitoring" in our example) is a free-form field. You can store any AutoLISP value there — a handle, a list, a configuration name. To read it back:
(vlr-data my-reactor) ; → "Circle monitoring"
This is very useful for passing context to your callbacks without using global variables.
Practical example: maintaining a link between entities
Let us combine handles (seen in the previous chapter) and reactors to maintain a link between a circle and a text entity that displays its radius. When the user modifies the circle, the text updates automatically:
(defun update-text (owner reactor parameter-list /
circle-entity circle-data radius
text-handle text-entity text-data)
;; Get the circle's radius (after closing, so the data is up to date)
(setq circle-entity (vlax-vla-object->ename owner))
(setq circle-data (entget circle-entity))
(setq radius (cdr (assoc 40 circle-data)))
;; Find the linked text via the handle in xdata
(setq circle-data (entget circle-entity '("WIIP_LIEN")))
(setq text-handle (cdr (assoc 1005 (cdadr (assoc -3 circle-data)))))
(if text-handle
(progn
(setq text-entity (handent text-handle))
(if text-entity
(progn
;; Update the text content
(setq text-data (entget text-entity))
(setq text-data
(subst
(cons 1 (strcat "R=" (rtos radius 2 2)))
(assoc 1 text-data)
text-data
)
)
(entmod text-data)
(entupd text-entity)
)
)
)
)
(princ)
)
(defun c:create-circle-with-label (/ center radius circle-entity circle-data
circle-handle text-entity text-data
text-handle)
(regapp "WIIP_LIEN")
;; Create the circle
(setq center (getpoint "\nCircle center: "))
(setq radius (getreal "\nRadius: "))
(entmake
(list
'(0 . "CIRCLE")
(cons 10 (append center '(0.0)))
(cons 40 radius)
)
)
(setq circle-entity (entlast))
(setq circle-handle (cdr (assoc 5 (entget circle-entity))))
;; Create the label text
(entmake
(list
'(0 . "TEXT")
(cons 10 (list (+ (car center) radius 5.0) (cadr center) 0.0))
(cons 40 (* radius 0.3))
(cons 1 (strcat "R=" (rtos radius 2 2)))
)
)
(setq text-entity (entlast))
(setq text-handle (cdr (assoc 5 (entget text-entity))))
;; Link the circle to the text via xdata
(setq circle-data (entget circle-entity))
(setq circle-data
(append circle-data
(list
(list -3
(list "WIIP_LIEN"
(cons 1005 text-handle)
)
)
)
)
)
(entmod circle-data)
;; Attach a reactor to the circle
(vlr-object-reactor
(list (vlax-ename->vla-object circle-entity))
"Radius label update"
'((:vlr-objectClosed . update-text))
)
(princ "\nCircle created with linked label.")
(princ)
)
When the user modifies the circle's radius (by stretching, scaling, etc.), the text updates automatically. Note the use of :vlr-objectClosed rather than :vlr-modified — this is the safe time to read the circle's new data and modify the text.
Summary
| Concept | Function / Syntax | Description |
|---|---|---|
| Object reactor | vlr-object-reactor |
Monitors modifications to specific entities |
| Drawing reactor | vlr-dwg-reactor |
Monitors DWG file events |
| Command reactor | vlr-command-reactor |
Monitors executed commands |
| Remove a reactor | vlr-remove |
Frees the reactor's resources |
| List reactors | vlr-reactors |
Returns all active reactors |
| User data | vlr-data |
Reads the reactor's free-form field |
| VLA to ename | vlax-vla-object->ename |
Converts a VLA object to an ename for entget |
| Key event | When to use it |
|---|---|
:vlr-modified |
To be notified of a change (read only) |
:vlr-objectClosed |
To modify other entities in response to a change |
:vlr-erased |
To clean up links when an entity is deleted |
:vlr-commandWillStart |
To intercept a command before it executes |
In the next chapter, we will see how to communicate with external applications like Microsoft Excel using ActiveX interoperability.
Need an AutoCAD (AutoLISP, ObjectARX, .NET, VBA) development? Contact me for a free quote.