A question was recently asked in the customization newsgroup about how to combine several actions into one undo. I think this topic is of general interest and a good one to discuss here. I think this is a capability that's typically overlooked but is something that's easy to add to your existing macros. Below is an example of a simple macro that draws a triangle on the active sketch.
' Check to see that a sketch is active.
If Not TypeOf ThisApplication.ActiveEditObject Is sketch Then
MsgBox "A sketch must be active."
Exit Sub
End If
' Get a reference to the sketch.
Dim oSketch As sketch
Set oSketch = ThisApplication.ActiveEditObject
Dim oTG As TransientGeometry
Set oTG = ThisApplication.TransientGeometry
' Draw the three lines of the triangle.
Dim aoLines(2) As SketchLine
Set aoLines(0) = oSketch.SketchLines.AddByTwoPoints( _
oTG.CreatePoint2d(0, 0), _
oTG.CreatePoint2d(5, 0))
Set aoLines(1) = oSketch.SketchLines.AddByTwoPoints( _
aoLines(0).EndSketchPoint, _
oTG.CreatePoint2d(2.5, 4))
Set aoLines(2) = oSketch.SketchLines.AddByTwoPoints( _
aoLines(1).EndSketchPoint, _
aoLines(0).StartSketchPoint)
' Add a horizontal constraint to the bottom line.
Call oSketch.GeometricConstraints.AddHorizontal(aoLines(0))
End Sub
If you run this program it will create a triangle. If you want to get rid of the triangle you'll need to do 4 undo steps, one for the horizontal constraint and three for the lines. Since from the users perspective this was all created as a single step it would be nice if it would undo using a single undo.
Transaction Basics
You can combine multiple actions into a single undo using transactions. A transaction is any action that can be undone. Many, if not most, API calls are not transacted. In the example above, checking that a sketch is active, getting the sketch, and getting the TransientGeometry are not transacted since they don't cause any change to the current document. Creating the lines and the horizontal constraint are automatically transacted. If you don't do anything special, each API call that causes a transaction will show up in the undo list as an individual item and can be undone.
To combine a set of actions into a single undo the API allows you to create your own transaction which wraps the other transactions. Since the other transactions are nested within your transaction they aren't visible in the undo command and are undone and redone as a group whenever your transaction is undone or redone. The functionality to use transactions through the API is supported through the TransactionManager object. The modified version of the previous program shown below demonstrates this.
' Check to see that a sketch is active.
If Not TypeOf ThisApplication.ActiveEditObject Is sketch Then
MsgBox "A sketch must be active."
Exit Sub
End If
' Get a reference to the sketch.
Dim oSketch As sketch
Set oSketch = ThisApplication.ActiveEditObject
Dim oTG As TransientGeometry
Set oTG = ThisApplication.TransientGeometry
' Create a transaction.
Dim oTrans As Transaction
Set oTrans = oTransMgr.StartTransaction( _
ThisApplication.ActiveDocument, "Create Triangle")
' Draw the three lines of the triangle.
Dim aoLines(2) As SketchLine
Set aoLines(0) = oSketch.SketchLines.AddByTwoPoints( _
oTG.CreatePoint2d0,0),
oTG.CreatePoint2d(5, 0))
Set aoLines(1) = oSketch.SketchLines.AddByTwoPoints( _
aoLines(0).EndSketchPoint,
oTG.CreatePoint2d(2.5, 4))
Set aoLines(2) = oSketch.SketchLines.AddByTwoPoints( _
aoLines(1).EndSketchPoint,
aoLines(0).StartSketchPoint)
' Add a horizontal constraint to the bottom line.
Call oSketch.GeometricConstraints.AddHorizontal(aoLines(0))
' End the transaction.
oTrans.End
End Sub
There are four extra lines from the previous program that are highlighted in red. After running this program and looking in the Edit menu you'll see that you can undo "Create Triangle", as shown in the picture below.
For many programs these four extra lines are all that you'll need to implement undo behavior. The StartTransaction method creates and starts your transaction. The first argument is a document and can be any document, it doesn't matter which one. The second argument is the text that will appear in the Edit menu and can be whatever string you want to describe the group of steps being wrapped by this transaction. The API calls after the StartTransaction and before the call of the End method will be nested within your transaction.
Handling Errors
The code above does demonstrate the minimum to wrap multiple actions into a single undo. The only problem with it is if something happens in the program so that the Transaction.End method is never called. For example, if an error is encountered when creating the lines. In this case your transaction is started, some other transactions are created as a result of the API calls and are nested within your transaction but then it terminates if an error occurs, which leaves Inventor in a bit of a weird state. To make sure this doesn't happen it's best to correctly handle any errors that might occur. The modified example code below illustrates this.
' Enable error handling.
On Error GoTo ErrorFound
' Check to see that a sketch is active.
If Not TypeOf ThisApplication.ActiveEditObject Is sketch Then
MsgBox "A sketch must be active."
Exit Sub
End If
' Get a reference to the sketch.
Dim oSketch As sketch
Set oSketch = ThisApplication.ActiveEditObject
Dim oTG As TransientGeometry
Set oTG = ThisApplication.TransientGeometry
' Create a transaction.
Dim oTrans As Transaction
Set oTrans = oTransMgr.StartTransaction( _
ThisApplication.ActiveDocument, "Create Triangle")
' Draw the three lines of the triangle.
Dim aoLines(2) As SketchLine
Set aoLines(0) = oSketch.SketchLines.AddByTwoPoints( _
oTG.CreatePoint2d(0, 0),
oTG.CreatePoint2d(5, 0))
Set aoLines(1) = oSketch.SketchLines.AddByTwoPoints( _
aoLines(0).EndSketchPoint,
oTG.CreatePoint2d(2.5, 4))
Set aoLines(2) = oSketch.SketchLines.AddByTwoPoints( _
aoLines(1).EndSketchPoint,
aoLines(0).StartSketchPoint)
' Add a horizontal constraint to the bottom line.
Call oSketch.GeometricConstraints.AddHorizontal(aoLines(0))
' End the transaction.
oTrans.End
' No errors were found so exit before the error handling code.
Exit Sub
' Handle any errors
ErrorFound:
MsgBox "Unexpected error encountered."
' Check to see if a transaction was started.
If Not oTrans Is Nothing Then
' Abort the transaction.
oTrans.Abort
End If
End Sub
The new code is highlighted. At the beginning of the macro the On Error statement is used to turn on error handling. In this case it's set up to jump to the ErrorFound: line if any error should occur. In the ErrorFound: section it checks to see if a transaction object was created and if it was it calls the Transaction.Abort method. This results in undoing every action since your transaction was created. Instead of the Abort method the End method could have been called which will result in creating everything that was created up to the point of the error occurring. The choice is yours whether to use Abort or End. The reason this section checks to see if a transaction was created before call Abort is because an error could have occurred before the transaction was created and calling Abort or End would fail since your transaction object won't exist.
Transaction Issues to be Aware Of
There are a couple of things to be aware of when using transactions through the API. First, let's look at the behavior of Inventor relative to transactions and undo. Try this:
- Create a new part document.
- Create another new part document.
- In one of the part documents (part 1), draw some lines in a sketch.
- Activate the other part document (part 2) and draw two lines.
- Activate part 1 and draw one line.
- Do an undo and the line will go away.
- Do another undo. One of the lines in part 2 will go away.
- Close part 1.
- Do another undo.
There are two things to notice from the above steps. First is that there is a single undo list for all of Inventor, not for each document. The fact that you performed operations in different documents doesn't matter, all actions go onto a single undo list and are undone in the order they were performed. The second issue is that in step 9 you are unable to do an undo because the undo command is unavailable. This is because when a document is closed the undo stack is cleared. This seems like a fairly big limitation but doesn't seem to matter in the real world since I've never found anyone that knew of this behavior before I showed it to them.
Because the undo stack is cleared when a document is closed you can't wrap the opening and closing of documents in a transaction. That's probably the single biggest problem people have when using transactions. There is one exception to this rule. If the opening and closing of a document is done within your transaction, then it's ok. For example, you could perform the following steps:
- Start a transaction.
- Open a part.
- Change a parameter.
- Update the part.
- Save a copy to a new file.
- Close the part.
- Insert the new part into the assembly.
- End the transaction.
Even though a document was closed, it didn't clear the undo stack since the opening and closing of the document was within your transaction. If you perform an undo after the previous steps, the part will be removed from the assembly. However, the file on disk will remain.
Other Functionality
The TransactionManager supports a lot more functionality than I’ve described here. However, I think the samples above demonstrate all that’s needed by 99.9% of the programs written. There are descriptions of the additional functionality in the online help, if you’re interested.
You might also see references to something called a change processor. This is a different mechanism for grouping multiple operations within a single undo but it’s much more complicated to use and cannot be used from within VBA. I wouldn’t recommend it for most programs. In fact I would only recommend it for larger application type of add-ins (like Tube & Pipe) that want to automate testing their custom commands. The change processor provides important functionality to support that.