Author Topic: ObjectAppended Event (Updating all added objects in CommandEnded)  (Read 5761 times)

0 Members and 1 Guest are viewing this topic.

Jeff H

  • Needs a day job
  • Posts: 6150
Does anyone have a clean way of adding a entry to the Extension Dictionary of all objects added to a drawing?

Before I get too deep in this I was seeing if you guys had any suggestions and ideas.

Problems I have found
--If you try to create a block using the UI the block will be empty------None of the entities are added.
--If you add a dimension it kills it and quits adding the dictionary to the newly added objects.


Added the second if statement because with a empty drawing when the first entity was added a 'RegAppTableRecord' object caused it to fail
and a 'DimStyleTableRecord' would cause it to fail in AddUserName() method when casting it to a DBobject.--(Now cast to a Entity)
Code: [Select]
        void Database_ObjectAppended(object sender, ObjectEventArgs e)
        {
            if (!cmdFlag)
                return;           
           
            if (e.DBObject.ObjectId.ObjectClass.IsDerivedFrom(RXClass.GetClass(typeof(Entity))))
                appendedIds.Add(e.DBObject.ObjectId);         
        }







Code: [Select]

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;



namespace HpadStudentCad
{
    public class Loader : IExtensionApplication
    {
        //// Collection to hold open documents
        static Dictionary<Document, DocMgr> docs;
       
        DocumentCollection documents;
       
        public void Initialize()
        {
            documents = Application.DocumentManager;

            docs = new Dictionary<Document,DocMgr>();

            ///// Add all open documents to collection and while adding
            ///// Create New DocMgr Instance
            foreach (Document doc in documents)
            {
                docs.Add(doc, new DocMgr(doc));
            }

            //// Add Handlers for Drawings
            documents.DocumentActivated += new DocumentCollectionEventHandler(documents_DocumentActivated);
            documents.DocumentToBeDestroyed += new DocumentCollectionEventHandler(documents_DocumentToBeDestroyed);

        }

        //// If a document becomes active and has not been added to collection then add it.
        void documents_DocumentActivated(object sender, DocumentCollectionEventArgs e)
        {
            if (!(docs.ContainsKey(e.Document)))
            {
                docs.Add(e.Document, new DocMgr(e.Document));
            }
        }

        //// If a document is closed then remove it from the collection.
        void documents_DocumentToBeDestroyed(object sender, DocumentCollectionEventArgs e)
        {
            if (docs.ContainsKey(e.Document))
            {
                docs[e.Document].DocDestroy();
                docs.Remove(e.Document);
            }
        }


        public void Terminate()
        {
            documents.DocumentActivated -= new DocumentCollectionEventHandler(documents_DocumentActivated);
            documents.DocumentToBeDestroyed -= new DocumentCollectionEventHandler(documents_DocumentToBeDestroyed);
        }

    }


    //////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////HELPER CLASS FOR DOCUMENTS////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////
    public class DocMgr
    {
        private Document doc;
        private Database db;


        private static string userName;
        private  List<ObjectId> appendedIds;

        private const string DICT_NAME = "HpadStudentDictionary";
        private const string USER_NAME_ENTRY = "HpadStudentUserName";

        private bool cmdFlag;

        static DocMgr()
        {
            userName = Environment.UserName;           
        }

        public DocMgr(Document document)
        {
            appendedIds = new List<ObjectId>();
            cmdFlag = false;

            doc = document;
            db = doc.Database;
             
            db.ObjectAppended += new ObjectEventHandler(Database_ObjectAppended);
            db.ObjectModified += new ObjectEventHandler(Database_ObjectModified);
           
            doc.CommandWillStart += new CommandEventHandler(doc_CommandWillStart);
            doc.CommandEnded += new CommandEventHandler(doc_CommandEnded);
        }

        void doc_CommandWillStart(object sender, CommandEventArgs e)
        {
            cmdFlag = true;
        }

        void doc_CommandEnded(object sender, CommandEventArgs e)
        {
            if (!cmdFlag) return;

            foreach (ObjectId id in appendedIds)
            {
                AddUserName(id);
            }
            cmdFlag = false;           
            appendedIds.Clear();

        }

        void Database_ObjectModified(object sender, ObjectEventArgs e)
        {
           
        }

        void Database_ObjectAppended(object sender, ObjectEventArgs e)
        {
            if (!cmdFlag)
                return;           
           
            if (e.DBObject.ObjectId.ObjectClass.IsDerivedFrom(RXClass.GetClass(typeof(Entity))))
                appendedIds.Add(e.DBObject.ObjectId);         
        }


