Author Topic: Implementing undo for nested transactions  (Read 4696 times)

0 Members and 1 Guest are viewing this topic.

Chumplybum

  • Newt
  • Posts: 97
Implementing undo for nested transactions
« on: January 10, 2013, 11:52:53 PM »
Hi all,

I'm trying to implement an 'undo' for one of my routines and from searching the net i've very little information on this. One link (http://docs.autodesk.com/ACD/2010/ENU/AutoCAD%20.NET%20Developer's%20Guide/index.html?url=WS73099cc142f48755f2fc9df120970276f7a3e.htm,topicNumber=d0e12382) shows how to use nested transaction which I think i need to do... ie. the inner transaction adds the entities to the current space and the top transaction allows the inner transactions to be undone one at a time. The code below is my first attempt which doesn't work, I'm pretty sure its the way i'm trying to implement it, but cannot work out a way to make it work.

Code: [Select]

Imports AAAS = Autodesk.AutoCAD.ApplicationServices
Imports AADS = Autodesk.AutoCAD.DatabaseServices
Imports AAEI = Autodesk.AutoCAD.EditorInput


Public Class Plugin

    Public Sub Execute()

        Dim doc As AAAS.Document = AAAS.Application.DocumentManager.MdiActiveDocument
        Dim db As AADS.Database = doc.Database
        Dim ed As AAEI.Editor = doc.Editor

        Using topTransaction As AADS.Transaction = db.TransactionManager.StartTransaction

            Dim btr As AADS.BlockTableRecord = topTransaction.GetObject(db.CurrentSpaceId, AADS.OpenMode.ForWrite)

            Dim pickoptions As AAEI.PromptPointOptions = New AAEI.PromptPointOptions(String.Format("Specify point", Environment.NewLine))
            pickoptions.AllowNone = True
            pickoptions.Keywords.Add("Undo")
            Dim pickResult As AAEI.PromptPointResult

            Do
                pickResult = ed.GetPoint(pickoptions)

                Select Case pickResult.Status
                    Case Autodesk.AutoCAD.EditorInput.PromptStatus.Keyword
                        Select Case pickResult.StringResult
                            Case "Undo"
                                topTransaction.TransactionManager.TopTransaction.Abort()

                        End Select

                    Case Autodesk.AutoCAD.EditorInput.PromptStatus.OK
                        Using innerTransaction As AADS.Transaction = db.TransactionManager.StartTransaction
                            Dim NewCircle As New AADS.Circle(pickResult.Value, Autodesk.AutoCAD.Geometry.Vector3d.ZAxis, 1)
                            NewCircle.SetDatabaseDefaults()

                            btr.AppendEntity(NewCircle)

                            innerTransaction.AddNewlyCreatedDBObject(NewCircle, True)

                            innerTransaction.TransactionManager.QueueForGraphicsFlush()
                            innerTransaction.Commit()

                        End Using

                    Case Else

                End Select

            Loop While pickResult.Status = Autodesk.AutoCAD.EditorInput.PromptStatus.OK OrElse pickResult.Status = Autodesk.AutoCAD.EditorInput.PromptStatus.Keyword

            topTransaction.Commit()
        End Using

    End Sub

End Class



Has anyone implemented an undo using transactions as opposed to issuing the undo command, or could point me in the direction of some examples?


Thanks in advance, Mark

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: Implementing undo for nested transactions
« Reply #1 on: January 11, 2013, 05:17:55 PM »
this thread may be of use to you:
http://www.theswamp.org/index.php?topic=9341.0

Chumplybum

  • Newt
  • Posts: 97
Re: Implementing undo for nested transactions
« Reply #2 on: January 11, 2013, 07:01:17 PM »
Thanks TT

I've removed the outer transaction, but then had acad crash when trying to use the 'undo' keyword as there was no active transactions to abort (each of the inner transactions were committed and disposed of).
You mentioned nested undo groups, however, i can't find any reference to these in the object browser... there is a few functions to do with setting undo marks (autodesk.autocad.internal.utils) but i'm unsure how to use these inside the command.

I have some new code (see below) which does exactly what i need and all appears to work great (including the undo) until autocad thinks about what its just done and realizes that thats not quiet right, then crashes.

Code: [Select]

Imports AAAS = Autodesk.AutoCAD.ApplicationServices
Imports AADS = Autodesk.AutoCAD.DatabaseServices
Imports AAEI = Autodesk.AutoCAD.EditorInput
Imports AAR = Autodesk.AutoCAD.Runtime


Public Class Plugin

    <AAR.CommandMethod("xxx", AAR.CommandFlags.Modal)> _
    Public Shared Sub UndoStuff()

        Try
            'Using plugin As New Plugin
            Dim plugin As New Plugin
            plugin.Execute()
            'End Using
        Catch ex As Exception
            AAAS.Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(String.Format("{0}{1}", Environment.NewLine, ex.Message))
        End Try

    End Sub


    Public Sub Execute()

        Dim doc As AAAS.Document = AAAS.Application.DocumentManager.MdiActiveDocument
        Dim db As AADS.Database = doc.Database
        Dim ed As AAEI.Editor = doc.Editor

        Using topTransaction As AADS.Transaction = db.TransactionManager.StartTransaction

            Dim btr As AADS.BlockTableRecord = topTransaction.GetObject(db.CurrentSpaceId, AADS.OpenMode.ForWrite)

            Dim pickoptions As AAEI.PromptPointOptions = New AAEI.PromptPointOptions(String.Format("Specify point", Environment.NewLine))
            pickoptions.AllowNone = True
            Dim pickResult As AAEI.PromptPointResult

            Dim UndoAdded As Boolean = False

            Do
                If UndoAdded = False AndAlso topTransaction.TransactionManager.NumberOfActiveTransactions > 1 Then
                    pickoptions.Keywords.Add("Undo")
                    UndoAdded = True
                End If


                pickResult = ed.GetPoint(pickoptions)

                Select Case pickResult.Status
                    Case Autodesk.AutoCAD.EditorInput.PromptStatus.Keyword
                        Select Case pickResult.StringResult
                            Case "Undo"
                                topTransaction.TransactionManager.TopTransaction.Abort()

                        End Select

                    Case Autodesk.AutoCAD.EditorInput.PromptStatus.OK
                        Dim innerTransaction As AADS.Transaction = db.TransactionManager.StartTransaction
                        Dim NewCircle As New AADS.Circle(pickResult.Value, Autodesk.AutoCAD.Geometry.Vector3d.ZAxis, 1)
                        NewCircle.SetDatabaseDefaults()

                        btr.AppendEntity(NewCircle)

                        innerTransaction.AddNewlyCreatedDBObject(NewCircle, True)

                        innerTransaction.TransactionManager.QueueForGraphicsFlush()
                        'innerTransaction.Commit()

                        'End Using

                    Case Else

                End Select

            Loop While pickResult.Status = Autodesk.AutoCAD.EditorInput.PromptStatus.OK OrElse pickResult.Status = Autodesk.AutoCAD.EditorInput.PromptStatus.Keyword

            'commit inner transactions, keeping in mind that the very first transaction is the top / outer transaction
            For counter As Integer = topTransaction.TransactionManager.NumberOfActiveTransactions - 1 To 1 Step -1
                topTransaction.TransactionManager.TopTransaction.Commit()
                topTransaction.TransactionManager.TopTransaction.Dispose()
            Next

            topTransaction.Commit()

        End Using

    End Sub

End Class



thanks again for any help

cheers, Mark

Chumplybum

  • Newt
  • Posts: 97
Re: Implementing undo for nested transactions
« Reply #3 on: January 11, 2013, 07:06:32 PM »
@Will

After reading the thread, it looks like this is uses the undomarks to determine certain points that the undo command will revert back to and not points that are accessible inside the routine without using the 'ed.SendStringToExecute' which i'm trying to avoid.... is this right?

Thanks, Mark

TheMaster

  • Guest
Re: Implementing undo for nested transactions
« Reply #4 on: January 14, 2013, 01:27:57 AM »
I originally thought you wanted to undo individual steps that you take within your command, after it completed, but that doesn't look like what your code is trying to do.  Unfortunately, the design of the transaction mechanism didn't include a way to undo a previously-committed transaction, nested or otherwise. IMO, that's not a good design.

The easiest way to solve that problem is to just implement the undo yourself using Erase(). Since changes don't get comitted to the database until the outer-most transaction is comitted, there's no real overhead or other problem with just erasing entities as a means of implementing undo-able steps.

Code - C#: [Select]
  1.  
  2. namespace Namespace1
  3. {
  4.    public static class Commands
  5.    {
  6.  
  7.       /// Example: Sub-command undo
  8.       ///
  9.       /// This is the simplest way to implement
  10.       /// individually-undoable steps within a
  11.       /// command that repetitively prompts for
  12.       /// input and creates geometry immediately
  13.       /// after each input request.
  14.  
  15.       [CommandMethod( "SUBUNDOEXAMPLE" )]
  16.       public static void UndoGroupExample()
  17.       {
  18.          Document doc = Application.DocumentManager.MdiActiveDocument;
  19.          Database db = doc.Database;
  20.          ObjectId ms = SymbolUtilityServices.GetBlockModelSpaceId( db );
  21.          PromptPointOptions ppo = new PromptPointOptions( "\nPick center point: " );
  22.          ppo.AllowNone = true;     // allow exit via ENTER
  23.          ppo.Keywords.Add( "Undo" );
  24.          Keyword undoKeyword = ppo.Keywords[0];
  25.          var transmgr = doc.TransactionManager;
  26.          Stack<Entity> circles = new Stack<Entity>();
  27.          using( Transaction outer = transmgr.StartTransaction() )
  28.          {
  29.             transmgr.EnableGraphicsFlush( true );
  30.             while( true )
  31.             {
  32.                undoKeyword.Enabled = circles.Count > 0;
  33.                PromptPointResult ppr = doc.Editor.GetPoint( ppo );
  34.                switch( ppr.Status )
  35.                {
  36.                   case PromptStatus.Cancel:  // ESC exit roll back everything
  37.                      return;
  38.                   case PromptStatus.None:    // ENTER exit and commit changes
  39.                      outer.Commit();
  40.                      return;
  41.                   case PromptStatus.Keyword: // Undo the last circle
  42.                      if( circles.Count > 0 )
  43.                      {
  44.                         UpdateDisplay( () => circles.Pop().Erase() ); // see below
  45.                      }
  46.                      else
  47.                         doc.Editor.WriteMessage( "\nNothing to undo" );
  48.                      continue;
  49.                }
  50.                if( ppr.Status != PromptStatus.OK )
  51.                   throw new InvalidOperationException( "Unexpected result" );
  52.                using( Transaction tr = transmgr.StartTransaction() )
  53.                {
  54.                   BlockTableRecord model = (BlockTableRecord)
  55.                      tr.GetObject( ms, OpenMode.ForWrite ) as BlockTableRecord;
  56.                   Circle circle = new Circle( ppr.Value, Vector3d.ZAxis, 1.0 );
  57.                   try
  58.                   {
  59.                      circle.SetDatabaseDefaults( db );
  60.                      model.AppendEntity( circle );
  61.                   }
  62.                   catch
  63.                   {
  64.                      circle.Dispose();
  65.                      throw;
  66.                   }
  67.                   transmgr.AddNewlyCreatedDBObject( circle, true );
  68.                   circles.Push( circle );
  69.                   transmgr.QueueForGraphicsFlush();
  70.                   tr.Commit();
  71.                   doc.Editor.UpdateScreen();
  72.                }
  73.             }
  74.          }
  75.       }
  76.  
  77.       ///  Helper:
  78.       ///
  79.       ///  Executes caller-supplied delegate within a
  80.       ///  transaction and updates the display:
  81.  
  82.       public static void UpdateDisplay( Action action )
  83.       {
  84.          Document doc = Application.DocumentManager.MdiActiveDocument;
  85.          if( doc == null )
  86.             throw new InvalidOperationException( "no document" );
  87.          var transmgr = doc.TransactionManager;
  88.          transmgr.EnableGraphicsFlush( true );
  89.          using( Transaction tr = transmgr.StartTransaction() )
  90.          {
  91.             action();
  92.             transmgr.QueueForGraphicsFlush();
  93.             tr.Commit();
  94.             doc.Editor.UpdateScreen();
  95.          }
  96.       }
  97.    }
  98.  }
  99.  
  100.  
« Last Edit: January 14, 2013, 06:32:03 AM by TT »

WILL HATCH

  • Bull Frog
  • Posts: 450
Re: Implementing undo for nested transactions
« Reply #5 on: January 14, 2013, 02:41:30 PM »
@Will

After reading the thread, it looks like this is uses the undomarks to determine certain points that the undo command will revert back to and not points that are accessible inside the routine without using the 'ed.SendStringToExecute' which i'm trying to avoid.... is this right?

Thanks, Mark

Had a chance to look at the method outlined in the link I gave you and it appears that it doesn't operate as I had hoped.  When I call _.undo from the editor it does show that it is undoing a group, but I can't find a way to undo each transaction in the routine.  It seems that the proposed method from TT is going to be your best option


Code - C#: [Select]
  1. [CommandMethod("testundo2")]
  2.         public void testundo2()
  3.         {
  4.             Document doc = Application.DocumentManager.MdiActiveDocument;
  5.             Editor ed = doc.Editor;
  6.             Database db = doc.Database;
  7.             IAcadDocument curdoc = (IAcadDocument)Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.AcadDocument;
  8.             curdoc.StartUndoMark();
  9.             using (Transaction tr = doc.TransactionManager.StartTransaction())
  10.             {
  11.                 BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
  12.                 BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
  13.                 Circle c = new Circle();
  14.                 c.SetDatabaseDefaults();
  15.                 c.Center = new Autodesk.AutoCAD.Geometry.Point3d(0, 0, 0);
  16.                 c.Radius = 1;
  17.                 ms.AppendEntity(c);
  18.                 tr.AddNewlyCreatedDBObject(c, true);
  19.                 tr.Commit();
  20.             }
  21.             curdoc.EndUndoMark();
  22.             curdoc.StartUndoMark();
  23.             using (Transaction tr = doc.TransactionManager.StartTransaction())
  24.             {
  25.                 BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
  26.                 BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
  27.                 Circle c = new Circle();
  28.                 c.SetDatabaseDefaults();
  29.                 c.Center = new Autodesk.AutoCAD.Geometry.Point3d(1, 1, 0);
  30.                 c.Radius = 1;
  31.                 ms.AppendEntity(c);
  32.                 tr.AddNewlyCreatedDBObject(c, true);
  33.                 tr.Commit();
  34.             }
  35.             curdoc.EndUndoMark();
  36.             curdoc.StartUndoMark();
  37.             using (Transaction tr = doc.TransactionManager.StartTransaction())
  38.             {
  39.                 BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
  40.                 BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
  41.                 Circle c = new Circle();
  42.                 c.SetDatabaseDefaults();
  43.                 c.Center = new Autodesk.AutoCAD.Geometry.Point3d(2, 2, 0);
  44.                 c.Radius = 1;
  45.                 ms.AppendEntity(c);
  46.                 tr.AddNewlyCreatedDBObject(c, true);
  47.                 tr.Commit();
  48.             }
  49.             curdoc.EndUndoMark();
  50.         }
  51.  

Chumplybum

  • Newt
  • Posts: 97
Re: Implementing undo for nested transactions
« Reply #6 on: January 15, 2013, 08:14:53 PM »
thank TT, that works great... i had no idea it would end up being that complex though

Thanks again, Mark