Managing Undo and Redo


Undo and Redo allow users to quickly and easily reverse drawing operations in order to correct mistakes or to try alternatives.  Please refer to the Undo and Redo Explained topic for details of how Visual CADD™ implements undo/redo.

To recap how custom add-on tools created using the API and designed to support undo/redo must implement undo/redo:

Initializing the Tool for Undo/Redo

Preparation of the tool for undo/redo begins with how the tool ends.  Tools can create temporary or permanent entities and can erase or modify existing entities while they run.  Tools can end two ways, either normal completion or aborted.  When a tool completes normally, your tool may want or need to remove any temporary entities, such as rubber-bands, and will need to make permanent all the pending changes to existing entities, including correct undo/redo levels.  When a tool aborts, your tool should restore everything about the drawing to its original state as when the tool started, including all the original undo/redo levels.  Steps for initializing a custom add-on tool supporting undo/redo include:

Note that instead of VCIncrementUndoLevel and VCLastEntity, it is sometimes possible to call VCBeginOperation instead.  VCBeginOperation internally calls both VCIncrementUndoLevel and VCLastEntity so it can accomplish much the same thing when used in conjunction with VCAbortOperation and VCEndOperation.  However, there are limitations to VCBeginOperation: it must be paired with VCAbortOperation or VCEndOperation and therefore cannot support nested undo levels in a single tool; the VCLastEntity is saved for internal use whereas your tool is almost certain to need to call and save VCLastEntity for its use and is redundant; VCAbortOperation and VCEndOperation do some drawing clean-up upon tool completion, however in most cases your tool will need to do related clean-up for its own specific purposes and is redundant; and VCBeginOperation can produce unpredictable or incorrect results when your tool makes use of native tools which are also controlling undo/redo.  Legacy tools using VCBeginOperation, VCAbortOperation, and VCEndOperation should continue to work but they are not recommended for new development.

Tool Operations for Undo/Redo

The operation of your tool must always be aware of how each individual operation affects undo/redo and the steps required for normal completion or aborting the tool.  Following is an overview of the typical tool operations and a few issues involved:

Completing the Tool for Undo/Redo

Normal completion of a tool usually involves a little clean-up beyond what your tool does during its operation:

Note if VCBeginOperation was used during initialization, then instead of VCPurgeRedo you should call VCEndOperation.  If you used VCBeginOperation, it should be paired a VCEndOperation or VCAbortOperation.  VCEndOperation makes an internal call to VCPurgeRedo.  As discussed above, there are many disadvantages to using VCBeginOperation and VCEndOperation.  Legacy tools using VCBeginOperation, VCAbortOperation, and VCEndOperation should continue to work but they are not recommended for new development.

Aborting the Tool for Undo/Redo

Aborting a tool usually involves a little clean-up:

Note if VCBeginOperation was used during initialization, then instead of VCTruncFrom and VCDecrementUndoLevel you should call VCAbortOperation.  If you used VCBeginOperation, it should be paired a VCEndOperation or VCAbortOperation.  VCAbortOperation makes an internal call to VCTruncFrom and VCDecrementUndoLevel.  As discussed above, there are many disadvantages to using VCBeginOperation and VCAbortOperation.  Legacy tools using VCBeginOperation, VCAbortOperation, and VCEndOperation should continue to work but they are not recommended for new development.

The following samples are fully-functional C++, VB.NET, and VBScript tools illustrating all the techniques, function calls, and issues discussed above.

 

C++ VB.NET VBScript
// C++ to Implement Undo/Redo
//   lines starting with a // are comments
#include "stdafx.h"
#include "VCADDTools.h"
#include "UndoRedo.h"
 
//  file scope variables
static short iError;
static Point2D ptFrom;
static Point2D ptTo;
static BOOL tfRubberband;
static ENTITYHANDLEhOriginalEnt;
static long iCount;
static ENTITYHANDLE hLastEnt;
 
void WINAPI UndoRedo(charszDllCommandLine)
{
    //  initialize
    tfRubberband = FALSE;
    hOriginalEnt = NULL;
    hLastEnt = -1;
 
    //  required parameter but unused by this function
    //  circumvent the unreferenced formal parameter warning
    UNUSED_PARAMETER(szDllCommandLine);
 
    //  UndoRedo tool is much like the native Move/MV tool.  Its 
    //  purpose is to demonstrate managing undo/redo on the
    //  original selection and on the new entities.  The tool has 
    //  2 states:
    //      (1) place reference point
    //      (2) place offset point
 
    //  To manage the undo/redo of the original selection, 
    //  allocate an array to hold the original entity handles
    iCount = VCGetSelectionCount(&iError);
    if (iCount > 0)
    {
        //  allocate the array
        hOriginalEnt = new ENTITYHANDLE[iCount];
    }
    else
    {
        //  Nothing selected, abort and return.  In practice, 
        //  tools usually start the selection ribalog when a 
        //  selection is required but none is given.
        VCAbort();
        return;
    }
 
    //  Loop through the selection and save each entity handle.
    //  Start at the first selected entity.
    short iKind;
    VCFirstSelected(&iError, &iKind);
 
    //  Save the entity handles by looping through all selected 
    //  entities 
    long i = 0;
    while (!iError)
    {
        hOriginalEnt[i] = VCGetCurrentEntityHandle(&iError);
        i++;
        VCNextSelected(&iError, &iKind);
    }
 
    //  Save the original last entity handle for restoring the 
    //  database when done, for example, to erase temporary 
    //  entities in case of aborting
    VCLastEntity(&iError, &hLastEnt);
    
    //  Increment the undo level to support undo/redo.
    //  Optional - use VCBeginOperation() instead.  
    //      VCBeginOperation() does only two things - it 
    //      increments the undo level and saves the current end 
    //      of the database for cleanup when done.  This tool
    //      does both of those directly.
    VCIncrementUndoLevel(&iError);
 
    //  Optional - save the settings to be restored later
    VCSaveSettings();
 
    //  Start a user tool and define the status bar command line
    //  prompts for the UndoRedo tool.  The states are 0, 1.
    //  The first prompt for state 0 is set by VCSetUserTool()
    //  and the next prompt is set by VCSetPrompt().
    VCSetUserTool(1, "UndoRedo""Pick Reference Point");
    VCSetPrompt(1, "Pick Offset Point");
 
    //  Set alert events so our tool gets notifications from 
    //  Visual CADD about user interface events, other tool
    //  operations, and Windows messages.  We are requesting 
    //  alerts on mouse-down (point placement), mouse-move (for
    //  rubber-bands), tool pen-up (when tool ends normally),
    //  tool abort (when user aborts or another tool starts), and
    //  Windows messages.  Each of these has a corresponding 
    //  callback function defined in code below.
    VCSetAlertAppDllEx(&iError"API_Help_C.dll""UndoRedo", 
                        ALERT_APP_UTOOL_MOUSEDOWN + 
                        ALERT_APP_UTOOL_MOUSEMOVE + 
                        ALERT_APP_UTOOL_PENUP +
                        ALERT_APP_UTOOL_ABORT,
                        ALERT_APP_EX_PRE_TRANSLATE_GET_MESSAGE);
}
 
