TheSwamp

Code Red => .NET => Topic started by: GumbyCAD on June 03, 2014, 08:02:37 AM

Title: On Drawing Close (save or No Save)
Post by: GumbyCAD on June 03, 2014, 08:02:37 AM
Hi Team,

A quick question or 2.  More like peoples opinion / ideals.

I want to be able to trap / catch when a person tries to exit the drawing and capture if they choose Yes or No to Saving of Changes.

If they say Yes I want to execute code.

Any suggestions?

Stephan
Title: Re: On Drawing Close (save or No Save)
Post by: WILL HATCH on June 03, 2014, 09:02:36 AM
Check out the Database BeginSave event
Title: Re: On Drawing Close (save or No Save)
Post by: GumbyCAD on June 04, 2014, 09:35:30 AM
Thanks, I did.

I also found the following link on this forum.

http://www.theswamp.org/index.php?topic=42961.0 (http://www.theswamp.org/index.php?topic=42961.0)

My question is if I attached my code to the Beginsave Event on the OpenDoc, do I need to remove the event before the document is closed or will it just remove itself automatically.
(be gentle I have not played much with events, and lots of example always show add and remove)

Code - Visual Basic: [Select]
  1.  
  2.             Dim OpenDoc As Document = DocumentCollectionExtension.Open(mDocMan, FileOpen, False)
  3.  
  4.             AddHandler OpenDoc.Database.BeginSave, AddressOf ExtractOnSave
  5.  
  6.  

Thanks.
Title: Re: On Drawing Close (save or No Save)
Post by: BillZndl on June 04, 2014, 11:51:07 AM
If you are going to close the document, then no, you don't have to remove the handler.
But if you are going to run code after the event fires, then you may want to remove it.
Title: Re: On Drawing Close (save or No Save)
Post by: WILL HATCH on June 04, 2014, 02:48:25 PM
To add to what Bill has said, when I am working on events with per-database data I use the Disposed event to clean up on my end.
Title: Re: On Drawing Close (save or No Save)
Post by: Keith Brown on June 04, 2014, 03:22:52 PM
Hi Will,


Would you mind posting an example?  I haven't had a chance to use the disposed event and I am interested in seeing what you are cleaning up.  Thanks.
Title: Re: On Drawing Close (save or No Save)
Post by: WILL HATCH on June 04, 2014, 08:54:30 PM
I'm away from my development machine but here's the pseudo code for my database watcher class:

Create a dictionary to hold all the per database info.  In my case it was dictionary of <ObjectId (of database), dictionary of <ObjectId (of objects within database), MyClass (to hold this object data)>>

-add handler to Application.DocumentManager.DocumentCreated event
     This handler will register any other per document or per database events for the new database (IF the database was found to contain info that I wanted to watch)

-Database.BeginSave handler:
    This handler will serialize the data held in the MyClass objects and store in an XRecord which is thrown into a dictionary right before the drawing is saved

-Database.Disposed handler:
    This handler removes the per database info from the dictionary and unregisters all other events from this database

FYI to anybody interested, this watcher sits next to an overrule where I've written my own close method to observe how the 'interesting' objects are changing through the drawing's life. Will have more time to elaborate later, hope this helps point in the right direction!
Title: Re: On Drawing Close (save or No Save)
Post by: GumbyCAD on June 04, 2014, 09:29:09 PM
I look forward to see what Will will show.

A second question:
So in principal because I am attaching my Code to the event of the Document itself then I could execute different code for different drawing types.

I.e.
Detail Drawings Execute CodeX on Begin Save
Elevation Drawings execute CodeY on BeginSave
.....

Title: Re: On Drawing Close (save or No Save)
Post by: Kerry on June 04, 2014, 09:59:52 PM
Yes Stephan.

Do you use distinctive borders for each ? .. just thinking about how your routine would tell the difference.
You may need to establish a document dictionary at creation to hold the config data. ( or use an external sql database )

Looks like you are progressing nicely :)
Title: Re: On Drawing Close (save or No Save)
Post by: GumbyCAD on June 04, 2014, 10:05:47 PM
Thanks Kerry.

I have managed to get the Model Data to extract to an SQL database on Save of the drawing.
Wanted to extend it to extract Drawing data too.

And I think your suggestion to save config type is a good idea.

I am already using an extension dictionary to store some of my model data.

