Xdata erased on Undo

Xdata erased on Undo

SRSDS
Advisor Advisor
1,588 Views
6 Replies
Message 1 of 7

Xdata erased on Undo

SRSDS
Advisor
Advisor

I’m desperately hoping there’s a solution to this.

If I modify an objects xdata during an object erased event, then hit undo, the xdata no longer exists.

 

Below is some code that creates a circle with xdata on document load.

To test it you need to draw a line or something before netloading so that there is something to erase.

Delete the line, hit undo, and the circle's xdata is gone.

 

The xdata coding has been working fine in many other event CommandWillStart and CommandEnded events.

 e.GlobalCommandName = "ERASE" seems to be an issue. 

Imports Autodesk.AutoCAD.Runtime
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.Geometry
Imports Autodesk.AutoCAD.EditorInput
<Assembly: ExtensionApplication(GetType(MyProject))>
Public Class MyProject
    Implements IExtensionApplication
    Private DocMan As DocumentCollection
    Dim MyInteger As Integer = 100, CircleObjID As ObjectId
    Public Sub Initialize() Implements IExtensionApplication.Initialize
        DocMan = Application.DocumentManager
        For Each doc As Document In DocMan
            AddHandler doc.CommandWillStart, AddressOf callback_CommandWillStart
        Next
        'Add Circle with xdata
        Dim db As Database = Application.DocumentManager.MdiActiveDocument.Database
        Using docLock As DocumentLock = Application.DocumentManager.MdiActiveDocument.LockDocument()
            Using trans As Transaction = Application.DocumentManager.MdiActiveDocument.TransactionManager.StartTransaction
                Dim bt As BlockTable = trans.GetObject(db.BlockTableId, OpenMode.ForRead)
                Dim btr As BlockTableRecord = trans.GetObject(bt(BlockTableRecord.ModelSpace), OpenMode.ForWrite)
                Using acCirc As Circle = New Circle()
                    acCirc.Center = New Point3d(0, 0, 0)
                    acCirc.Radius = 10000
                    btr.AppendEntity(acCirc)
                    trans.AddNewlyCreatedDBObject(acCirc, True)
                    CircleObjID = acCirc.ObjectId
                    WriteXdata(CircleObjID, trans)
                End Using
                trans.Commit()
            End Using
        End Using
    End Sub
    Private Sub callback_CommandWillStart(ByVal sender As Object, ByVal e As CommandEventArgs)
        Dim ed As Editor = Application.DocumentManager.MdiActiveDocument.Editor
        If e.GlobalCommandName = "ERASE" Then
            Using trans As Transaction = Application.DocumentManager.MdiActiveDocument.TransactionManager.StartOpenCloseTransaction
                ReadXData(CircleObjID, trans)
                WriteXdata(CircleObjID, trans)
                trans.Commit()
            End Using
        End If
    End Sub
    Sub WriteXdata(ByVal ObjID As ObjectId, ByRef trans As Transaction)
        Using data As ResultBuffer = New ResultBuffer(New TypedValue(DxfCode.ExtendedDataRegAppName, "MyApp"),
                                                      New TypedValue(DxfCode.ExtendedDataInteger16, Convert.ToInt16(MyInteger)))
            AddXData(ObjID, data, trans)
        End Using
    End Sub
    Sub ReadXData(ByVal ObjID As ObjectId, ByRef trans As Transaction)
        Dim Ent As Entity = trans.GetObject(ObjID, OpenMode.ForRead)
        Dim myXdata As Array = Ent.XData.AsArray
        MyInteger = myXdata.GetValue(1).value
    End Sub
    Sub AddXData(ByVal EntityID As ObjectId, ByVal BufferIn As ResultBuffer, ByRef trans As Transaction)
        AddXDataApp(BufferIn.AsArray(0).Value, trans)
        Dim selEnt As Entity = trans.GetObject(EntityID, OpenMode.ForWrite)
        selEnt.XData = BufferIn
    End Sub
    Sub AddXDataApp(ByVal AppName As String, ByRef trans As Transaction)
        Dim doc As Document = Application.DocumentManager.MdiActiveDocument
        Dim myAppTable As RegAppTable = trans.GetObject(doc.Database.RegAppTableId, OpenMode.ForWrite)
        If myAppTable.Has(AppName) = False Then
            Dim myAppTableRecord As New RegAppTableRecord
            myAppTableRecord.Name = AppName
            myAppTable.Add(myAppTableRecord)
            trans.AddNewlyCreatedDBObject(myAppTableRecord, True)
        End If
    End Sub
    Public Sub Terminate() Implements Autodesk.AutoCAD.Runtime.IExtensionApplication.Terminate
    End Sub