void WINAPI UndoRedo_MouseDown(WPARAM wParamLPARAM lParam)
{
    //  required parameters but unused by this function
    //  circumvent the unreferenced formal parameter warning
    UNUSED_PARAMETER(wParam);
    UNUSED_PARAMETER(lParam);
 
    //  get the tool state so we know the state of the tool
    short iState;
    iState = VCGetUserToolState(&iError);
 
    //  Visual CADD increases the tool state of user tools after 
    //  mouse clicks.  Tool state 1 means the tool state was 
    //  previously 0 and the user has clicked for the reference 
    //  point.
    if (iState == 1)
    {
        //  get the mouse-click point from Visual CADD
        VCGetUserToolLBDown(&iError, &ptFrom);
 
        //  Setup the rubber-band entities.  These entities will
        //  start by being exact duplicates of the originals.
        for (int i = 0; i < iCounti++)
        {
            ENTITYHANDLE hEnt;
            
            //  handle to an original entity from the saved list
            hEnt = hOriginalEnt[i];
    
            //  make the entity current and match its settings
            VCSetCurrentEntity(&iErrorhEnt);
            VCMatchCurrentEntity(&iError);
 
            //  duplicate the original entity to a new one
            VCDuplicateInDrawOrder(&iErrorhEnt);
            
            //  Erase the original entity, but DO NOT set its 
            //  redo level.  Erasing normally sets the redo level 
            //  so that the erased entity can be un-erased (or 
            //  re-done) by the Undo/OO tool.  Because we may 
            //  abort our tool before it completes, we may want 
            //  to later restore the original entities to their 
            //  original condition.  So we do not want to change
            //  their redo level yet.
            VCSetCurrentErasedNoRedo(&iError);
        }  
 
        //  set a flag so we know we have created rubber-bands
        tfRubberband = TRUE;
    }
 
    //  Tool state 2 means the tool state was previously 1 and 
    //  the user has clicked the offset point to finish the tool.
    else if (iState == 2)
    {
        //  draw the rubber-bands to remove them from the 
        //  screen
        DrawRubberBands();
 
        //  get the mouse-click point from Visual CADD
        VCGetUserToolLBDown(&iError, &ptTo);
 
        //  move the original entities to the rubber-band 
        //  entities by the offset
        MoveFromTo();
 
        //  draw the rubber-bands which are now the permanent new 
        //  entities
        for (int i = 0; i < iCounti++)
        {
            ENTITYHANDLE hEnt;
 
            //  get handle to rubber-band entity
            hEnt = hLastEnt + i + 1;
 
            //  draw entities
            VCSetCurrentEntity(&iErrorhEnt);
            VCDrawCurrentEntity(&iError);
        }  
 
        //  a mouse-click in the final state means we end our
        //  tool with PenUp
        VCPenUp();
    }
}
 
void DrawRubberBands(void)
{
    //  Drawing rubber-bands involves erasing the old 
    //  rubber-band and drawing the new one.  The function
    //  VCDrawCurrentEntityXOR() is used to draw entities 
    //  using the user's rubber-band color.  The XOR means 
    //  if it is already drawn on-screen XOR will remove it; 
    //  if it is not already drawn on-screen XOR will draw it.
    for (int i = 0; i < iCounti++)
    {
        ENTITYHANDLE hEnt;
 
        //  get handle to rubber-band entity
        hEnt = hLastEnt + i + 1;
 
        //  draw rubber-band entities with XOR
        VCSetCurrentEntity(&iErrorhEnt);
        VCDrawCurrentEntityXOR(&iError);
    }  
}
 
void WINAPI UndoRedo_MouseMove(WPARAM wParamLPARAM lParam)
{
    //  required parameters but unused by this function
    //  circumvent the unreferenced formal parameter warning
    UNUSED_PARAMETER(wParam);
    UNUSED_PARAMETER(lParam);
 
    //  get the tool state so we know the state of the tool
    short iState;
    iState = VCGetUserToolState(&iError);
 
    //  Use the mouse move to draw the rubber-band while the user
    //  is choosing the offset point.  Tool state 1 means the 
    //  user has already clicked the reference point so we will 
    //  draw the rubber-band at the current mouse location.
    if (iState == 1)
    {
        //  draw the rubber-bands to remove them from the 
        //  screen
        DrawRubberBands();
        
        //  get the mouse location point from Visual CADD
        VCGetUserToolMouseMove(&iError, &ptTo);
 
        //  move the original entities to the rubber-band 
        //  entities by the offset
        MoveFromTo();
 
        //  draw the rubber-bands to draw them on-screen
        DrawRubberBands();
    }
}
 