Stephan
Title: Re: On Drawing Close (save or No Save)
Post by: Jeff H on June 04, 2014, 10:11:53 PM
I use DatabaseSummaryInfo so can read quickly by reading the beginning bytes and not have to open whole file, people can see dwg properties in window explorer, etc.......
Title: Re: On Drawing Close (save or No Save)
Post by: GumbyCAD on June 04, 2014, 10:23:07 PM
Interesting Jeff.

I found the following Example:
Code: [Select]
[CommandMethod("WriteSummary")]
 static public void WriteSummary()
 {
     Document doc = Application.DocumentManager.MdiActiveDocument;
     Database db = doc.Database;
 
     DatabaseSummaryInfoBuilder infobuilder =
                            new DatabaseSummaryInfoBuilder();
 
     infobuilder.Title = "Test";
     infobuilder.Author = "DevTech";
     infobuilder.Comments = "This is a test drawing";
     infobuilder.Subject = "Testing";
 
     infobuilder.CustomPropertyTable.Add("A", "1");
     infobuilder.CustomPropertyTable.Add("B", "2");
     infobuilder.CustomPropertyTable.Add("C", "3");
 
     DatabaseSummaryInfo info = infobuilder.ToDatabaseSummaryInfo();
     db.SummaryInfo = info; //set the summary info
 }
Title: Re: On Drawing Close (save or No Save)
Post by: WILL HATCH on June 04, 2014, 11:57:00 PM
Well Gumby, I propose a trade.

I have had a hard time getting anything working with an sql database.  It should be easy (I think) but keep running into issues.

Since the code I'm going to be sharing is at the heart of an application I've spent about 400 hours working on I'd like to see what you've got going on as well.
Title: Re: On Drawing Close (save or No Save)
Post by: GumbyCAD on June 05, 2014, 12:05:08 AM
Will happy to share anything.
I understand that if you have spent many hours on work you just don't want to give it away.
I was really only interested in learning from others i.e. the way people think about how the code gets put together.

My only drama is the data I am transferring is from a Proxys Objects (3rd Party App for AUtoCAD) at this stage.
Plan is to extend it to store attribute information from Drawing Sheet Blocks.

Principal goes like this:

And all this is done by a command at the moment.

I am just trying to implement the same so that when the user saves / closes the DWG and hits save the data gets pushed back up to the SQL server.

Stephan
Title: Re: On Drawing Close (save or No Save)
Post by: WILL HATCH on June 05, 2014, 12:41:37 AM
Sorry, guess I lied and used the DatabaseToBeDestroyed event here and not Disposed... Can't remember why, but I do see that I tried out disposed so maybe there was a good reason...

For those who have been around for awhile you will recognize a pattern in the overrule here that I learned from Tony.

A bit of background... In the oil and gas industry in Calgary most small to mid sized companies are doing their 3d piping and structural modelling with cadworx.  When I first started my engineering career I was dumped into doing the tedious civil/structural tasks (because we didn't have a civil guy in house and he didn't care about pile schedules) like checking pile schedules.

It was awful! Imagine checking the info for 200 piles, finding it on a 2d layout that tags it, finding it in the model, id'ing the top and inputting to excel.  I knew there was a better way.

The piling models are stand alone models with nothing but piles (HS structural tubing) and pile caps in them.  Cadworx uses a centerline which it draws 3d solids around.  It stores info on these lines in the xdata (hence the overrule applies to lines only).  The overall application has a mechanism for numbering piles from a top level approach start here then go there then go there concept, ignoring all the details about how many are in between. 

Once the database has had piles numbered in it it stores info into the extension dictionary on modelspace, which is used to define the database as interesting.  Interesting databases are tracked as a part of the revision management system, which allows me to spit out a new pile schedule which includes showing what changes have been made to help with clouding, would also like to be able to roll forward/back through the design.  That tracking was the purpose for this part of the application.

[edit]
Guess I should add that this application sits on it's own and is loaded at startup, the IExtensionApplication interface is the mechanism used to instantiate the class at that time (along with licensing, updates, etc.)
[/edit]

HTH