        private void AddUserName(ObjectId id)
        {
            using (Transaction trx = db.TransactionManager.StartTransaction())
            {
                Entity ent = trx.GetObject(id, OpenMode.ForRead) as Entity;
                if (ent == null) return;
                try
                {

                    if (ent.ExtensionDictionary == ObjectId.Null)
                    {
                        ent.UpgradeOpen();
                        ent.CreateExtensionDictionary();
                    }

                    DBDictionary studentDic;
                    DBDictionary extDic = (DBDictionary)trx.GetObject(ent.ExtensionDictionary, OpenMode.ForRead);
                    if (!extDic.Contains(DICT_NAME))
                    {
                        extDic.UpgradeOpen();
                        studentDic = new DBDictionary();
                        Xrecord xr = new Xrecord();
                        xr.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, userName));

                        extDic.SetAt(DICT_NAME, studentDic);
                        studentDic.SetAt(USER_NAME_ENTRY, xr);
                    }

                }


                catch (System.Exception)
                {

                }

                trx.Commit();               
            }

        }



        public void DocDestroy()
        {
            doc.Database.ObjectAppended -= new ObjectEventHandler(Database_ObjectAppended);
            doc.Database.ObjectModified -= new ObjectEventHandler(Database_ObjectModified);
            doc.CommandEnded -= new CommandEventHandler(doc_CommandEnded);
            doc.CommandWillStart -= new CommandEventHandler(doc_CommandWillStart);
        }
   
    }


}

kaefer

  • Guest
Re: ObjectAppended Event (Updating all added objects in CommandEnded)
« Reply #1 on: May 30, 2011, 03:08:39 AM »
Problems I have found
--If you try to create a block using the UI the block will be empty------None of the entities are added.
--If you add a dimension it kills it and quits adding the dictionary to the newly added objects.

Can't confirm:
--The blocks aren't empty, but the UI radio button "Convert to block" isn't honoured
--Dimensions are ok

Observations:
I think you will need to take the CommandCancelled/CommandFailed events into account.
You should be able to run the complete list through the same transaction.

Code: [Select]
type CmdClass() =
    let doc = Application.DocumentManager.MdiActiveDocument
    let db = doc.Database
    let ed = doc.Editor

    let DICT_NAME = "HpadStudentDictionary"
    let USER_NAME_ENTRY = "HpadStudentUserName"

    let userName = System.Environment.UserName

    let mutable isRunning = false
   
    let appendedIds = new ResizeArray<ObjectId>()

    let addUserName objs =
        use tr = db.TransactionManager.StartTransaction()
        for oid in objs do
            match tr.GetObject(oid, OpenMode.ForRead) with
            | :? Entity as ent ->
                if ent.ExtensionDictionary = ObjectId.Null then
                    ent.UpgradeOpen()
                    ent.CreateExtensionDictionary()

                let extDic = tr.GetObject(ent.ExtensionDictionary, OpenMode.ForRead) :?> DBDictionary
                if not(extDic.Contains DICT_NAME) then
                    extDic.UpgradeOpen()
                    let studentDic = new DBDictionary()
                    let xr = new Xrecord()
                    xr.Data <- new ResultBuffer(new TypedValue(int DxfCode.Text, userName))
                    extDic.SetAt(DICT_NAME, studentDic) |> ignore
                    studentDic.SetAt(USER_NAME_ENTRY, xr) |> ignore
            | _ -> ()

        tr.Commit()
           
    let objectAppended =
        new ObjectEventHandler(
            fun _ e ->
                let oid = e.DBObject.ObjectId
                if  oid.ObjectClass.IsDerivedFrom(RXClass.GetClass(typeof<Entity>)) &&
                    not(appendedIds.Contains oid) then
                    appendedIds.Add oid )

    let commandEnded =
        new CommandEventHandler(
            fun _ e ->
                addUserName appendedIds
                appendedIds.Clear() )

    let commandCancelled =
        new CommandEventHandler(
            fun _ e ->
                appendedIds.Clear() )

    let commandFailed =
        new CommandEventHandler(
            fun _ e ->
                appendedIds.Clear() )

    [<CommandMethod "Foo1">]
    member __.Foo1() =
        if not isRunning then
            isRunning <- true
            db.ObjectAppended.AddHandler objectAppended
            doc.CommandEnded.AddHandler commandEnded
            doc.CommandCancelled.AddHandler commandCancelled
            doc.CommandFailed.AddHandler commandFailed

    [<CommandMethod "Foo0">]
    member __.Foo0() =
        if isRunning then
            isRunning <- false
            db.ObjectAppended.RemoveHandler objectAppended
            doc.CommandEnded.RemoveHandler commandEnded
            doc.CommandCancelled.RemoveHandler commandCancelled
            doc.CommandFailed.RemoveHandler commandFailed

