Author Topic: Reactors/Events  (Read 9366 times)

0 Members and 1 Guest are viewing this topic.

Glenn R

  • Guest
Reactors/Events
« on: November 20, 2006, 06:10:44 PM »
I had occasion to write a program the other day and I had to enable document manager then document events.

Over the last few years that I have been using C#, I've used 2 approaches to setting these up, with the recent one above being the 3rd different way.

The first was loosely based off Jim Awe's ArxDbgMgd. I then realised his approach was a bit convoluted, so I modified it ever further.
The last program I completely changed it.

So, how do you set up your events for Document Manager Level downwards all the way to Database and Entity events.

Cheers,
Glenn.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Reactors/Events
« Reply #1 on: November 21, 2006, 07:21:30 PM »
Hi Glenn

I haven't done much with Events Trapping so can't respond with any useful info.

Mention of Jim's DbgMgd reminds me that he was going to update the samples for 2007++ .. I should chase that down.

What approach are you currently using, ? .. can you say ?

Regards
kwb
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Glenn R

  • Guest
Re: Reactors/Events
« Reply #2 on: November 21, 2006, 07:35:06 PM »
Glad you asked Kerry :)

In a file called Entrypoint.cs which kicks the whole process off:

Code: [Select]
// © Copyright 2006 by TCGSoftware.

using System.Collections.Generic;

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;

using acadApp = Autodesk.AutoCAD.ApplicationServices.Application;

[assembly: ExtensionApplication(typeof(TCGS.tcgsClass))]

namespace TCGS
{
/// <summary>
/// Summary description for tcgsClass.
/// </summary>
public class tcgsClass : IExtensionApplication
{
#region Private state vars

/// <summary>
/// Stores all the documents and their associated events.
/// </summary>
private Dictionary<Document, DocumentEvents> _docs = null;
/// <summary>
/// Pointer to the one and only document manager.
/// </summary>
private DocumentCollection _docMan = null;

#endregion

#region Constructor(s)

/// <summary>
/// Default ctor
/// </summary>
public tcgsClass( ) { }

#endregion

#region IExtensionApplication Members

public void Initialize( )
{
// Init our internal dictionary
_docs = new Dictionary<Document, DocumentEvents>();
// Grab a pointer to the document manager...
_docMan = acadApp.DocumentManager;
// Loop over all open documents and add them
// to our dictionary and hook up events for each
foreach (Document doc in _docMan)
_docs.Add(doc, new DocumentEvents(doc));
// Enable the document manager level events we're interested in....
_docMan.DocumentCreated += new DocumentCollectionEventHandler(_docMan_DocumentCreated);
_docMan.DocumentToBeDestroyed += new DocumentCollectionEventHandler(_docMan_DocumentToBeDestroyed);
// Let everybody know we're in...
Editor ed = acadApp.DocumentManager.MdiActiveDocument.Editor;
ed.WriteMessage("\nYourAppNameHere, Copyright © 2006 by TCGSoftware.");
}

public void Terminate( )
{
try
{
// Try to remove our event hooks...
_docMan.DocumentCreated -= new DocumentCollectionEventHandler(_docMan_DocumentCreated);
_docMan.DocumentToBeDestroyed -= new DocumentCollectionEventHandler(_docMan_DocumentToBeDestroyed);
}
catch
{
// Explicit catch all here as acad is shutting down fast.
}
}

#endregion

#region Event callbacks

private void _docMan_DocumentCreated(object sender, DocumentCollectionEventArgs e)
{
// If our list already has this document, bail out
if (_docs.ContainsKey(e.Document))
return;
//...it doesn't, so add it in and hook up events...
_docs.Add(e.Document, new DocumentEvents(e.Document));
}

private void _docMan_DocumentToBeDestroyed(object sender, DocumentCollectionEventArgs e)
{
// Does our list contain the document being destroyed?
// If not, bail out.
if (!_docs.ContainsKey(e.Document))
return;
// Unhook the events for this document...
_docs[e.Document].UnhookEvents();
//...and remove it from our list
_docs.Remove(e.Document);
}

#endregion
}
}

Notice the generic dictionary collection variable '_docs'. It is what ties the whole thing together.
I will post up the "DocumentEvents" class file once you've dgiested the above.

Let me know your thoughts.

