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:
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:
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:
Normal completion of a tool usually involves a little clean-up beyond what your tool does during its operation:
Aborting a tool usually involves a little clean-up:
The following samples are fully-functional C++, VB.NET, and VBScript tools illustrating all the techniques, function calls, and issues discussed above.
// 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 ENTITYHANDLE* hOriginalEnt; static long iCount; static ENTITYHANDLE hLastEnt; void WINAPI UndoRedo(char* szDllCommandLine) { // 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 wParam, LPARAM 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 < iCount; i++) { ENTITYHANDLE hEnt; // handle to an original entity from the saved list hEnt = hOriginalEnt[i]; // make the entity current and match its settings VCSetCurrentEntity(&iError, hEnt); VCMatchCurrentEntity(&iError); // duplicate the original entity to a new one VCDuplicateInDrawOrder(&iError, 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. 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 < iCount; i++) { ENTITYHANDLE hEnt; // get handle to rubber-band entity hEnt = hLastEnt + i + 1; // draw entities VCSetCurrentEntity(&iError, hEnt); 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 < iCount; i++) { ENTITYHANDLE hEnt; // get handle to rubber-band entity hEnt = hLastEnt + i + 1; // draw rubber-band entities with XOR VCSetCurrentEntity(&iError, hEnt); VCDrawCurrentEntityXOR(&iError); } } void WINAPI UndoRedo_MouseMove(WPARAM wParam, LPARAM 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 wParam, LPARAM 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 < iCount; i++) { ENTITYHANDLE hEnt; // get the rubber-band entity, erase it, an draw it // to remove it from screen hEnt = hLastEnt + i + 1; VCSetCurrentEntity(&iError, hEnt); 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(&iError, hEnt); VCSetCurrentUnErased(&iError); VCSetCurrentSelected(&iError); VCDrawCurrentEntity(&iError); } // Truncate the database starting with the rubber-band // to restore the database to its original condition. VCTruncFrom(&iError, hLastEnt + 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(MSG* pMsg) { // 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 < iCount; i++) { ENTITYHANDLE hEnt; short iPointCount; // get the number of points in the entity hEnt = hOriginalEnt[i]; VCSetCurrentEntity(&iError, hEnt); iPointCount = VCGetCurrentEntityPointCount(&iError); // loop through all the points in the entity for (short iPoint = 0; iPoint < iPointCount; iPoint++) { Point2D pt; // get the entity point from the original entity hEnt = hOriginalEnt[i]; VCSetCurrentEntity(&iError, hEnt); pt = VCGetCurrentEntityPoint(&iError, iPoint); // 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(&iError, hEnt); VCSetCurrentEntityPoint(&iError, iPoint, &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 < iCount; i++) { // 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(&iError, hOriginalEnt[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 String) As 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 Integer, ByRef uMsg As Integer, ByRef wParam As Integer, ByRef lParam As Integer, ByRef Time As Integer, ByRef PointX As Integer, ByRef PointY As Integer) As 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
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