After some much needed clarification on DocumentCollection Events in another thread, I thought I'd start learning more about Events in general in a similar way that I learned Visual LISP Reactors... So I am taking aspects of my LISP library that we use daily for production, and attempting to both port the code to C#, and enhance where possible.
Perhaps I am still just naive to the AutoCAD .NET API, but I was shocked when I looked into Autodesk.AutoCAD.DatabaseService.Viewport and found no Events at all... When I was hoping, anticipating, expecting to find some pseudo ViewportActivated, ViewportToBeActivated, ViewportDeactivated, ViewportToBeDeactivated, etc. Events.
Now, in my Visual LISP Reactor, I am relegated to using a callback to check for MSpace Object to detect when a PViewport is active.
To replicate this functionality (which works fine, again, I'm just trying to learn porting existing code) in C#, should I follow the same logic by registering (+=) a CommandEnded Event handler, etc.... or am I missing something?
TIA
After testing a combination of both TILEMODE and CVPORT to account for three conditions (i.e., ModelSpace, PaperSpace, and PViewport Active), the SystemVariableChanged event misfires when switching from Model Tab to Layout* Tab, but does fire correctly when switching from PViewport Active to PaperSpace.I not sure I follow exactly what your saying but it is very easy for me to be confused.
The behavior I am observing, is that when switching from Model Tab (CVPORT = 2), to the only Layout Tab in my test drawing (CVPORT = 1), the SystemVariableChanged Event fires as I switch to Layout Tab, CVPORT is queried with a value of 2 (Model Tab) prior to the tab actually having been switched.
Command: <Switching to: Layout1>
[App Event] : System Var Changing : NAVBARDISPLAY
[App Event] : System Var Changed : NAVBARDISPLAY
[App Event] : System Var Changing : NAVBARDISPLAY
[App Event] : System Var Changed : NAVBARDISPLAY
[App Event] : System Var Changing : CLAYER
[App Event] : System Var Changed : CLAYER
[App Event] : System Var Changing : TILEMODE
[App Event] : System Var Changed : TILEMODE
[App Event] : System Var Changing : REGENMODE
[App Event] : System Var Changed : REGENMODE
[App Event] : System Var Changing : REGENMODE
[App Event] : System Var Changed : REGENMODERestoring cached viewports - Regenerating layout.
[App Event] : System Var Changing : VIEWBACKSTATUS
[App Event] : System Var Changed : VIEWBACKSTATUS
Command: <Switching to: Layout2>
[App Event] : System Var Changing : VIEWBACKSTATUS
[App Event] : System Var Changed : VIEWBACKSTATUS
[App Event] : System Var Changing : NAVBARDISPLAY
[App Event] : System Var Changed : NAVBARDISPLAY
[App Event] : System Var Changing : NAVBARDISPLAY
[App Event] : System Var Changed : NAVBARDISPLAY
[App Event] : System Var Changing : CLAYER
[App Event] : System Var Changed : CLAYER
[App Event] : System Var Changing : UCSNAME
[App Event] : System Var Changed : UCSNAME
[App Event] : System Var Changing : PSLTSCALE
[App Event] : System Var Changed : PSLTSCALE
[App Event] : System Var Changing : PLIMCHECK
[App Event] : System Var Changed : PLIMCHECK
[App Event] : System Var Changing : PINSBASE
[App Event] : System Var Changed : PINSBASE
[App Event] : System Var Changing : PLIMMIN
[App Event] : System Var Changed : PLIMMIN
[App Event] : System Var Changing : PLIMMAX
[App Event] : System Var Changed : PLIMMAX
[App Event] : System Var Changing : PEXTMIN
[App Event] : System Var Changed : PEXTMIN
[App Event] : System Var Changing : PEXTMAX
[App Event] : System Var Changed : PEXTMAX
[App Event] : System Var Changing : TILEMODE
[App Event] : System Var Changed : TILEMODE
[App Event] : System Var Changing : REGENMODE
[App Event] : System Var Changed : REGENMODE
[App Event] : System Var Changing : REGENMODE
[App Event] : System Var Changed : REGENMODERestoring cached viewports - Regenerating layout.
[App Event] : System Var Changing : VIEWBACKSTATUS
[App Event] : System Var Changed : VIEWBACKSTATUS
Command: <Switching to: Model>
[App Event] : System Var Changing : VIEWBACKSTATUS
[App Event] : System Var Changed : VIEWBACKSTATUS
[App Event] : System Var Changing : NAVBARDISPLAY
[App Event] : System Var Changed : NAVBARDISPLAY
[App Event] : System Var Changing : NAVBARDISPLAY
[App Event] : System Var Changed : NAVBARDISPLAY
[App Event] : System Var Changing : CLAYER
[App Event] : System Var Changed : CLAYER
[App Event] : System Var Changing : UCSNAME
[App Event] : System Var Changed : UCSNAME
[App Event] : System Var Changing : TILEMODE
[App Event] : System Var Changed : TILEMODERestoring cached viewports.
[App Event] : System Var Changing : UCSNAME
[App Event] : System Var Changed : UCSNAME
MgdDbg is a must have.
Do you have to know when they change or just need to know what action to take in a method depending on which space is active?
Also if your inside a viewport or the viewport is active then Database.CurrentSpaceId will return ModelSpaces ObjectID.
I do not know but first thang that came to mind was could shorten a little and not have to open blocktable and BlockTablerecord
Still more to add before I've fully ported my VL routine to .NET, but as for this particular task... I've got it sorted now. :-)
Here's the working source-code (inefficient as it may be):Code - C#: [Select]
using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Internal; using Autodesk.AutoCAD.Runtime; using acApp = Autodesk.AutoCAD.ApplicationServices.Application; using System; namespace FOO { public class Events : IExtensionApplication { void IExtensionApplication.Initialize() { Editor ed = acApp.DocumentManager.MdiActiveDocument.Editor; ed.WriteMessage("\nFOO events registered \n"); Register(); } void IExtensionApplication.Terminate() { } public static void Register() { DocumentCollection acDocs = acApp.DocumentManager; Editor ed = acDocs.MdiActiveDocument.Editor; try { foreach (Document doc in acDocs) { doc.CommandEnded -= doc_CommandEnded; doc.CommandEnded += doc_CommandEnded; } acDocs.DocumentCreated -= acDocs_DocumentCreated; acDocs.DocumentCreated += acDocs_DocumentCreated; acDocs.DocumentActivated -= acDocs_DocumentActivated; acDocs.DocumentActivated += acDocs_DocumentActivated; Reconcile(); } catch (System.Exception ex) { ed.WriteMessage("\n; error: " + ex.Message + " \n"); } } public static void acDocs_DocumentActivated(Object sender, DocumentCollectionEventArgs e) { if (e.Document != null) { Reconcile(); } } public static void acDocs_DocumentCreated(Object sender, DocumentCollectionEventArgs e) { if (e.Document != null) { Register(); } } public static void doc_CommandEnded(Object sender, CommandEventArgs e) { if (Utils.WcMatch(e.GlobalCommandName.ToUpper(), "*CHSPACE,*MSPACE,*PSPACE,*LAYOUT_CONTROL,U,*UNDO,*VPMAX,*VPMIN")) { Reconcile(); } } public static bool PViewportActive(Document doc) { Editor ed = doc.Editor; Database db = doc.Database; Transaction tr = db.TransactionManager.StartOpenCloseTransaction(); //db.TransactionManager.StartTransaction(); using (tr) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForRead); if (btr.Name.ToUpper() == "*MODEL_SPACE") { return true; } else { return false; } } } public static void Reconcile() { DocumentCollection acDocs = acApp.DocumentManager; Document doc = acDocs.MdiActiveDocument; Editor ed = doc.Editor; string tilemode = acApp.GetSystemVariable("TILEMODE").ToString(); // Model space if (tilemode == "1") { ed.WriteMessage("\n** ModelSpace active ** \n"); } else { // Pviewport if (PViewportActive(doc)) { ed.WriteMessage("\n** PViewport active ** \n"); } // Paper space else { ed.WriteMessage("\n** PaperSpace active ** \n"); } } } } }
Separately, I'd still appreciate it if someone could clarify if I am using StartOpenCloseTransaction() correctly, or if there's something I should watch for / correct in this test expression.
TIA
the removing and then immediately adding back event handlersThis is a practice I use to insure an event handler is not registered more than once for example within a Documentactivated event handler :
Tony,Quotethe removing and then immediately adding back event handlersThis is a practice I use to insure an event handler is not registered more than once for example within a Documentactivated event handler :Code - C#: [Select]Let me know if you think it is not a good practice and which one one you think is a better one.
void docMan_DocumentActivated(object sender, DocumentCollectionEventArgs e) { e.Document.CommandEnded -= doc_CommandEnded; e.Document.CommandEnded += doc_CommandEnded; }
RenderMan,
IMO, using both Documentactivated and DocumentCreated handlers is redundant.
If you used DocumentCreated, you do not need to remove the handler before adding it as the handler sould be add only once for a newly opened or created document.
If i do not misunderstand what you are trying to do, you want to register a CommandEnded event handler to all already opened documents when your application is loaded and to all those which may be created after.
Here's a way:Code - C#: [Select]
public class Events : IExtensionApplication { private DocumentCollection acDocs; public void Initialize() { acDocs = acApp.DocumentManager; // register doc_CommandEnded for all already opened documents foreach (Document doc in acDocs) { doc.CommandEnded += doc_CommandEnded; } // register acDocs_DocumentCreated for all futurely created documents acDocs.DocumentCreated += acDocs_DocumentCreated; } public void Terminate() { } void acDocs_DocumentCreated(object sender, DocumentCollectionEventArgs e) { // register doc_CommandEnded for a newly created document e.Document.CommandEnded += doc_CommandEnded; } void doc_CommandEnded(object sender, CommandEventArgs e) { if (Utils.WcMatch(e.GlobalCommandName.ToUpper(), "*CHSPACE,*MSPACE,*PSPACE,*LAYOUT_CONTROL,U,*UNDO,*VPMAX,*VPMIN")) { Reconcile(); } } private void Reconcile() { Document doc = acDocs.MdiActiveDocument; Editor ed = doc.Editor; short tilemode = (short)acApp.GetSystemVariable("TILEMODE"); // Model space if (tilemode == 1) { ed.WriteMessage("\n** ModelSpace active ** \n"); } else { short cvport = (short)acApp.GetSystemVariable("CVPORT"); // Pviewport if (cvport > 1) { ed.WriteMessage("\n** PViewport active ** \n"); } // Paper space else { ed.WriteMessage("\n** PaperSpace active ** \n"); } } } }
As for using LayoutSwitched && SystemVariableChanged; this will not function properly in MDI, as when one Document is in ModelSpace, and a second is in Layout with PViewport Active, switching between them does not change a Document level System Variable.
Further adding to the mix, as I am attempting to enhance my VL routine, is that I have just learned that not even .NET API has direct exposure to the Preferences Objects, and must use COM first before accessing PreferencesDisplay, etc.
Not a big deal on the surface, but I am again disappointed to learn that I cannot simply monitor a Preferences*Modified event. If I am correct in my understanding, then even my .NET adaptation suffers similar limitations for accounting for user changes via Options dialog or VL functions which change Preferences* Objects outside of this plug-in.
Further adding to the mix, as I am attempting to enhance my VL routine, is that I have just learned that not even .NET API has direct exposure to the Preferences Objects, and must use COM first before accessing PreferencesDisplay, etc.
Further adding to the mix, as I am attempting to enhance my VL routine, is that I have just learned that not even .NET API has direct exposure to the Preferences Objects, and must use COM first before accessing PreferencesDisplay, etc.
I just ran across this which I guess I started and forgot about. Might be helpful and could put in whatever settings you need so not to have to reference the Interop assemblies.
Jeff / Tony -
Forgive my elementary question here; just trying to better understand....
I've read that when using the dynamic (http://msdn.microsoft.com/en-us/library/dd264736.aspx) type, "you do not have to be concerned about whether the object gets its value from a COM API, from a dynamic language such as IronPython, from the HTML Document Object Model (DOM), from reflection, or from somewhere else," but I do not understand how or why one would not need to reference the Interop assembly.
How does one access the Application.AcadApplication Object without the appropriate reference, and only System[.*] using statements?
... and yet another documentation issue.?? This is a 'new to .NET 4' type. It's documented fairly well, imho, in the .NET docs. I first learned of it on Kean's blog when he was first discussing 2013's new API features.
Intellisense was probably way more motivation then should have been(if any is good),Further adding to the mix, as I am attempting to enhance my VL routine, is that I have just learned that not even .NET API has direct exposure to the Preferences Objects, and must use COM first before accessing PreferencesDisplay, etc.
I just ran across this which I guess I started and forgot about. Might be helpful and could put in whatever settings you need so not to have to reference the Interop assemblies.
Other than for the sake of limited Intellisense support, I'm not sure all of that is necessaryCode - C#: [Select]
public static void Example() { string path = ( (dynamic) Application.Preferences ).Files.AutoSavePath; }
All you have to do is cast the AcadApplication object to dynamic.
My mistake; the link to the Preferences* COM Object reference in the documentation I posted previously was for earlier than 2012, specifically coding for 2011 (.NET 3.5).
Look at Tony's Save extension methods here (http://www.theswamp.org/index.php?topic=42016.msg471429#msg471429)
It shows using a dynamic object ---- requires 4.0
and shows using reflection ----- pre 4.0
Just to confirm then... .NET 3.5 (2011), I will still require the Interop reference and methodology described in my earlier post (as specified in the documentation linked)?
Tony,Quotethe removing and then immediately adding back event handlersThis is a practice I use to insure an event handler is not registered more than once for example within a Documentactivated event handler :Code - C#: [Select]Let me know if you think it is not a good practice and which one one you think is a better one.
void docMan_DocumentActivated(object sender, DocumentCollectionEventArgs e) { e.Document.CommandEnded -= doc_CommandEnded; e.Document.CommandEnded += doc_CommandEnded; }