Cheers,
Glenn.
« Last Edit: November 21, 2006, 07:36:53 PM by Glenn R »

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Reactors/Events
« Reply #3 on: November 21, 2006, 08:04:11 PM »
I'll have a good look when I get a chance to come up to breathe ...

Using the  Framework Dictionary is interesting ...
I've been reading up on them/it with the idea of storing attribute Keys and Values

... looks promising
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Glenn R

  • Guest
Re: Reactors/Events
« Reply #4 on: November 21, 2006, 08:18:55 PM »
Yes, generics are a good thing. The generic dictionary works a little differently than the one in System.Collections, mainly in how you iterate over it.
Let me know when you want to see the next file, DocumentEvents.

Cheers,
Glenn.

TonyT

  • Guest
Re: Reactors/Events
« Reply #5 on: November 22, 2006, 01:34:16 AM »
Here's one way (no generics, runs on NET 1.1):

    http://www.caddzone.com/DocumentDataObject.cs

I had occasion to write a program the other day and I had to enable document manager then document events.

Over the last few years that I have been using C#, I've used 2 approaches to setting these up, with the recent one above being the 3rd different way.

The first was loosely based off Jim Awe's ArxDbgMgd. I then realised his approach was a bit convoluted, so I modified it ever further.
The last program I completely changed it.

So, how do you set up your events for Document Manager Level downwards all the way to Database and Entity events.

Cheers,
Glenn.

Glenn R

  • Guest
Re: Reactors/Events
« Reply #6 on: November 22, 2006, 06:26:54 AM »
Tony,

That's an interesting approach - mimicing the arx docData...never thought of that.
I'll give it a good thrashing tomorrow, however, one question - why mark the class as 'Serializable'?

Cheers,
Glenn.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Reactors/Events
« Reply #7 on: November 22, 2006, 04:18:31 PM »
.................. Let me know when you want to see the next file, DocumentEvents.

Cheers,
Glenn.

Glenn, don't feel that you should wait till I'm ready ... I'm running my butt off at the moment and probably won't get to look at your offering till the weekend.  If you happen to get a chance to post the DocumentEvents stuff I'll look at both together ... and I'm sure there are some lurkers who would be interested. :-)

be well,
kwb
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Glenn R

  • Guest
Re: Reactors/Events
« Reply #8 on: November 22, 2006, 06:19:18 PM »
Similar in basic principle to Tony's except he's using what I would call a 'factory' to spin up instance types from a static method/class.
C# 2.0 now allows static to be applied to a class, so I will investigate that coupled with Tony's method.

Anyway, here it is - nothing flash, just standard oop stuff really (data hiding etc):
Code: [Select]
// © Copyright 2006 by TCGSoftware.

using System;
using System.IO;
using System.Windows.Forms;

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Internal;

namespace TCGS
{
/// <summary>
/// Encapsulates the events on this document and it's database.
/// </summary>
public class DocumentEvents : IDisposable
{
#region Private state variables

/// <summary>
/// Document to be watched.
/// </summary>
private Document _doc = null;
/// <summary>
/// Database associated with the watched document.
/// </summary>
private Database _db = null;
/// <summary>
/// Editor associated with the watched document.
/// </summary>
private Editor _ed = null;
/// <summary>
/// Represents this objects 'disposed' state.
/// </summary>
private bool disposed = false;

#endregion

#region Constructor(s)

/// <summary>
/// Default ctor.
/// </summary>
/// <param name="doc">Document that will have events planted on.</param>
public DocumentEvents(Document doc)
{
// Init our state vars...
_doc = doc;
_db = doc.Database;
_ed = doc.Editor;
// Wire up the events we're interested in...
_db.SaveComplete += OnSaveComplete;
}

#endregion

#region Event callbacks

private void OnSaveComplete(object sender, DatabaseIOEventArgs e)
{
// TO DO: Save mojo goes here...
}

#endregion

#region Public helper methods

private void UnhookEvents(bool disposing)
{
if (!this.disposed)
if (disposing)
try { _db.SaveComplete -= OnSaveComplete; }
catch {/* Explicit catch all here as this drawing is closing down fast*/}

this.disposed = true;
}

#endregion

#region IDisposable Members

public void Dispose( )
{
UnhookEvents(true);
_ed = null;
_db = null;
_doc = null;
}

#endregion
}
}

Cheers,
Glenn.

Glenn R

  • Guest