kaefer

  • Guest
Re: ObjectAppended Event (Updating all added objects in CommandEnded)
« Reply #2 on: May 30, 2011, 08:26:47 AM »
Problems I have found
--If you try to create a block using the UI the block will be empty------None of the entities are added.
--If you add a dimension it kills it and quits adding the dictionary to the newly added objects.

Can't confirm:
--The blocks aren't empty, but the UI radio button "Convert to block" isn't honoured
--Dimensions are ok

Can confirm: Especially bad with -BLOCK, its command line version. Bombs with eWasOpenForWrite...

Jeff H

  • Needs a day job
  • Posts: 6150
Re: ObjectAppended Event (Updating all added objects in CommandEnded)
« Reply #3 on: May 30, 2011, 09:29:54 AM »
Got a little better

Stepping through adding a dimension which adds a anonymous block there are 24 objects for it add and I not quite sure what AutoCAD is doing but it is maybe doing something like building the dimension with objects for some reason then deleteing them. There a number of lines solids that were erased.

So checking for ObjectId.IsErased or I was using IsEffectivelyErased
Code: [Select]
        void doc_CommandEnded(object sender, CommandEventArgs e)
        {
            if (!cmdFlag) return;
            using (Transaction trx = db.TransactionManager.StartTransaction())
            {
                foreach (ObjectId id in appendedIds)
                {
                    if (id.IsEffectivelyErased) continue;     
                   
                    AddUserName(id, trx);
                }
                trx.Commit();
            }
            cmdFlag = false;           
            appendedIds.Clear();

        }



If you use the block command and build a block all that is returned are 2 objects AcdbBeginBlock and AcDbEndBlock and no entites are saved.
If you enter BE and build the block in the block editor then it saves it and works.


But seems to work using Xdata when created block the AcdbBeginBlock and AcDbEndBlock plus all the enties were returned

Changes in Red
Code: [Select]
  public DocMgr(Document document)
        {
            appendedIds = new List<ObjectId>();
            cmdFlag = false;

            doc = document;
            db = doc.Database;
         [color=red]   using (Transaction trx = db.TransactionManager.StartTransaction())
            {
                RegAppTable regTbl = (RegAppTable)trx.GetObject(db.RegAppTableId, OpenMode.ForWrite, false);
                RegAppTableRecord regTblRec = new RegAppTableRecord();
                regTblRec.Name = "HpadStudentUserName";
                regTbl.Add(regTblRec);
                trx.AddNewlyCreatedDBObject(regTblRec, true);

                trx.Commit();
            }[/color]
            db.ObjectAppended += new ObjectEventHandler(Database_ObjectAppended);
            db.ObjectModified += new ObjectEventHandler(Database_ObjectModified);
           
            doc.CommandWillStart += new CommandEventHandler(doc_CommandWillStart);
            doc.CommandEnded += new CommandEventHandler(doc_CommandEnded);
        }


Code: [Select]
        private void AddUserName(ObjectId id, Transaction trx)
        {
[color=green]            //using (Transaction trx = db.TransactionManager.StartTransaction())
            //{[/color]             
             [color=red]   try
                {
                    Entity ent = trx.GetObject(id, OpenMode.ForRead) as Entity;
                    if (ent == null) return;
                    ent.UpgradeOpen();
                    ResultBuffer rb;
                    rb = ent.XData;
                    if (rb == null)
                    {
                        ent.XData = new ResultBuffer(new TypedValue(1001, "HpadStudentUserName"), new TypedValue(1000, userName));

                    }[/color]

[color=green]                    //if (ent.ExtensionDictionary == ObjectId.Null)
                    //{
                    //    ent.UpgradeOpen();
                    //    ent.CreateExtensionDictionary();
                    //}

                    //DBDictionary studentDic;
                    //DBDictionary extDic = (DBDictionary)trx.GetObject(ent.ExtensionDictionary, OpenMode.ForRead);
                    //if (!extDic.Contains(DICT_NAME))
                    //{
                    //    extDic.UpgradeOpen();
                    //    studentDic = new DBDictionary();
                    //    Xrecord xr = new Xrecord();
                    //    xr.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, userName));

                    //    extDic.SetAt(DICT_NAME, studentDic);
                    //    studentDic.SetAt(USER_NAME_ENTRY, xr);
                    //}[/color]

                }


                catch (System.Exception)
                {

                }

                //trx.Commit();               
            //}

        }


