000

COM API Update to Entity And Transaction

.
When moving from AutoCAD VBA (COM API) programming to AutoCAD ObjectARX .NET API programming, one of the most "dramatic" changes an AutoCAD programmer might find is the concept of using Transaction. That is, the code that updates anything in drawing database (entities, table records...) must be wrapped in a transaction, and the transaction must be explicitly committed in order for the update/change to be materialized.

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;

[assemblyCommandClass(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.


Blog Archive