Multi-Document Commands

A multi-document command allows users to switch documents at selected user prompts, while the command remains in control. The ability to have a single command remain active across documents is very complex. At the point of execution when a user has picked another document to switch to, all documents are polling for input. They therefore have potentially intricate command processor states established, including possibly nested commands, AutoLISP expressions, scripts active, and all in arbitrary nesting order.

One can't easily define, let alone graft, the pertinent elements of one state into another without major effort. Instead, the best strategy is for the application to retain control across user-specified document switches, even if the application has to be implemented as different commands in different windows. Then, all an application needs is a mechanism to chain separate commands running in different documents, and control which command to start when changing documents.

To synchronize the actions of the multiple commands, implement a reactor that overrides the following AcApDocManager reactor functions:

virtual void
documentActivated(
    AcApDocument* pActivatedDoc);
 
virtual void
documentToBeDeactivated(
    AcApDocument* pDeActivatedDoc);

The documentToBeActivated() reactor function can also be used, but it occurs before the document is activated. The document context has not been set in this case.

These callbacks are invoked whenever the user clicks on a different document to activate it. The reactors should only be used on AcApDocManager when in an initial command just before prompting for user input, at a point when document switching is to be supported. The reactor should be removed when any or all such prompts are completed or canceled. From the callback, invoke:

virtual Acad::ErrorStatus 
sendStringToExecute(
    AcApDocument* pAcTargetDocument,
    const char * pszExecute,
    bool bActivate = true,
    bool bWrapUpInactiveDoc = false,
    bool bEchoString = true) = 0;

This function queues up a string to be interpreted the next time the specified document is activated. The string should typically be a command invocation (we'll call this the secondary command), but can also be an AutoLISP expression, a command fragment, or a menu token. The string limit is 296 bytes, so longer sequences should be implemented as a SCRIPT command running a temporary script, or as an AutoLISP expression to load and execute an AutoLISP program. The new document will be locked according to the new command's lock level as specified during its registration.

If the input prompt in the initial command looks the same as the first prompt in the secondary command, the user need not be aware that two separate commands are taking place.

Note: Because this technique involves calling from the documentActivated() method, you should pass kFalse into the bActivate parameter to avoid errors or infinite loops.

Also, to manage the flow of control across documents, this callback should maintain whatever transition state the application needs. For example, a nonreentrant variant could remember the original document, and a flag for each document to indicate whether it is already active, and therefore not have to invoke sendStringToExecute().

When a multi-document command completes, the controlling application should be sure the command left no pending command states in previous documents. The application can do this by sending an ESC or ENTER to the documents it has traversed through, by using the bWrapUpInactiveDoc parameter of sendStringToExecute(). If this is not done, documents may be left in a non-quiescent state.

Coordination between the initial command and the secondary command (and possibly multiple invocations thereof) must be managed through static or heap-resident variables.

Both the initial and secondary commands should be registered through acedRegCmds() or acedDefun(). The initial command should be coded to complete successfully on its own, in case the user decides to perform the entire command without switching documents. The second command need not be a full command, just a portion of a command that can be invoked to accumulate information (rooted in a static structure) from different open documents, and apply the results. The second command may also have to be coded such that it can be reentered in yet another document, if that is how the command is supposed to be structured.

Remember that UNDO runs separately in each document when designing these constructs.

Note: The “normal” acedSSGet() is not viable, because it can prompt multiple times, thus not returning any selection set in progress. Instead, acedEntSel() is used, because it either returns an entity, or RTNONE, meaning the user is really done, or RTCAN, which can be either a real cancel or a “moved to another document” signal. Set the local “done” flag, perform the action, then queue up ESC to every other active document so that the command is finished up in that document the next time the user goes to click into it.