Code - C#: [Select]
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using Autodesk.AutoCAD.Runtime;
  7. using Autodesk.AutoCAD.DatabaseServices;
  8. using Autodesk.AutoCAD.ApplicationServices;
  9. using Autodesk.AutoCAD.Geometry;
  10. //[assembly: CommandClass(typeof(CivilPiling.PileOverruleCommands))]
  11.  
  12. namespace CivilPiling
  13. {
  14.     public static partial class Tools
  15.     {
  16.         static Tools()
  17.                 {
  18.                         Application.QuitWillStart += quitWillStart;
  19.                 }
  20.  
  21.                 static void quitWillStart( object sender, EventArgs e )
  22.                 {
  23.                         Application.QuitWillStart -= quitWillStart;
  24.                         if( overrule != null )
  25.                         {
  26.                 //Application.ShowAlertDialog("Quit start");
  27.                                 overrule.Dispose();
  28.                                 overrule = null;
  29.                 GC.Collect(2, GCCollectionMode.Default, true);
  30.                         }
  31.                 }
  32.  
  33.                 public static PileOverrule overrule = new PileOverrule(true);
  34.     }
  35.     public class PileOverrule : ObjectOverrule
  36.     {
  37.  
  38.         bool enabled = false;
  39.         RXClass rxclass = RXClass.GetClass(typeof(Line));
  40.         static string AppName = "CADWORX_STEEL";
  41.         public static Dictionary<IntPtr, PileRevision> currentPiles;
  42.         public static Dictionary<IntPtr, PileRevision> issuedPiles;
  43.         public PileOverrule(bool enabled = true)
  44.         {
  45.             currentPiles = new Dictionary<IntPtr, PileRevision>();
  46.             issuedPiles = new Dictionary<IntPtr, PileRevision>();
  47.             Application.DocumentManager.DocumentCreated += DocumentManager_DocumentCreated;
  48.             Enabled = enabled;
  49.             this.SetXDataFilter(AppName);
  50.         }
  51.         //Add database to watch list if it has the appname we're interested in
  52.         void DocumentManager_DocumentCreated(object sender, DocumentCollectionEventArgs e)
  53.         {
  54.             WatchDatabase(e.Document.Database);
  55.         }
  56.  
  57.         public static void WatchDatabase(Database db)
  58.         {
  59.             if (db.isInteresting())
  60.             {
  61.                 if (!currentPiles.ContainsKey(db.UnmanagedObject))
  62.                 {
  63.                     //Application.ShowAlertDialog(
  64.                     string.Format("Watching {0}", db.Filename).WriteMessage();
  65.                     db.BeginSave += Database_BeginSave;
  66.                     db.DatabaseToBeDestroyed += db_DatabaseToBeDestroyed;
  67.                     currentPiles.Add(db.UnmanagedObject, new PileRevision(db, Tools.RevCurrent));
  68.                     if (db.isInteresting())//if db had invalid data it would no longer be interesting at this point
  69.                     {
  70.                         PileRev value = db.GetLastIssuedRevision();
  71.                         if (!value.IsNull()) issuedPiles.Add(db.UnmanagedObject, new PileRevision(db, value));
  72.                     }
  73.                 }
  74.             }
  75.         }
  76.         public static void StopWatching(Database db)
  77.         {
  78.             if (currentPiles.ContainsKey(db.UnmanagedObject))
  79.                 currentPiles.Remove(db.UnmanagedObject);
  80.             if (issuedPiles.ContainsKey(db.UnmanagedObject))
  81.                 issuedPiles.Remove(db.UnmanagedObject);
  82.             db.BeginSave -= Database_BeginSave;
  83.             db.DatabaseToBeDestroyed -= db_DatabaseToBeDestroyed;
  84.         }
  85.         public static void UpdateIssuedRev(Database db, string Revision)
  86.         {
  87.             //update issued piles
  88.             if (issuedPiles.ContainsKey(db.UnmanagedObject)) issuedPiles[db.UnmanagedObject] = new PileRevision(db, Revision);
  89.             else issuedPiles.Add(db.UnmanagedObject, new PileRevision(db, Revision));
  90.             //update current piles
  91.             currentPiles[db.UnmanagedObject].ClearFlags();
  92.         }
  93.         static void db_DatabaseToBeDestroyed(object sender, EventArgs e)
  94.         {
  95.             try
  96.             {
  97.                 string.Format("Database {0} to be destroyed", ((Database)sender).Filename).WriteMessage();
  98.             }
  99.             finally
  100.             {
  101.  
  102.             }
  103.             Database db = (Database)sender;
  104.             if (currentPiles.ContainsKey(db.UnmanagedObject))
  105.                 currentPiles.Remove(db.UnmanagedObject);
  106.             if (issuedPiles.ContainsKey(db.UnmanagedObject))
  107.                 issuedPiles.Remove(db.UnmanagedObject);
  108.         }
  109.         ////remove database from our dictionary
  110.         //void Database_Disposed(object sender, EventArgs e)
  111.         //{
  112.         //    string.Format("Database {0} disposed", ((Database)sender).Filename).WriteMessage();
  113.         //    Database db = (Database)sender;
  114.         //    if (currentPiles.ContainsKey(db.UnmanagedObject))
  115.         //        currentPiles.Remove(db.UnmanagedObject);
  116.         //}
  117.  
  118.         //save our dictionary into the current rev place
  119.         static void Database_BeginSave(object sender, DatabaseIOEventArgs e)
  120.         {
  121.             try
  122.             {
  123.                 Database db = (Database)sender;
  124.                 if (currentPiles.ContainsKey(db.UnmanagedObject))
  125.                     using (Transaction tr = db.TransactionManager.StartTransaction())
  126.                     {
  127.                         //Don't think this is required
  128.                         //DBDictionary revDict;
  129.                         //db.GetCreateRevDict(out revDict);
  130.  
  131.                         string.Format("Database {0} begin save", e.FileName).WriteMessage();
  132.                         currentPiles[db.UnmanagedObject].Issue(new PileRev(Tools.RevCurrent));
  133.                         tr.Commit();
  134.                     }
  135.             }
  136.             catch (System.Exception ex)
  137.             {
  138.                 ex.Show();
  139.             }
  140.         }
  141.  
  142.  
  143.         public virtual bool Enabled
  144.         {
  145.             get
  146.             {
  147.                 return this.enabled;
  148.             }
  149.             set
  150.             {
  151.                 if (this.enabled ^ value)
  152.                 {
  153.                     this.enabled = value;
  154.                     if (value)
  155.                     {
  156.                         Overrule.AddOverrule(rxclass, this, true);
  157.                         this.SetXDataFilter(Tools.AppName);
  158.                     }
  159.                     else
  160.                         Overrule.RemoveOverrule(rxclass, this);
  161.                     OnEnabledChanged(this.enabled);
  162.                 }
  163.             }
  164.         }
  165.         public override void Erase(DBObject dbObject, bool erasing)
  166.         {
  167.             Database db = dbObject.Database;
  168.             if (currentPiles.ContainsKey(db.UnmanagedObject) && currentPiles[db.UnmanagedObject].LivePiles.ContainsKey(dbObject.ObjectId))
  169.             {
  170.                 if (erasing)
  171.                 {
  172.                     Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nRemoving {0} from {1}", dbObject.Id, db.Filename);
  173.                     currentPiles[db.UnmanagedObject].LivePiles[dbObject.Id].Changes |= PileChanges.Deleted;
  174.                     currentPiles[db.UnmanagedObject].LivePiles[dbObject.Id].Erased = true;
  175.                 }
  176.                 else
  177.                 {
  178.                     Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nUndoing removing {0} from {1}", dbObject.Id, db.Filename);
  179.                     currentPiles[db.UnmanagedObject].LivePiles[dbObject.Id].Changes &= PileChanges.NewPile;
  180.                     currentPiles[db.UnmanagedObject].LivePiles[dbObject.Id].Erased = false;
  181.                 }
  182.             }
  183.             base.Erase(dbObject, erasing);
  184.         }
  185.         public override void Close(DBObject obj)
  186.         {
  187.             try
  188.             {
  189.                 PileDataSet pds = obj.ReportExData();
  190.                 if (pds != null)
  191.                 {
  192.                     Database db = obj.Database;
  193.                     if (currentPiles.ContainsKey(db.UnmanagedObject))
  194.                     {
  195.                         ObjectId id = obj.ObjectId;
  196.                         if (obj.IsErased)
  197.                         {
  198.                         }
  199.                         else if (obj.IsNewObject)
  200.                         {
  201.                             ////////////////////////////////////////////////need to thrash pile tag in xdata
  202.                             try
  203.                             {
  204.                                 obj.SetPileTag(" ");
  205.                                 pds.Changes = PileChanges.NewPile;
  206.                                 Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nAdding {0} to {1}", id, db.Filename);
  207.                                 currentPiles[db.UnmanagedObject].LivePiles.Add(obj.ObjectId, pds);
  208.                             }
  209.                             catch (System.Exception e)
  210.                             {
  211.                                 Application.ShowAlertDialog(string.Format("Fail: {0}", e));
  212.  
  213.                             }
  214.                         }
  215.                         else if (obj.IsModified)
  216.                         {
  217.                             ////////////////////////////////////////////////////////////////////determine modifications, update current rev
  218.                             if (issuedPiles.ContainsKey(db.UnmanagedObject) && issuedPiles[db.UnmanagedObject].LivePiles.ContainsKey(id))
  219.                             {
  220.                                 //check all properties against original
  221.                                 PileDataSet issued = issuedPiles[db.UnmanagedObject].LivePiles[pds.Id];
  222.                                 if (pds.PileTag != issued.PileTag) pds.Changes |= PileChanges.PileTag;
  223.                                 if (pds.PileLength != issued.PileLength) pds.Changes |= PileChanges.PileLength;
  224.                                 if (pds.PileSize != issued.PileSize) pds.Changes |= PileChanges.PileSize;
  225.                                 if (pds.Location.X != issued.Location.X) pds.Changes |= PileChanges.XCoordinate;
  226.                                 if (pds.Location.Y != issued.Location.Y) pds.Changes |= PileChanges.YCoordinate;
  227.                                 if (pds.Location.Z != issued.Location.Z) pds.Changes |= PileChanges.ZCoordinate;
  228.                                 string.Format("Changing {0}: {1}", pds.PileTag == null ? "" : pds.PileTag, pds.GetFlags()).WriteMessage();
  229.  
  230.                             }
  231.                             else
  232.                             {
  233.                                 //must be new object since last issue? we can or the changes of the pds we're looking at with the one in current issue?
  234.                                 pds.Changes |= currentPiles[db.UnmanagedObject].LivePiles[pds.Id].Changes;
  235.                                 Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nNew since last rev {0}: {1}", id, pds.GetFlags());
  236.                             }
  237.                             currentPiles[db.UnmanagedObject].LivePiles[id] = pds;
  238.                         }
  239.                     }
  240.                 }
  241.             }
  242.             catch (System.Exception e) { e.Show(); }
  243.             base.Close(obj);
  244.         }
  245.  
  246.         protected virtual void OnEnabledChanged(bool enabled)
  247.         {
  248.         }
  249.  
  250.         protected override void Dispose(bool disposing)
  251.         {
  252.             if (disposing && !base.IsDisposed)
  253.             {
  254.                 Enabled = false;
  255.                 rxclass.Dispose();
  256.                 foreach (IntPtr db in currentPiles.Keys)
  257.                 {
  258.                     string.Format("Destroying {0} data", db).WriteMessage();
  259.                     currentPiles.Remove(db);
  260.                 }
  261.                
  262.                 currentPiles = null;
  263.                 Application.DocumentManager.DocumentCreated -= DocumentManager_DocumentCreated;
  264.             }
  265.             base.Dispose(disposing);
  266.         }
  267.  
  268.     }
  269. }
  270.  
