How to perform Undo Operation in a Single Ctrl + Z Action in AutoCAD

How to perform Undo Operation in a Single Ctrl + Z Action in AutoCAD

csharp_dev
Contributor Contributor
476 Views
4 Replies
Message 1 of 5

How to perform Undo Operation in a Single Ctrl + Z Action in AutoCAD

csharp_dev
Contributor
Contributor
When a user draws an entity, my plugin listens to that event and performs additional operations, such as creating text entities and storing related data using multiple transactions.

The problem is that during undo, the user has to press Ctrl + Z multiple times to fully undo everything related to that action.

Based on my requirements, I cannot perform all the additional operations in a single transaction.

Is there a way to perform the undo operation only for the user's action and exclude my plugin's transactions from the undo stack, so that the user can perform undo with a single Ctrl + Z action?
 
Any suggestions or best practices would be appreciated.
0 Likes
477 Views
4 Replies
Replies (4)
Message 2 of 5

ActivistInvestor
Mentor
Mentor

Use StartOpenCloseTransaction() instead of StartTransaction(). 

0 Likes
Message 3 of 5

csharp_dev
Contributor
Contributor

Thanks for the reply.

 

As I mentioned earlier, after the user creates an entity, my plugin performs some additional operations like creating other entities and storing data in named dictionaries. I’m unable to do all these operations within a single StartOpenCloseTransaction(), especially for large drawings where it causes noticeable delays.

 

Do you have any suggestions or alternative approaches to handle this more efficiently ?

0 Likes
Message 4 of 5

ActivistInvestor
Mentor
Mentor

You don't have to do everything within a single OpenCloseTransaction(). You can use multiple OpenCloseTransactions() as needed.

 

An OpenCloseTransaction does not create undo groups, so if the entire operation is still not undoable as a single group when using one or more OpenCloseTransactions, there is another problem. Since you show no code whatsoever, no one can tell you what that problem may be.

 

However, what I can tell you in general, is that if you are relying on API's like SendStringToExecute(), it's impossible to have things done by commands that are executed that way to be part of a single, logical undo group.

0 Likes
Message 5 of 5

ActivistInvestor
Mentor
Mentor

What I say above is not entirely true.  

 

There is a way to encapsulate your operations/changes into a single, logical undo group that includes the user-initiated operations that triggered your operations, but it is not trivial.  Below is a class that you can use to define logical undo groups without having to use commands (e.g., UNDO/Begin/End).

 

You must create an instance of the class before the user operation that initiates your operation starts, which is typically done in a CommandWillStart event handler.

 

After the user-initiated operation and your subsequent operations end, you dispose the instance. That should encapsulate the user-initiated operations and your operations that were triggered by the user operation into a single undo group.

 

/// AcMgdLib - https://github.com/ActivistInvestor/AcMgdLib
///
/// UndoGroup.cs
/// 
/// Activist Investor / Tony T
/// 
/// Distributed under the terms of the MIT license

using System;
using System.Runtime.InteropServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Runtime.NativeInterop;
using AcRx = Autodesk.AutoCAD.Runtime;

namespace AcMgdLib.DatabaseServices
{

   /// <summary>
   /// Encapsulation of a logical undo group. 
   /// 
   /// This class can be used to group multiple
   /// operations into a single undo group without
   /// the need to execute AutoCAD commands. The
   /// instance starts an undo group when it is
   /// created, and ends the undo group when it
   /// is disposed.
   /// </summary>

   public class UndoGroup : IDisposable
   {
      /// <summary>
      /// VERSION dependence:
      /// 
      /// This code uses P/Invoke to call native methods in
      /// acdbXX.dll, which has a version/release-dependent
      /// filename (e.g., acdb19.dll... acdb25.dll, etc).
      /// 
      /// Hence, you must replace the following string
      /// with the name of the file for the product or
      /// release you're using.
      /// 
      /// If you want to eliminate the version-dependent
      /// filename problem, you can use dynamic loading
      /// using classes from this library. 
      /// 
      /// See the following files in the Common folder:
      /// 
      ///   DllImport.cs
      ///   AcDbNativeMethods.cs
      ///   
      /// Eventually, this code will refactored to avoid
      /// the version-dependent filename issue, using the
      /// above classes.
      /// </summary>

      const string ACDB_DLL = "acdb25.dll";  // AutoCAD 2025/2026

      Database db;
      bool disposed = false;

      public UndoGroup(Database db)
      {
         if(db is null)
            throw new ArgumentNullException(nameof(db));
         this.db = db;
         Begin(db);
      }

      public void Dispose()
      {
         if(!disposed)
         {
            disposed = true;
            End(db);
         }
      }

      [DllImport(ACDB_DLL, CallingConvention = CallingConvention.ThisCall,
         EntryPoint = "?undoController@AcDbDatabase@@QEBAPEAVAcDbUndoController@@XZ")]
      static extern IntPtr GetUndoController(IntPtr db);

      [DllImport(ACDB_DLL, CallingConvention = CallingConvention.ThisCall,
         EntryPoint = "?beginGroup@AcDbImpUndoController@@UEAA?AW4ErrorStatus@Acad@@XZ")]
      static extern ErrorStatus beginGroup(IntPtr controller);

      [DllImport(ACDB_DLL, CallingConvention = CallingConvention.ThisCall,
         EntryPoint = "?endGroup@AcDbImpUndoController@@UEAA?AW4ErrorStatus@Acad@@XZ")]
      static extern ErrorStatus endGroup(IntPtr controller);

      static ErrorStatus Begin(Database db)
      {
         var controller = GetUndoController(db.UnmanagedObject);
         if(controller == IntPtr.Zero)
            throw new AcRx.Exception(AcRx.ErrorStatus.NullPtr);
         return beginGroup(controller);
      }

      static ErrorStatus End(Database db)
      {
         var controller = GetUndoController(db.UnmanagedObject);
         if(controller == IntPtr.Zero)
            throw new AcRx.Exception(AcRx.ErrorStatus.NullPtr);
         return endGroup(controller);
      }

   }
}

 

0 Likes