I believe I was not the only one who forgot to call Transaction.Commit() quite a few times during the early stage of moving from VBA to .NET programming until calling Commit() became basic instinct after writing a lot .NET code.
One good thing of moving from VBA to .NET programming is that one can still use COM API in .NET code, although it is debatable what is a good practice to max COM API and .NET API, which is not the topic of this article.
One thing I have been wondering occasionally all the years since I moved to use .NET APIs: what would happen if the COM API calls, which change stuff in drawing, are wrapped inside a .NET API's Transaction?
Naturally, my assumption is that the Transaction is not necessary, unless I want to have a chance to undo the changes made with COM API calls (that is, Transaction.Abort() can undo changes wrapped by the Transaction, be it the changes are made via .NET API, or COM API). To verify this assumption, I wrote following code to verify it:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Interop;
using Autodesk.AutoCAD.Interop.Common;
[assembly: CommandClass(typeof(ComApiInTransaction.MyCommands))]
namespace ComApiInTransaction
{
public class MyCommands
{
[CommandMethod("PureCom")]
public static void ComWithoutTransaction()
{
Document dwg = Application.DocumentManager.MdiActiveDocument;
Editor ed = dwg.Editor;
try
{
AcadDocument acadDoc = (AcadDocument)dwg.GetAcadDocument();
UpdateEntitiesWithCom(acadDoc);
}
catch (System.Exception ex)
{
ed.WriteMessage("\nCommand \"MyCmd\" failed:");
ed.WriteMessage("\n{0}\n{1}", ex.Message, ex.StackTrace);
}
finally
{
Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
}
}
[CommandMethod("TransCom")]
public static void ComWithTransaction()
{
Document dwg = Application.DocumentManager.MdiActiveDocument;
Editor ed = dwg.Editor;
try
{
AcadDocument acadDoc = (AcadDocument)dwg.GetAcadDocument();
using (var trans = dwg.TransactionManager.StartTransaction())
{
UpdateEntitiesWithCom(acadDoc);
dwg.Editor.UpdateScreen();
PromptKeywordOptions opt = new PromptKeywordOptions(
"\nSelect option:");
opt.AppendKeywordsToMessage = true;
opt.Keywords.Add("Commit");
opt.Keywords.Add("Abort");
opt.Keywords.Default = "Commit";
bool commit = false;
PromptResult res = ed.GetKeywords(opt);
if (res.Status==PromptStatus.OK)
{
commit = res.StringResult == "Commit";
}
if (commit)
trans.Commit();
else
trans.Abort();
}
}
catch (System.Exception ex)
{
ed.WriteMessage("\nCommand \"MyCmd\" failed:");
ed.WriteMessage("\n{0}\n{1}", ex.Message, ex.StackTrace);
}
finally
{
Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
}
}
[CommandMethod("NetChange")]
public static void NetChange()
{
Document dwg = Application.DocumentManager.MdiActiveDocument;
Editor ed = dwg.Editor;
try
{
using (var trans = dwg.TransactionManager.StartTransaction())
{
UpdateEntitiesWithNetApi(dwg, trans);
//This line of code makes sure the entity color changes
//in AutoCAD editor before the Transaction is commited
trans.TransactionManager.QueueForGraphicsFlush();
dwg.Editor.UpdateScreen();
PromptKeywordOptions opt = new PromptKeywordOptions(
"\nSelect option:");
opt.AppendKeywordsToMessage = true;
opt.Keywords.Add("Commit");
opt.Keywords.Add("Abort");
opt.Keywords.Default = "Commit";
bool commit = false;
PromptResult res = ed.GetKeywords(opt);
if (res.Status == PromptStatus.OK)
{
commit = res.StringResult == "Commit";
}
if (commit)
trans.Commit();
else
trans.Abort();
}
}
catch (System.Exception ex)
{
ed.WriteMessage("\nCommand \"MyCmd\" failed:");
ed.WriteMessage("\n{0}\n{1}", ex.Message, ex.StackTrace);
}
finally
{
Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
}
}
private static void UpdateEntitiesWithCom(AcadDocument dwg)
{
foreach (AcadEntity ent in dwg.ModelSpace)
{
int index=(int)ent.TrueColor.ColorIndex;
index++;
if (index<1 || index>7) index=1;
AcadAcCmColor color=new AcadAcCmColor();
color.ColorIndex=(AcColor)index;
ent.TrueColor = color;
ent.Update();
}
}
private static void UpdateEntitiesWithNetApi(
Document dwg, Transaction tran)
{
ObjectId modelId =
SymbolUtilityServices.GetBlockModelSpaceId(dwg.Database);
BlockTableRecord model = (BlockTableRecord)
tran.GetObject(modelId, OpenMode.ForRead);
foreach (ObjectId entId in model)
{
Entity ent = (Entity)tran.GetObject(entId, OpenMode.ForWrite);
short index = ent.Color.ColorIndex;
index++;
if (index < 1 || index > 7) index = 1;
Autodesk.AutoCAD.Colors.Color color =
Autodesk.AutoCAD.Colors.Color.FromColorIndex(
Autodesk.AutoCAD.Colors.ColorMethod.ByColor, index);
ent.Color = color;
}
}
}
}
The code uses COM API to change all entities' color in ModelSpace. The first Command "PureCom" simply goes ahead to change entities' color with COM API; while the second command "TransCom" wraps the changes made by COM API in a Transaction, and user gets chance to decide whether to commit the changes or abort.
There is also a third command that uses .NET API to to the same with entities in ModelSpace, as a comparison to the COM API counterpart.
See this video clip for the code in action.