void WINAPI UndoRedo_Abort(WPARAM wParamLPARAM lParam)
{
    //  required parameters but unused by this function
    //  circumvent the unreferenced formal parameter warning
    UNUSED_PARAMETER(wParam);
    UNUSED_PARAMETER(lParam);
 
    //  UndoRedo tool has aborted, so we need to cleanup the 
    //  database.
 
    //  If UndoRedo made it as far as a rubber-band, then erase
    //  from the screen any rubber-bands and restore and draw the
    //  original entities.
    if (tfRubberband)
    {
        //  loop through all original entities
        for (int i = 0; i < iCounti++)
        {
            ENTITYHANDLE hEnt;
 
            //  get the rubber-band entity, erase it, an draw it
            //  to remove it from screen
            hEnt = hLastEnt + i + 1;
            VCSetCurrentEntity(&iErrorhEnt);
            VCSetCurrentErased(&iError);
            VCDrawCurrentEntity(&iError);
 
            //  get the original entity from the saved list, 
            //  un-erase it, an draw on-screen.  Note, this 
            //  restoration of the original entities is the 
            //  reason we did not change the original entity redo
            //  levels when we first erased them.  They are now 
            //  restored and have their original redo levels.
            hEnt = hOriginalEnt[i];
            VCSetCurrentEntity(&iErrorhEnt);
            VCSetCurrentUnErased(&iError);
            VCSetCurrentSelected(&iError);
            VCDrawCurrentEntity(&iError);
        }  
 
        //  Truncate the database starting with the rubber-band
        //  to restore the database to its original condition.
        VCTruncFrom(&iErrorhLastEnt + 1);
    }
 
    //  Decrement the undo level since the UndoRedo was aborted 
    //  and no entities were added.  Optional - if you used 
    //  VCBeginOperation(), then you should call 
    //  VCAbortOperation() which does the same VCTruncFrom() and 
    //  VCDecrementUndoLevel().
    VCDecrementUndoLevel(&iError);
 
    //  do housekeeping
    Done();
}
 
void Done(void)
{
    //  We are done so clear the alert events so Visual CADD
    //  no longer sends the alerts.
    VCClearAlertAppDll(&iError"API_Help_C.dll""UndoRedo");
 
    //  Restore the saved settings so the user settings are as 
    //  they began.
    VCRestoreSettings();
 
    //  delete the original entity list
    delete [] hOriginalEnt;
}
 
BOOL WINAPI UndoRedo_PreTranslateGetMessage(MSGpMsg)
{
    //  PreTranslateGetMessage processes posted messages, usually
    //  keyboard messages, but any posted messages may be 
    //  processed.
 
    //  The function return will tell Visual CADD if we have 
    //  processed the message and whether it should process it.
    //  The default is FALSE, we have not processed it.  Setting 
    //  to TRUE tells Visual CADD to not process the message.
 
    //  VK_ESCAPE is Esc key.  Abort the UndoRedo tool 
    //  anytime the user presses Esc.
    if ((pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_ESCAPE))
    {
        VCAbort();
        //  have processed the message, tell Visual CADD not to
        return TRUE;
    }
 
    //  have not processed the message, let Visual CADD
    return FALSE;
}
 
void MoveFromTo(void)
{
    //  The UndoRedo tool is a move-like tool to move selected 
    //  entities from a reference point to an offset point.  The 
    //  MoveFromTo() subroutine calculates the offset and applies
    //  that offset to each original entity to set the required 
    //  points for the new or rubber-band entities.
 
    //  calculate the offset
    double dx;
    double dy;
    dx = ptTo.x - ptFrom.x;
    dy = ptTo.y - ptFrom.y;
 
    //  loop through all original entities
    for (int i = 0; i < iCounti++)
    {
        ENTITYHANDLE hEnt;
        short iPointCount;
        
        //  get the number of points in the entity
        hEnt = hOriginalEnt[i];
        VCSetCurrentEntity(&iErrorhEnt);
        iPointCount = VCGetCurrentEntityPointCount(&iError);
 
        //  loop through all the points in the entity
        for (short iPoint = 0; iPoint < iPointCountiPoint++)
        {
            Point2D pt;
 
            //  get the entity point from the original entity
            hEnt = hOriginalEnt[i];
            VCSetCurrentEntity(&iErrorhEnt);
            pt = VCGetCurrentEntityPoint(&iErroriPoint);
 
            //  calculate the new point using the offset
            pt.x += dx;
            pt.y += dy;
 
            //  set the entity point in the rubber-band entity
            hEnt = hLastEnt + i + 1;
            VCSetCurrentEntity(&iErrorhEnt);
            VCSetCurrentEntityPoint(&iErroriPoint, &pt);
        }
    }
}
 
void UndoRedo_PenUp(void)
{
    //  get the tool state so we know the state of the tool
    short iState;
    iState = VCGetUserToolState(&iError);
 
    //  UndoRedo tool has completed normally, so we need to 
    //  cleanup the database and do final processing.
 
    //  Check that the user has clicked 2 points and not just
    //  run PenUp/PU prematurely
    if (iState < 2)
    {
        //  There were not 2 clicks, so Abort instead.
        VCAbort();
    }
    else
    {
        //  clear selection
        VCClearSelection();
 
        //  Loop through the original entities to set their redo 
        //  levels.  Note, we purposely did not already set the 
        //  redo levels because we might have aborted the tool 
        //  and still wanted the original redo levels.
        for (short i = 0; i < iCounti++)
        {
            // assign the current undo level of the drawing to 
            // the original entities and draw them.  They are
            // already erased, so drawing will remove them from 
            // screen.
            VCSetCurrentEntity(&iErrorhOriginalEnt[i]);
            VCSetCurrentEntityRedo(&iError);
            VCDrawCurrentEntity(&iError);
        }
 
        //  Purge the database to cleanup redo conflicts possibly
        //  left by previous tools.  Optional - if you used 
        //  VCBeginOperation(), then you should call 
        //  VCEndOperation() which does the same VCPurgeRedo().
        VCPurgeRedo(&iError);
 
        //  do housekeeping
        Done();
    }
}
' VB.NET to Implement Undo/Redo
'   lines starting with an apostrophe are comments
 