Title: Re: On Drawing Close (save or No Save)
Post by: GumbyCAD on June 05, 2014, 01:35:24 AM
Well at first glance, WOW.

I totally understand were you coming from as I am a structural guy too.  (But Draftsman turned Programmer - try hard)

I understand why you destroy the Event

But a lot of what you have I don't understand, mostly because I dont understand dictionaries.

Thanks for Sharing.  I will study it some more , need to do some reading on dictionaries first.

Stephan
Title: Re: On Drawing Close (save or No Save)
Post by: WILL HATCH on June 05, 2014, 01:48:16 AM
I totally understand were you coming from as I am a structural guy too.

This is the reason for writing the code.  I'm an electrical engineer, this extra job was just a pain and wanted it to go away lol

Dictionaries are pretty simple, you store and access objects by a key.  It's very fast, which is why I use it in the overridden methods to access data.
Title: Re: On Drawing Close (save or No Save)
Post by: GumbyCAD on June 05, 2014, 01:50:53 AM
Quote
Dictionaries are pretty simple, you store and access objects by a key.  It's very fast, which is why I use it in the overridden methods to access data.

Whats the difference between using List(of ObjectType) or Dictionary?  (VB Code sorry)

Is one better than the other?
They seam to do the same job.

Stephan
Title: Re: On Drawing Close (save or No Save)
Post by: WILL HATCH on June 05, 2014, 02:06:04 AM
very similar, the dictionary provides indexing.

I use a dictionary<objectId, CustomDataObject> meaning the customdataobject (value) is accessed by the objectid (key)
Each key value pair is quickly accessible by the key

This is important in the example as I am dealing with per object data and it allows me to access the correct data.

In your case, this could be applicable where you create a dictionary and upon drawing creation you construct some data about the drawing, then when the save event fires you save a copy of that data, and when the tobedestroyed event fires you delete the data from your dictionary