pkohut

  • Bull Frog
  • Posts: 483
Re: ObjectAppended Event (Updating all added objects in CommandEnded)
« Reply #4 on: May 30, 2011, 10:41:17 AM »
You should do something in the empty catch block, like a log message or some dummy code you can set a breakpoint on when in debug mode.  As it is now, if an exception is thrown you won't know anything about it.
New tread (not retired) - public repo at https://github.com/pkohut

kaefer

  • Guest
Re: ObjectAppended Event (Updating all added objects in CommandEnded)
« Reply #5 on: May 31, 2011, 09:45:04 AM »
Got a little better

You weren't seriously ill, were you?

Quote
But seems to work using Xdata when created block the AcdbBeginBlock and AcDbEndBlock plus all the enties were returned

Funny things, those extension dictionaries. I would have thought that listening to the combination of Database.WblockNotice and Database.BeginSave might produce an alternative solution to your original approach, but no such luck.

Jeff H

  • Needs a day job
  • Posts: 6150
Re: ObjectAppended Event (Updating all added objects in CommandEnded)
« Reply #6 on: June 01, 2011, 08:57:12 PM »
Got a little better

You weren't seriously ill, were you?

Quote
But seems to work using Xdata when created block the AcdbBeginBlock and AcDbEndBlock plus all the enties were returned

Funny things, those extension dictionaries. I would have thought that listening to the combination of Database.WblockNotice and Database.BeginSave might produce an alternative solution to your original approach, but no such luck.
No wasn't ill but thanks.

Jeff H

  • Needs a day job
  • Posts: 6150
Re: ObjectAppended Event (Updating all added objects in CommandEnded)
« Reply #7 on: June 06, 2011, 07:29:33 PM »
Thanks to Philippe Leefsma at ADN,
Looks like this took care of the issues.

All handled in ObjectAppended

Code: [Select]
    public class DocMgr
    {
        private Document doc;
        private Database db;


        private static string userName;
     
        private List<ObjectId> appendedIds;

        private const string DICT_NAME = "HpadStudentDictionary";
        private const string USER_NAME_ENTRY = "HpadStudentUserName";

        private bool cmdFlag;

        static DocMgr()
        {
           
            userName = Environment.UserName;
           
        }

        public DocMgr(Document document)
        {

            doc = document;
            db = doc.Database;
            db.ObjectAppended += new ObjectEventHandler(Database_ObjectAppended);
                   
        }




        void Database_ObjectAppended(object sender, ObjectEventArgs e)
        {

            using (Transaction trx = db.TransactionManager.StartOpenCloseTransaction())
            {

                try
                {

                    DBObject dbObj = e.DBObject;



                    if (dbObj == null)

                        return;



                    if (dbObj is DBDictionary || dbObj is Xrecord)

                        return;



                    if (dbObj.ObjectId.IsEffectivelyErased)

                        return;



                    if (dbObj.ExtensionDictionary == ObjectId.Null)
                    {

                        dbObj.UpgradeOpen();

                        dbObj.CreateExtensionDictionary();

                    }



                    DBDictionary studentDic;

                    DBDictionary extDic = (DBDictionary)trx.GetObject(dbObj.ExtensionDictionary, OpenMode.ForRead);

                    if (!extDic.Contains(DICT_NAME))
                    {

                        extDic.UpgradeOpen();

                        studentDic = new DBDictionary();

                        Xrecord xr = new Xrecord();

                        xr.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, userName));



                        extDic.SetAt(DICT_NAME, studentDic);

                        studentDic.SetAt(USER_NAME_ENTRY, xr);



                        trx.AddNewlyCreatedDBObject(xr, true);

                        trx.AddNewlyCreatedDBObject(studentDic, true);



                    }



                    trx.Commit();



                }



                catch (System.Exception ex)
                {



                }

            }

        }


   

        public void DocDestroy()
        {
            doc.Database.ObjectAppended -= new ObjectEventHandler(Database_ObjectAppended);           
        }





    }




kaefer

  • Guest
Re: ObjectAppended Event (Updating all added objects in CommandEnded)
« Reply #8 on: June 13, 2011, 09:19:17 AM »
Thanks to Philippe Leefsma at ADN,
Looks like this took care of the issues.

I think the crucial differences are
1. that there were missing calls to Transaction.AddNewlyCreatedDBObject and
2. missing tests for DBDictionary and Xrecord to avoid unwanted recursion.