Public Class UndoRedo
 
    ' create Visual CADD object with events
    Shared WithEvents vcadd As New VCadd32.Automation
 
    ' create point objects and variables
    Shared ptFrom As New VCadd32.VCPoint2D
    Shared ptTo As New VCadd32.VCPoint2D
    Shared pt0 As New VCadd32.VCPoint2D
    Shared tfRubberband As Boolean
    Shared hOriginalEnt As Long()
    Shared iCount As Long
    Shared hLastEnt As Long
 
    Public Shared Function Run(ByVal str As StringAs Integer
        '   initialize
        Array.Resize(hOriginalEnt, 0)
        tfRubberband = False
        hLastEnt = -1
 
        '   UndoRedo tool is much like the native Move/MV tool.  Its 
        '   purpose is to demonstrate managing undo/redo on the
        '   original selection and on the new entities.  The tool has 
        '   2 states:
        '       (1) place reference point
        '       (2) place offset point
 
        '   To manage the undo/redo of the original selection, 
        '   allocate an array to hold the original entity handles.
        '   First check for a selection.
        iCount = vcadd.GetSelectionCount()
        If (iCount > 0) Then
            '   allocate the array
            Array.Resize(hOriginalEnt, iCount)
        Else
            '   Nothing selected, abort and return.  In practice, 
            '   tools usually start the selection ribalog when a 
            '   selection is required but none is given.
            vcadd.Abort()
            Return True
        End If
 
        '   Loop through the selection and save each entity handle.
        '   Start at the first selected entity.
        Dim iKind As Short
        iKind = vcadd.FirstSelected()
 
        '   Save the entity handles by looping through all selected 
        '   entities
        Dim i As Long = 0
        Do While (vcadd.GetErrorCode() = 0)
            hOriginalEnt(i) = vcadd.GetCurrentEntityHandle()
            i = i + 1
            iKind = vcadd.NextSelected()
        Loop
 
        '   Save the original last entity handle for restoring the 
        '   database when done, for example, to erase temporary 
        '   entities in case of aborting
        hLastEnt = vcadd.LastEntity()
 
        '   Increment the undo level to support undo/redo.
        '   Optional - use VCBeginOperation() instead.  
        '       VCBeginOperation() does only two things - it 
        '       increments the undo level and saves the current end 
        '       of the database for cleanup when done.  This tool
        '       does both of those directly.
        vcadd.IncrementUndoLevel()
 
        '   Optional - save the settings to be restored later
        vcadd.SaveSettings()
 
        '   Start a user tool and define the status bar command line
        '   prompts for the UndoRedo tool.  The states are 0, 1.
        '   The first prompt for state 0 is set by VCSetUserTool()
        '   and the next prompt is set by VCSetPrompt().
        vcadd.SetUserTool(1, "UndoRedo""Pick Reference Point")
        vcadd.SetPrompt(1, "Pick Offset Point")
 
        '   Set alert events so our tool gets notifications from 
        '   Visual CADD about user interface events, other tool
        '   operations, and Windows messages.  We are requesting 
        '   alerts on mouse-down (point placement), mouse-move (for
        '   rubberbands), pen up (when tool ends normally),
        '   abort (when user aborts or another tool starts), and
        '   Windows messages.  Each of these has a corresponding 
        '   callback function below.
        vcadd.SetAlertEvent(ALERT_APP_UTOOL_MOUSEDOWN + ALERT_APP_UTOOL_MOUSEMOVE + ALERT_APP_UTOOL_PENUP + ALERT_APP_UTOOL_ABORT, ALERT_APP_EX_PRE_TRANSLATE_GET_MESSAGE)
 
        '   Attach the alert events to the handler functions
        AddHandler vcadd.MouseDown, AddressOf vcadd_MouseDown
        AddHandler vcadd.MouseMove, AddressOf vcadd_MouseMove
        AddHandler vcadd.PenUp, AddressOf vcadd_PenUp
        AddHandler vcadd.Abort, AddressOf vcadd_Abort
        AddHandler vcadd.PreTranslateGetMessage, AddressOf vcadd_PreTranslateGetMessage
 
        ' return
        Return True
 
    End Function
 
    Private Shared Function vcadd_PreTranslateGetMessage(ByRef hWnd As IntegerByRef uMsg As Integer,
                                                    ByRef wParam As IntegerByRef lParam As Integer,
                                                    ByRef Time As IntegerByRef PointX As Integer,
                                                    ByRef PointY As IntegerAs Integer
        '   PreTranslateGetMessage processes posted messages, usually
        '   keyboard messages, but any posted messages may be 
        '   processed.
 
        '   The function return will tell Visual CADD if we have 
        '   processed the message and whether it should process it.
        '   The default is FALSE, we have not processed it.  Setting 
        '   to TRUE tells Visual CADD to not process the message.
 
        '   &H100 is the WM_KEYDOWN and 27 is VK_ESCAPE or Esc key.
        '   Abort the UndoRedo tool anytime the user presses Esc.
        If ((uMsg = &H100) And (wParam = 27)) Then
            vcadd.Abort()
            Return True
        End If
 
        '   have not processed the message, let Visual CADD
        Return False
 
    End Function
 
    Private Shared Sub vcadd_MouseDown(wParam As Integer, lParam As Integer)
        '   get the tool state so we know the state of the tool
        Dim iState As Short
        iState = vcadd.GetUserToolState()
 
        '   Visual CADD increases the tool state of user tools after 
        '   mouse clicks.  Tool state 1 means the tool state was 
        '   previously 0 and the user has clicked for the reference 
        '   point.
        If (iState = 1) Then
            '   get the mouse-click point from Visual CADD
            ptFrom = vcadd.GetUserToolLBDown()
 
            '   Setup the rubber-band entities.  These entities will
            '   start by being exact duplicates of the originals.
            For i = 0 To iCount - 1
                '   handle to an original entity from the saved list
                Dim hEnt As Long
                hEnt = hOriginalEnt(i)
 
                '   make the entity current and match its settings
                vcadd.SetCurrentEntity(hEnt)
                vcadd.MatchCurrentEntity()
 
                '   duplicate the original entity to a new one
                vcadd.DuplicateInDrawOrder(hEnt)
 
                '   Erase the original entity, but DO NOT set its 
                '   redo level.  Erasing normally sets the redo level 
                '   so that the erased entity can be un-erased (or 
                '   re-done) by the Undo/OO tool.  Because we may 
                '   abort our tool before it completes, we may want 
                '   to later restore the original entities to their 
                '   original condition.  So we do not want to change
                '   their redo level yet.
                vcadd.SetCurrentErasedNoRedo()
            Next
 
            '   set a flag so we know we have created rubber-bands
            tfRubberband = True
 
            '   Tool state 2 means the tool state was previously 1 and 
            '   the user has clicked the offset point to finish the tool.
        ElseIf (iState = 2) Then
            '   draw the rubber-bands to remove them from the 
            '   screen
            DrawRubberBands()
 
            '   get the mouse-click point from Visual CADD
            ptTo = vcadd.GetUserToolLBDown()
 
            '   move the original entities to the rubber-band 
            '   entities by the offset
            MoveFromTo()
 
            '   draw the rubber-bands which are now the permanent new 
            '   entities
            For i = 0 To iCount - 1
                '   get handle to rubber-band entity
                Dim hEnt As Long
                hEnt = hLastEnt + i + 1
 
                '   draw entities
                vcadd.SetCurrentEntity(hEnt)
                vcadd.DrawCurrentEntity()
            Next
 
            '   a mouse-click in the final state means we end our
            '   tool with PenUp
            vcadd.PenUp()
        End If
 
    End Sub
 
    Private Shared Sub vcadd_MouseMove(wParam As Integer, lParam As Integer)
        '   get the tool state so we know the state of the tool
        Dim iState As Short
        iState = vcadd.GetUserToolState()
 
        '   Use the mouse move to draw the rubber-band while the user
        '   is choosing the offset point.  Tool state 1 means the 
        '   user has already clicked the reference point so we will 
        '   draw the rubber-band at the current mouse location.
        If (iState = 1) Then
            '   draw the rubber-bands to remove them from the 
            '   screen
            DrawRubberBands()
 
            '   get the mouse location point from Visual CADD
            ptTo = vcadd.GetUserToolMouseMove()
 
            '   move the original entities to the rubber-band 
            '   entities by the offset
            MoveFromTo()
 
            '   draw the rubber-bands to draw them on-screen
            DrawRubberBands()
        End If
    End Sub
 
 
    Private Shared Sub vcadd_PenUp(wParam As Integer, lParam As Integer)
        '   get the tool state so we know the state of the tool
        Dim iState As Short
        iState = vcadd.GetUserToolState()
 
        '   UndoRedo tool has completed normally, so we need to 
        '   cleanup the database and do final processing.
 
        '   Check that the user has clicked 2 points and not just
        '   run PenUp/PU prematurely
        If (iState < 2) Then
            '   There were not 2 clicks, so Abort instead.
            vcadd.Abort()
        Else
            '   clear selection
            vcadd.ClearSelection()
 
            '   Loop through the original entities to set their redo 
            '   levels.  Note, we purposely did not already set the 
            '   redo levels because we might have aborted the tool 
            '   and still wanted the original redo levels.
            For i = 0 To iCount - 1
                '  assign the current undo level of the drawing to 
                '  the original entities and draw them.  They are
                '  already erased, so drawing will remove them from 
                '  screen.
                Dim hEnt As Long
                hEnt = hOriginalEnt(i)
                vcadd.SetCurrentEntity(hEnt)
                vcadd.SetCurrentEntityRedo()
                vcadd.DrawCurrentEntity()
            Next
 
            '   Purge the database to cleanup redo conflicts possibly
            '   left by previous tools.  Optional - if you used 
            '   VCBeginOperation(), then you should call 
            '   VCEndOperation() which does the same VCPurgeRedo().
            vcadd.PurgeRedo()
 
            '   do housekeeping
            Done()
        End If
    End Sub
 
    Private Shared Sub Done()
        '   We are done so clear the alert events so Visual CADD
        '   no longer sends the alerts.
        vcadd.ClearAlertEvent()
 
        '   Remove the alert events from the handler functions
        RemoveHandler vcadd.MouseDown, AddressOf vcadd_MouseDown
        RemoveHandler vcadd.MouseMove, AddressOf vcadd_MouseMove
        RemoveHandler vcadd.PenUp, AddressOf vcadd_PenUp
        RemoveHandler vcadd.Abort, AddressOf vcadd_Abort
        RemoveHandler vcadd.PreTranslateGetMessage, AddressOf vcadd_PreTranslateGetMessage
 
        '   Restore the saved settings so the user settings are as 
        '   they began.
        vcadd.RestoreSettings()
    End Sub
 
    Private Shared Sub vcadd_Abort(wParam As Integer, lParam As Integer)
        '   UndoRedo tool has aborted, so we need to cleanup the 
        '   database.
 
        '   If UndoRedo made it as far as a rubber-band, then erase
        '   from the screen any rubber-bands and restore and draw the
        '   original entities.
        If (tfRubberband) Then
            '   loop through all original entities
            For i = 0 To iCount - 1
                '   get the rubber-band entity, erase it, an draw it
                '   to remove it from screen
                Dim hEnt As Long
                hEnt = hLastEnt + i + 1
                vcadd.SetCurrentEntity(hEnt)
                vcadd.SetCurrentErased()
                vcadd.DrawCurrentEntity()
 
                '   get the original entity from the saved list, 
                '   un-erase it, an draw on-screen.  Note, this 
                '   restoration of the original entities is the 
                '   reason we did not change the original entity redo
                '   levels when we first erased them.  They are now 
                '   restored and have their original redo levels.
                hEnt = hOriginalEnt(i)
                vcadd.SetCurrentEntity(hEnt)
                vcadd.SetCurrentUnErased()
                vcadd.SetCurrentSelected()
                vcadd.DrawCurrentEntity()
            Next
 
            '   Truncate the database starting with the rubber-band
            '   to restore the database to its original condition.
            vcadd.TruncFrom(hLastEnt + 1)
        End If
 
        '   Decrement the undo level since the UndoRedo was aborted 
        '   and no entities were added.  Optional - if you used 
        '   VCBeginOperation(), then you should call 
        '   VCAbortOperation() which does the same VCTruncFrom() and 
        '   VCDecrementUndoLevel().
        vcadd.DecrementUndoLevel()
 
        '   do housekeeping
        Done()
 
    End Sub
 
    Private Shared Sub DrawRubberBands()
        '   Drawing rubber-bands involves erasing the old 
        '   rubber-band and drawing the new one.  The function
        '   VCDrawCurrentEntityXOR() is used to draw entities 
        '   using the user's rubber-band color.  The XOR means 
        '   if it is already drawn on-screen XOR will remove it 
        '   if it is not already drawn on-screen XOR will draw it.
        For i = 0 To iCount - 1
            '   get handle to rubber-band entity
            Dim hEnt As Long
            hEnt = hLastEnt + i + 1
 
            '   draw rubber-band entities with XOR
            vcadd.SetCurrentEntity(hEnt)
            vcadd.DrawCurrentEntityXOR()
        Next
    End Sub
 
    Private Shared Sub MoveFromTo()
        '   The UndoRedo tool is a move-like tool to move selected 
        '   entities from a reference point to an offset point.  The 
        '   MoveFromTo() subroutine calculates the offset and applies
        '   that offset to each original entity to set the required 
        '   points for the new or rubber-band entities.
 
        '   calculate the offset
        Dim dx As Double
        Dim dy As Double
        dx = ptTo.X - ptFrom.X
        dy = ptTo.Y - ptFrom.Y
 
        '   loop through all original entities
        For i = 0 To iCount - 1
            '   get the number of points in the entity
            Dim hEnt As Long
            hEnt = hOriginalEnt(i)
            vcadd.SetCurrentEntity(hEnt)
 
            Dim iPointCount As Short
            iPointCount = vcadd.GetCurrentEntityPointCount()
 
            '   loop through all the points in the entity
            For iPoint = 0 To iPointCount - 1
                '   get the entity point from the original entity
                hEnt = hOriginalEnt(i)
                vcadd.SetCurrentEntity(hEnt)
                pt0 = vcadd.GetCurrentEntityPoint(iPoint)
 
                '   calculate the new point using the offset
                pt0.X = pt0.X + dx
                pt0.Y = pt0.Y + dy
 
                '   set the entity point in the rubber-band entity
                hEnt = hLastEnt + i + 1
                vcadd.SetCurrentEntity(hEnt)
                vcadd.SetCurrentEntityPoint(iPoint, pt0)
            Next
        Next
    End Sub
 
End Class
' VBScript to Implement Undo/Redo
'   lines starting with an apostrophe are comments
 
' create Visual CADD object and connect to events
Set vcadd = WScript.CreateObject("VisualCADD.Application.9")
WScript.ConnectObject vcadd, "vcadd_"
 
' create point objects and variables
Set ptFrom = CreateObject("VisualCADD.Point2D.9")
Set ptTo = CreateObject("VisualCADD.Point2D.9")
Set pt0 = CreateObject("VisualCADD.Point2D.9")
 
' create point objects and variables
dim tfRubberband 
dim hOriginalEnt()
dim iCount 
dim hLastEnt 
tfRubberband = False
hLastEnt = -1
 
'   UndoRedo tool is much like the native Move/MV tool.  Its 
'   purpose is to demonstrate managing undo/redo on the
'   original selection and on the new entities.  The tool has 
'   2 states:
'       (1) place reference point
'       (2) place offset point
 
'   To manage the undo/redo of the original selection, 
'   allocate an array to hold the original entity handles.
'   First check for a selection.
iCount = vcadd.GetSelectionCount()
If (iCount > 0) Then
    '   allocate the array
    ReDim hOriginalEnt(iCount)
Else
    '   Nothing selected, abort and return.  In practice, 
    '   tools usually start the selection ribalog when a 
    '   selection is required but none is given.
    vcadd.Abort()
    WScript.Quit
End If
 
'   Loop through the selection and save each entity handle.
'   Start at the first selected entity.
Dim iKind
iKind = vcadd.FirstSelected()
 
'   Save the entity handles by looping through all selected 
'   entities
Dim i
i = 0
Do While (vcadd.GetErrorCode() = 0)
    i = i + 1
    hOriginalEnt(i) = vcadd.GetCurrentEntityHandle()
    iKind = vcadd.NextSelected()
Loop
 
'   Save the original last entity handle for restoring the 
'   database when done, for example, to erase temporary 
'   entities in case of aborting
hLastEnt = vcadd.LastEntity()
 
'   Increment the undo level to support undo/redo.
'   Optional - use VCBeginOperation() instead.  
'       VCBeginOperation() does only two things - it 
'       increments the undo level and saves the current end 
'       of the database for cleanup when done.  This tool
'       does both of those directly.
vcadd.IncrementUndoLevel()
 
'   Optional - save the settings to be restored later
vcadd.SaveSettings()
 
'   Start a user tool and define the status bar command line
'   prompts for the UndoRedo tool.  The states are 0, 1.
'   The first prompt for state 0 is set by VCSetUserTool()
'   and the next prompt is set by VCSetPrompt().
vcadd.SetUserTool 1, "UndoRedo""Pick Reference Point"
vcadd.SetPrompt 1, "Pick Offset Point"
 
'   Set alert events so our tool gets notifications from 
'   Visual CADD about user interface events, other tool
'   operations, and Windows messages.  We are requesting 
'   alerts on mouse-down (point placement), mouse-move (for
'   rubberbands), pen up (when tool ends normally),
'   abort (when user aborts or another tool starts), and
'   Windows messages.  Each of these has a corresponding 
'   callback function below.
vcadd.SetAlertEvent &h01 + &h02 + &h04 + &h20, &h01
 
'   Loop to keep the script running while we wait for the user input
tfLoop = True
While tfLoop = True
    '   Brief sleep to free the proceesor.  UndoRedo still gets events 
    '   during the sleep
    Wscript.Sleep 100
Wend
 
Set VCadd = Nothing
Set ptFrom = Nothing
Set ptTo = Nothing
Set pt0 = Nothing
 
Function vcadd_PreTranslateGetMessage(hWnd, uMsg , wParam , lParam , Time , PointX , PointY)
    '   PreTranslateGetMessage processes posted messages, usually
    '   keyboard messages, but any posted messages may be 
    '   processed.
 
    '   The function return will tell Visual CADD if we have 
    '   processed the message and whether it should process it.
    '   The default is FALSE, we have not processed it.  Setting 
    '   to TRUE tells Visual CADD to not process the message.
 
    '   &H100 is the WM_KEYDOWN and 27 is VK_ESCAPE or Esc key.
    '   Abort the UndoRedo tool anytime the user presses Esc.
    If ((uMsg = &H100) And (wParam = 27)) Then
        vcadd.Abort()
        vcadd_PreTranslateGetMessage = True
    End If
 
    '   have not processed the message, let Visual CADD
    vcadd_PreTranslateGetMessage = False
 
End Function
 
Sub vcadd_MouseDown(wParam, lParam)
    '   get the tool state so we know the state of the tool
    Dim iState
    iState = vcadd.GetUserToolState()
 
    '   Visual CADD increases the tool state of user tools after 
    '   mouse clicks.  Tool state 1 means the tool state was 
    '   previously 0 and the user has clicked for the reference 
    '   point.
    If (iState = 1) Then
        '   get the mouse-click point from Visual CADD
        Set ptFrom = vcadd.GetUserToolLBDown()
 
        '   Setup the rubber-band entities.  These entities will
        '   start by being exact duplicates of the originals.
        For i = 1 To iCount
            '   handle to an original entity from the saved list
            Dim hEnt 
            hEnt = hOriginalEnt(i)
 
            '   make the entity current and match its settings
            vcadd.SetCurrentEntity(hEnt)
            vcadd.MatchCurrentEntity()
 
            '   duplicate the original entity to a new one
            vcadd.DuplicateInDrawOrder(hEnt)
 
            '   Erase the original entity, but DO NOT set its 
            '   redo level.  Erasing normally sets the redo level 
            '   so that the erased entity can be un-erased (or 
            '   re-done) by the Undo/OO tool.  Because we may 
            '   abort our tool before it completes, we may want 
            '   to later restore the original entities to their 
            '   original condition.  So we do not want to change
            '   their redo level yet.
            vcadd.SetCurrentErasedNoRedo()
        Next
 
        '   set a flag so we know we have created rubber-bands
        tfRubberband = True
 
        '   Tool state 2 means the tool state was previously 1 and 
        '   the user has clicked the offset point to finish the tool.
    ElseIf (iState = 2) Then
        '   draw the rubber-bands to remove them from the 
        '   screen
        DrawRubberBands()
 
        '   get the mouse-click point from Visual CADD
        Set ptTo = vcadd.GetUserToolLBDown()
 
        '   move the original entities to the rubber-band 
        '   entities by the offset
        MoveFromTo()
 
        '   draw the rubber-bands which are now the permanent new 
        '   entities
        For i = 1 To iCount
            '   get handle to rubber-band entity
            hEnt = hLastEnt + i
 
            '   draw entities
            vcadd.SetCurrentEntity(hEnt)
            vcadd.DrawCurrentEntity()
        Next
 
        '   a mouse-click in the final state means we end our
        '   tool with PenUp
        vcadd.PenUp()
    End If
 
End Sub
 
Sub vcadd_MouseMove(wParam, lParam)
    '   get the tool state so we know the state of the tool
    Dim iState 
    iState = vcadd.GetUserToolState()
 
    '   Use the mouse move to draw the rubber-band while the user
    '   is choosing the offset point.  Tool state 1 means the 
    '   user has already clicked the reference point so we will 
    '   draw the rubber-band at the current mouse location.
    If (iState = 1) Then
        '   draw the rubber-bands to remove them from the 
        '   screen
        DrawRubberBands()
 
        '   get the mouse location point from Visual CADD
        Set ptTo = vcadd.GetUserToolMouseMove()
 
        '   move the original entities to the rubber-band 
        '   entities by the offset
        MoveFromTo()
 
        '   draw the rubber-bands to draw them on-screen
        DrawRubberBands()
    End If
End Sub
 
Sub vcadd_PenUp(wParam, lParam)
    '   get the tool state so we know the state of the tool
    Dim iState 
    iState = vcadd.GetUserToolState()
 
    '   UndoRedo tool has completed normally, so we need to 
    '   cleanup the database and do final processing.
 
    '   Check that the user has clicked 2 points and not just
    '   run PenUp/PU prematurely
    If (iState < 2) Then
        '   There were not 2 clicks, so Abort instead.
        vcadd.Abort()
    Else
        '   clear selection
        vcadd.ClearSelection()
 
        '   Loop through the original entities to set their redo 
        '   levels.  Note, we purposely did not already set the 
        '   redo levels because we might have aborted the tool 
        '   and still wanted the original redo levels.
        For i = 1 To iCount
            '  assign the current undo level of the drawing to 
            '  the original entities and draw them.  They are
            '  already erased, so drawing will remove them from 
            '  screen.
            Dim hEnt 
            hEnt = hOriginalEnt(i)
            vcadd.SetCurrentEntity(hEnt)
            vcadd.SetCurrentEntityRedo()
            vcadd.DrawCurrentEntity()
        Next
 
        '   Purge the database to cleanup redo conflicts possibly
        '   left by previous tools.  Optional - if you used 
        '   VCBeginOperation(), then you should call 
        '   VCEndOperation() which does the same VCPurgeRedo().
        vcadd.PurgeRedo()
 
        '   do housekeeping
        Done()
    End If
End Sub
 
Sub Done()
    '   We are done so clear the alert events so Visual CADD
    '   no longer sends the alerts.
    vcadd.ClearAlertEvent()
 
    '   Restore the saved settings so the user settings are as 
    '   they began.
    vcadd.RestoreSettings()
 
    tfLoop = False
End Sub
 
Sub vcadd_Abort(wParam, lParam)
    '   UndoRedo tool has aborted, so we need to cleanup the 
    '   database.
 
    '   If UndoRedo made it as far as a rubber-band, then erase
    '   from the screen any rubber-bands and restore and draw the
    '   original entities.
    If (tfRubberband) Then
        '   loop through all original entities
        For i = 1 To iCount
            '   get the rubber-band entity, erase it, an draw it
            '   to remove it from screen
            Dim hEnt 
            hEnt = hLastEnt + i
            vcadd.SetCurrentEntity(hEnt)
            vcadd.SetCurrentErased()
            vcadd.DrawCurrentEntity()
 
            '   get the original entity from the saved list, 
            '   un-erase it, an draw on-screen.  Note, this 
            '   restoration of the original entities is the 
            '   reason we did not change the original entity redo
            '   levels when we first erased them.  They are now 
            '   restored and have their original redo levels.
            hEnt = hOriginalEnt(i)
            vcadd.SetCurrentEntity(hEnt)
            vcadd.SetCurrentUnErased()
            vcadd.SetCurrentSelected()
            vcadd.DrawCurrentEntity()
        Next
 
        '   Truncate the database starting with the rubber-band
        '   to restore the database to its original condition.
        vcadd.TruncFrom(hLastEnt + 1)
    End If
 
    '   Decrement the undo level since the UndoRedo was aborted 
    '   and no entities were added.  Optional - if you used 
    '   VCBeginOperation(), then you should call 
    '   VCAbortOperation() which does the same VCTruncFrom() and 
    '   VCDecrementUndoLevel().
    vcadd.DecrementUndoLevel()
 
    '   do housekeeping
    Done()
 
End Sub
 
Sub DrawRubberBands()
    '   Drawing rubber-bands involves erasing the old 
    '   rubber-band and drawing the new one.  The function
    '   VCDrawCurrentEntityXOR() is used to draw entities 
    '   using the user's rubber-band color.  The XOR means 
    '   if it is already drawn on-screen XOR will remove it 
    '   if it is not already drawn on-screen XOR will draw it.
    For i = 1 To iCount
        '   get handle to rubber-band entity
        Dim hEnt 
        hEnt = hLastEnt + i
 
        '   draw rubber-band entities with XOR
        vcadd.SetCurrentEntity(hEnt)
        vcadd.DrawCurrentEntityXOR()
    Next
End Sub
 
Sub MoveFromTo()
    '   The UndoRedo tool is a move-like tool to move selected 
    '   entities from a reference point to an offset point.  The 
    '   MoveFromTo() subroutine calculates the offset and applies
    '   that offset to each original entity to set the required 
    '   points for the new or rubber-band entities.
 
    '   calculate the offset
    Dim dx 
    Dim dy 
    dx = ptTo.X - ptFrom.X
    dy = ptTo.Y - ptFrom.Y
 
    '   loop through all original entities
    For i = 1 To iCount
        '   get the number of points in the entity
        Dim hEnt 
        hEnt = hOriginalEnt(i)
        vcadd.SetCurrentEntity(hEnt)
 
        Dim iPointCount 
        iPointCount = vcadd.GetCurrentEntityPointCount()
 
        '   loop through all the points in the entity
        For iPoint = 0 To iPointCount - 1
            '   get the entity point from the original entity
            hEnt = hOriginalEnt(i)
            vcadd.SetCurrentEntity(hEnt)
            Set pt0 = vcadd.GetCurrentEntityPoint(iPoint)
 
            '   calculate the new point using the offset
            pt0.X = pt0.X + dx
            pt0.Y = pt0.Y + dy
 
            '   set the entity point in the rubber-band entity
            hEnt = hLastEnt + i
            vcadd.SetCurrentEntity(hEnt)
            vcadd.SetCurrentEntityPoint iPoint, pt0
        Next
    Next
End Sub

 

See Also:

VCAbort, VCFirstSelected, VCGetCurrentEntityHandle, VCNextSelected, VCLastEntity, VCIncrementUndoLevel, VCSaveSettings, VCSetUserTool, VCSetPrompt, SetAlertEvent, VCSetAlertAppDllEx, VCGetUserToolState, VCGetUserToolLBDown, VCSetCurrentEntity, VCMatchCurrentEntity, VCDuplicateInDrawOrder, VCSetCurrentErasedNoRedo, VCGetUserToolLBDown, VCDrawCurrentEntity, VCPenUp, VCDrawCurrentEntityXOR, VCGetUserToolMouseMove, VCSetCurrentErased, VCSetCurrentUnErased, VCSetCurrentSelected, VCTruncFrom, VCDecrementUndoLevel, ClearAlertEvent, VCClearAlertAppDll, VCRestoreSettings, VCGetCurrentEntityPointCount, VCGetCurrentEntityPoint, VCSetCurrentEntityPoint, VCSetCurrentEntityRedo, VCPurgeRedo