End Class
0 Likes
Accepted solutions (1)
1,589 Views
6 Replies
Replies (6)
Message 2 of 7

hgasty1001
Advisor
Advisor
Accepted solution

Hi,

I'm not sure about the use case, but this link may help: clear-the-undo-stack-programmatically-to-prevent-undo-ing-of-a-command

 

Gasty

 

0 Likes
Message 3 of 7

SRSDS
Advisor
Advisor

Not the perfect solution it's done the trick.

I can probably also write the xdata on the start of the next CommandWillStart

or DocumentToBeDeactivated if the document gets changed.

Thankyou.

Odd that it doesn't work as I'd expect.

 

 

0 Likes
Message 4 of 7

norman.yuan
Mentor
Mentor

Although you flagged your question as solved, the accepted solution is just a workaround - around the unsolved mystery of why XData "disappear".

 

To be honest, I was suspicious from beginning when seeing your post and thought it is no way the XData attached to an entity could disappear because of "undoing" as you described; I also spotted potential bugs in your code. Finally I had a bit time to try some code to see if I can reproduce the issue as you described. The code is basically the same as yours (corrected your bug, which I'll pointed out below), and the XData is not affected by first erasing other entity and then undoing the erased entity back.

 

Firstly the bugs in your code.

1.  In your CallBack_CommandWillStart() handler, you use MdiActiveDocument to start the Transaction, which could be potentially problematic, because the MdiActiveDocument could be different from the one where the DLL is Initialized and hence the circle is drawn there. Of course in your test, you did not change the MdiActiveDocument. In my code, I changed it to open the transaction with the Database associated to the circleId:

using (var tran=_circleId.Database.TransactionManager.StartTransaction())

{

    ... ...

}

 

2. In your ReadXData() method, you get embedded XData directly from Entity.XData, which is wrong. You should call

Entity.GetXDataForApplication(string appName) to get the XData as ResultBuffer ONLY SPECIFIC to given registered application name.

 

OK, here is my code (in C#). I did exactly as you described:

1. Open AutoCAD;

2. Draw a line (so I can erase it after the DLL is netloaded;

3. NetLoad the DLL (a circle is drawn at 0,0,0 after the DLL is loaded

4. Issue command Erase to erase the existing Line

5. Issue command UNDO to get back to the eraded Line

6. Examine the XData attached to the circle drawn when the dll is loaded with Express Tool->List XData. The XData is there.

7. In my code, I also handle CommandEnded event to test if the XData is still there if the command is "UNDO", which proves the XData is not lost because of undoing.

 

using System;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;

[assembly: CommandClass(typeof(XDataInUndo.MyCommand))]
[assembly: ExtensionApplication(typeof(XDataInUndo.MyCommand))]

namespace XDataInUndo
{
    public class MyCommand : IExtensionApplication
    {
        private const string XDATA_APP = "MyXDataApp";
        private int _myXDataValue = 100;
        private ObjectId _circleId = ObjectId.Null;

        #region IExtenstionApplication implementing

        public void Initialize()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;

            try
            {
                var docMan = Application.DocumentManager;
                foreach (Document doc in docMan)
                {
                    doc.CommandWillStart += Doc_CommandWillStart;
                    doc.CommandEnded += Doc_CommandEnded;
                }

                var appContext = CadApp.DocumentManager.IsApplicationContext;

                AddEntityWithXData(dwg);
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage("\nIitia;lizing error: {0}\n.", ex.Message);
            }
        }

        private void Doc_CommandEnded(object sender, CommandEventArgs e)
        {
            if (e.GlobalCommandName.ToUpper().Contains("UNDO"))
            {
                using (var tran = _circleId.Database.TransactionManager.StartTransaction())
                {
                    var ent = (Entity)tran.GetObject(_circleId, OpenMode.ForWrite);
                    ReadXData(ent);
                    tran.Commit();
                }

                CadApp.ShowAlertDialog($"After \"UNDO\", the XData embedded with the circle is: {_myXDataValue}.");
            }
        }

        private void Doc_CommandWillStart(object sender, CommandEventArgs e)
        {
            if (e.GlobalCommandName.ToUpper().Contains("ERASE"))
            {
                using (var tran = _circleId.Database.TransactionManager.StartTransaction())
                {
                    var ent = (Entity)tran.GetObject(_circleId, OpenMode.ForWrite);
                    ReadXData(ent);
                    WriteXData(ent, _circleId.Database, tran);

                    tran.Commit();
                }
            }
        }

        private void AddEntityWithXData(Document dwg)
        {
            var db = Application.DocumentManager.MdiActiveDocument.Database;
            using (var lck = dwg.LockDocument())
            {
                using (var tran = dwg.TransactionManager.StartTransaction())
                {
                    var bt = (BlockTable)tran.GetObject(db.BlockTableId, OpenMode.ForRead);
                    var btr = (BlockTableRecord)tran.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);

                    var acCircle = new Circle();
                    acCircle.Center = new Point3d(0, 0, 0);
                    acCircle.Radius = 100;
                    _circleId = btr.AppendEntity(acCircle);
                    tran.AddNewlyCreatedDBObject(acCircle, true);
                    WriteXData(acCircle, db, tran);

                    tran.Commit();
                }
            }
            
        }

        private void WriteXData(Entity ent, Database db, Transaction tran)
        {
            EnsureXDataApp(db, tran);
            using (var buffer = new ResultBuffer(
                new TypedValue[]
                {
                    new TypedValue((int)DxfCode.ExtendedDataRegAppName, XDATA_APP),
                    new TypedValue((int)DxfCode.ExtendedDataInteger32, _myXDataValue)
                }))
            {
                if (!ent.IsWriteEnabled) ent.UpgradeOpen();
                ent.XData = buffer;
            }
        }

        private void ReadXData(Entity ent)
        {
            var buffer = ent.GetXDataForApplication(XDATA_APP);
            if (buffer!=null)
            {
                var vals = buffer.AsArray();
                _myXDataValue = Convert.ToInt32(vals[1].Value);
            }
        }

        private void EnsureXDataApp(Database db, Transaction tran)
        {
            var appTable = (RegAppTable)tran.GetObject(db.RegAppTableId, OpenMode.ForRead);
            if (!appTable.Has(XDATA_APP))
            {
                var record = new RegAppTableRecord();
                record.Name = XDATA_APP;

                appTable.UpgradeOpen();
                appTable.Add(record);
                tran.AddNewlyCreatedDBObject(record, true);
            }
        }

        public void Terminate()
        {

        }

        #endregion
    }
}

So, I do not believe UNDO would cause XData loss as you described.

 

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes
Message 5 of 7

ActivistInvestor
Mentor
Mentor
To avoid problems with Undo/Redo, Code that executes from event handlers should not use a regular transaction. They should use an OpenCloseTransaction.
Message 6 of 7

ActivistInvestor
Mentor
Mentor
@norman.yuan wrote:

 

 

Firstly the bugs in your code.

1.  In your CallBack_CommandWillStart() handler, you use MdiActiveDocument to start the Transaction, which could be potentially problematic, because the MdiActiveDocument could be different from the one where the DLL is Initialized and hence the circle is drawn there. Of course in your test, you did not change the MdiActiveDocument. In my code, I changed it to open the transaction with the Database associated to the circleId:

using (var tran=_circleId.Database.TransactionManager.StartTransaction())

{

    ... ...

}

 

 


 

Technically, that is not a bug because the OP is using an OpenCloseTransaction as he should be using from an event handler, rather than a regular Transaction.

 

An OpenCloseTransaction has no affiliation with a database, and you can start one by just using 'new OpenCloseTransaction()', which is exactly what the TransactionManager's  StartOpenCloseTransaction() method does:

 

public virtual OpenCloseTransaction StartOpenCloseTransaction()
{
    return new OpenCloseTransaction();
}

So, it doesn't really matter what document/database the OP uses to start an OpenCloseTransaction.

 

As far as my previous comment above, when doing these sorts of things from event handlers, one must also test to ensure that it does not disable REDO.

 

 

 

 

0 Likes
Message 7 of 7

SRSDS
Advisor
Advisor

Norman,

I've just revisited this post to try to remember what the problem was and write an explanation of the problem in my code and why used the workaround of disabling the undo.

And I've just seen your reply.

I wonder if I'm not getting notifications.

Thank you again. I'll try to read your reply more fully tomorrow and implementing it.

0 Likes