Quote
All handled in ObjectAppended

You aren't getting any eHadMultipleReaders exceptions when you open the DBObject directly in the ObjectAppended handler? That's why I think that it's still necessary to collect the ObjectIds and act on them when the command ends.

Retrieving nested DBDictionaries and Xrecords struck me as very similar, so I've been playing with generalizing it. It's pretty useless for timestamping only objects appended, but would allow for ObjectModified too.

Code: [Select]
type ObjectId with
    member oid.IsType objType =
        RXClass.GetClass objType |> oid.ObjectClass.IsDerivedFrom
   
type CmdClass() =
    let doc = Application.DocumentManager.MdiActiveDocument
    let db = doc.Database
    let ed = doc.Editor

    let DICT_NAME = "StudentDictionary"
    let USER_NAME_ENTRY = "StudentUserName"

    let userName = System.Environment.UserName

    let mutable isRunning = false
   
    let appendedIds = new ObjectIdCollection()

    let addUserName (objs: ObjectIdCollection) =
        use tr = db.TransactionManager.StartTransaction()

        let openContainer (dictName: string) createContainer (dbDic : DBDictionary) =
            if dbDic.Contains dictName then
                tr.GetObject(dbDic.GetAt dictName, OpenMode.ForWrite) :?> _
            else
                let newContainer = createContainer()
                dbDic.SetAt(dictName, newContainer) |> ignore
                tr.AddNewlyCreatedDBObject(newContainer, true)
                newContainer

        for oid in objs do
            if not oid.IsNull && not oid.IsEffectivelyErased then
                try
                    let dbObj = tr.GetObject(oid, OpenMode.ForRead)
                    if dbObj.ExtensionDictionary.IsNull then
                        dbObj.UpgradeOpen()
                        dbObj.CreateExtensionDictionary()
                    tr.GetObject(dbObj.ExtensionDictionary, OpenMode.ForWrite) :?> DBDictionary
                    |> openContainer DICT_NAME (fun () -> new DBDictionary())
                    |> openContainer USER_NAME_ENTRY (fun () -> new DBDictionary())
                    |> openContainer System.Environment.UserName (fun () -> new Xrecord())
                    |> fun xr ->
                        xr.Data <-
                            new ResultBuffer[|
                                new TypedValue(
                                    int DxfCode.Text,
                                    System.DateTime.Now.ToString "yyyy-MM-dd HH:mm:ssK") |]
                with
                    ex ->
                        ed.WriteMessage("\n{0}\n{1} ", ex.Message, ex.StackTrace)
        tr.Commit()
           
    let objectAppended =
        new ObjectEventHandler(
            fun _ e ->
                let oid = e.DBObject.ObjectId
                if not(appendedIds.Contains oid) &&
                   not(oid.IsType typeof<DBDictionary>) &&
                   not(oid.IsType typeof<Xrecord>) then appendedIds.Add oid |> ignore )

    let commandEnded =
        new CommandEventHandler(
            fun _ _ ->
                addUserName appendedIds
                appendedIds.Clear() )

    let commandCancelledOrFailed =
        new CommandEventHandler(
            fun _ _ ->
                appendedIds.Clear() )

    [<CommandMethod "Foo1">]
    member __.Foo1() =
        if not isRunning then
            isRunning <- true
            db.ObjectAppended.AddHandler objectAppended
            doc.CommandEnded.AddHandler commandEnded
            doc.CommandCancelled.AddHandler commandCancelledOrFailed
            doc.CommandFailed.AddHandler commandCancelledOrFailed

    [<CommandMethod "Foo0">]
    member __.Foo0() =
        if isRunning then
            isRunning <- false
            db.ObjectAppended.RemoveHandler objectAppended
            doc.CommandEnded.RemoveHandler commandEnded
            doc.CommandCancelled.RemoveHandler commandCancelledOrFailed
            doc.CommandFailed.RemoveHandler commandCancelledOrFailed

Jeff H

  • Needs a day job
  • Posts: 6150
Re: ObjectAppended Event (Updating all added objects in CommandEnded)
« Reply #9 on: June 13, 2011, 09:50:36 AM »
I think the crucial differences are
1. that there were missing calls to Transaction.AddNewlyCreatedDBObject and
2. missing tests for DBDictionary and Xrecord to avoid unwanted recursion.
I agree


Quote
You aren't getting any eHadMultipleReaders exceptions when you open the DBObject directly in the ObjectAppended handler?

Not as long as I use

Database.TransactionManager.StartOpenCloseTransaction()