Re: Reactors/Events
« Reply #9 on: November 22, 2006, 09:12:16 PM »
scratch that - missed the property.
« Last Edit: November 22, 2006, 09:18:53 PM by Glenn R »

Glenn R

  • Guest
Re: Reactors/Events
« Reply #10 on: November 22, 2006, 11:28:05 PM »
Tony, thanks for sharing - nice stuff.

I added this to your abstract class:
Code: [Select]
private static DocumentCollection _docMan = null;

...then changed your event hookups in your Initialize method to this:
Code: [Select]
_docMan = acadApp.DocumentManager;
_docMan.DocumentCreated += OnDocumentCreated;
_docMan.DocumentToBeDestroyed += OnDocumentToBeDestroyed;

...then added this:
Code: [Select]
public static void Terminate( )
{
try
{
_docMan.DocumentCreated -= OnDocumentCreated;
_docMan.DocumentToBeDestroyed -= OnDocumentToBeDestroyed;

_docMan = null;
}
catch
{
}
}

...to allow the clean unhooking of the document manager events, otherwise last minute exceptions get thrown.
Now you just call DocumentDataObject.Terminate() from IExtensionApplication's Terminate method.

Cheers,
Glenn.

TonyT

  • Guest
Re: Reactors/Events
« Reply #11 on: November 25, 2006, 08:57:37 AM »
Hi Glenn.  I don't recall the exact reason, but obviuosly it might have something to do with a potential need to serialize a derived class's data.  :-)

Tony,

That's an interesting approach - mimicing the arx docData...never thought of that.
I'll give it a good thrashing tomorrow, however, one question - why mark the class as 'Serializable'?

Cheers,
Glenn.

TonyT

  • Guest
Re: Reactors/Events
« Reply #12 on: December 05, 2006, 02:21:00 PM »
Hi Glenn.  Thanks.

However, I've yet to find any need to unhook the document manager events.   :-)


Tony, thanks for sharing - nice stuff.

I added this to your abstract class:
...then added this: ...
...to allow the clean unhooking of the document manager events, otherwise last minute exceptions get thrown. Now you just call DocumentDataObject.Terminate() from IExtensionApplication's Terminate method.

Cheers,
Glenn.



Glenn R

  • Guest
Re: Reactors/Events
« Reply #13 on: December 05, 2006, 04:33:52 PM »
Hi Tony,

You're probably right; I'm just being tidy :)

Cheers,
Glenn.

Glenn R

  • Guest
Re: Reactors/Events
« Reply #14 on: April 12, 2007, 08:03:29 PM »
Tony,

I've been playing with your DocumentDataObject class and I'm getting some odd behaviour.

I have 2 projects - one that watches when save and layout events happen and another when certain sysvars are changed and layout events occur. I had these working with the method/classes I posted earlier in this thread, but then decided to retro-fit them and use yours.

Both of these projects are demand loaded through registry entries and whichever is in first works, the second in doesn't have any of it's events fire.

I thought I had traced it down to this:
Code: [Select]
if(instanceType != null)
return;

The debugger was showing the second dll hitting this line and returning out. I suspected this was because for some strange reason the first dll had already set instanceType...but that doesn't really make sense.

It get's even more strange. I then ripped out your factory and went back to old faithful, but yesterday I decided to play again.
I created 2 test projects, both using your class factory - one watched when the TILEMODE sysvar changed and the other BeginSave.
To make sure they were working I put in print statements to the commandline inside the event handlers of both.

I then manually loaded both into 2006 and they both worked! I sat there scratching my head and decided maybe I had screwed up on my last attempt and everything was good, so I worte another program yesterday and decided to give DocumentDataObject another bash, leaving my classes intact.

So, I have a 2 dll's loaded that both watch sysvars and whatnot - one uses my way of doing this and the other yours - The one in first still works and the one in second still fails. The only thing they have in common is that in the Initialize method of IExtensionApplication they both do this:
Code: [Select]
LayoutManager.Current.LayoutCreated += OnLayoutCreated;
LayoutManager.Current.LayoutToBeRenamed += new LayoutRenamedEventHandler(OnLayoutToBeRenamed);
LayoutManager.Current.LayoutCopied += new LayoutCopiedEventHandler(OnLayoutCopied);
LayoutManager.Current.LayoutRemoved += new LayoutEventHandler(OnLayoutRemoved);

It's the only common thing between them. Any thoughts?