明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 13623|回复: 12

[Kean专集] Kean专题(10)—Notification_Events

   关闭 [复制链接]
发表于 2009-6-7 17:35:00 | 显示全部楼层 |阅读模式
本帖最后由 作者 于 2009-6-12 7:33:30 编辑

原帖:http://through-the-interface.typepad.com/through_the_interface/notification_events
一、圆链
November 27, 2006
Linking Circles, Part 1: Using .NET events to relate AutoCAD geometry
I received this question some time ago from Paul Richardson from CAD System Engineering:
I have never been sure when to update objects programmatically. An example would be a user edits an entity that I’m tracking and I need to edit another entity in reaction to that change. Is there a standard used to cache the handle, and make the change.
Doesn’t seem editing of entities should be done in the event that is watching for the change. When/how does one do it? Doesn’t seem to be any info on a standard for this.
This is such an excellent question I'm going to spend a number of posts answering it. :-)
Introduction
But first, time for a little nostalgia. One of my first ObjectARX projects was back in 1996 - I'd been using LISP and ADS for several years by that point, but I decided what I really needed was a nice, juicy problem to help me immerse myself in ObjectARX. Along with a colleague at the time, I came up with the idea of using ObjectARX's notification mechanism to link circles together in a chain.
The idea was essentially that you "link" sets of two circles together, and whenever you move one of these circles, the other circle moves in the most direct line to stay attached to it. You would then be able to build up "chains" of linked circles, and the movement of the head of the chain would cause the rest of the chain to follow, with a ripple of notification events modifying one circle after the other.
It was my first major ObjectARX undertaking, so I was fairly heavy-handed with the architecture: each "link" was maintained by two persistent reactors - one attached to each of the linked entities. There were also a number of other reactors and objects involved in the whole system which, in spite of it's weight, worked pretty well. I demoed the sample to developers at a number of different events, to show the power of ObjectARX, and also built it into my first AutoCAD OEM demo application (called SnakeCAD :-).
Anyway - I hadn't thought about this code for several years, but then I received Paul's question and by chance stumbled across the source attached to an old email, so thought I'd spend some time reimplementing the system in .NET. I was able to recode the whole thing in less than a day, partly thanks to the additional experience of being 10 years longer-in-the-tooth, but mainly because of the advantages of using a much more modern development environment.
I'm going to serialize the code over a few posts. The first shows the basic implementation, which should allow you to focus on how the events do their stuff, and I'll later on deal with persistence of our data and some more advanced features (such as automatic linking and support for other circular - even spherical - objects).
The Basic Application
For this application I'm going to try something different, by putting line numbers in the below code (to make the explanation simpler) and providing the main class file as a download.
First, a little on the approach:
The basic application defines one single command - "LINK" (lines 162-194). This command asks the user to select two circles, which it then links together. It does this by using a special "link manager" object (the LinkedObjectManager class is defined from lines 23 to 115), which is used to maintain the references between the various circles.
This LinkedObjectManager stores one-to-many relationships by maintaining a Dictionary, mapping between ObjectIds and ObjectIdCollections. This means that any particular circle can be linked to multiple other circles. The relationships also get added bi-directionally, so the LinkedObjectManager will create a backwards link when it creates the forwards one (lines 37-38).
The linking behaviour is maintained by two main event callbacks: the first is Database.ObjectModified(), which is called whenever an object stored in the active drawing has been changed in some way. This event callback is implemented between lines 196 and 206. All it does is check whether the object that has been modified is one that is being "managed" by our link manager - if so, we add its ID to the list of entities to update later on (the collection that is declared on line 122).
This is really the answer to Paul's question: we store the ObjectId in a list that will get picked up in the Editor.CommandEnded() callback, where we go and update the various objects. My original implementation didn't do that: it opened the objects directly using Open()/Close() (which are marked as obsolete in the .NET API, as we're encouraging the use of Transactions instead), and made the changes right then. Overall the implementation in this version is safer and, I feel, more elegant - CommandEnded() is really the way to go for this kind of operation.
[Aside: for those of you that are ADN members, you should find additional information on this limitation on the ADN website. Here's an article that covers this for VBA, for instance: How to modify an object from object's Modified or document's ObjectAdded, ObjectModified, and so on events.]
The Editor.CommandEnded() callback is implemented between lines 219 and 227, and calls through to another function (UpdateLinkedEntities()) to do the heavy lifting (lines 230-316). This function checks the geometry of the linked objects - I've tried to keep the code fairly generic to make it easier for us to extend this later to handle non-circles - and moves the second one closer to the first one. This in turn fires the Database.ObjectModified() event again, which adds this entity's ObjectId into the list of entities to update. What's interesting about this implementation is that the foreach loop that is making the calls to UpdateLinkedEntities() for each object in the list (lines 222-225), will also take into account the newly added entities. This allows the change to ripple through the entire chain of circles.
Here's the C# code:
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.ApplicationServices;
  6. using Autodesk.AutoCAD.DatabaseServices;
  7. using Autodesk.AutoCAD.EditorInput;
  8. using Autodesk.AutoCAD.Geometry;
  9. [assembly:
  10.   CommandClass(
  11.     typeof(
  12.       AsdkLinkingLibrary.LinkingCommands
  13.     )
  14.   )
  15. ]
  16. namespace AsdkLinkingLibrary
  17. {
  18.   /// <summary>
  19.   /// Utility class to manage links between objects
  20.   /// </summary>
  21.   public class LinkedObjectManager
  22.   {
  23.     Dictionary<ObjectId, ObjectIdCollection> m_dict;
  24.     // Constructor
  25.     public LinkedObjectManager()
  26.     {
  27.       m_dict =
  28.         new Dictionary<ObjectId,ObjectIdCollection>();
  29.     }
  30.     // Create a bi-directional link between two objects
  31.     public void LinkObjects(ObjectId from, ObjectId to)
  32.     {
  33.       CreateLink(from, to);
  34.       CreateLink(to, from);
  35.     }
  36.     // Helper function to create a one-way
  37.     // link between objects
  38.     private void CreateLink(ObjectId from, ObjectId to)
  39.     {
  40.       ObjectIdCollection existingList;
  41.       if (m_dict.TryGetValue(from, out existingList))
  42.       {
  43.         if (!existingList.Contains(to))
  44.         {
  45.           existingList.Add(to);
  46.           m_dict.Remove(from);
  47.           m_dict.Add(from, existingList);
  48.         }
  49.       }
  50.       else
  51.       {
  52.         ObjectIdCollection newList =
  53.           new ObjectIdCollection();
  54.         newList.Add(to);
  55.         m_dict.Add(from, newList);
  56.       }
  57.     }
  58.     // Remove bi-directional links from an object
  59.     public void RemoveLinks(ObjectId from)
  60.     {
  61.       ObjectIdCollection existingList;
  62.       if (m_dict.TryGetValue(from, out existingList))
  63.       {
  64.         m_dict.Remove(from);
  65.         foreach (ObjectId id in existingList)
  66.         {
  67.           RemoveFromList(id, from);
  68.         }
  69.       }
  70.     }
  71.     // Helper function to remove an object reference
  72.     // from a list (assumes the overall list should
  73.     // remain)
  74.     private void RemoveFromList(
  75.       ObjectId key,
  76.       ObjectId toremove
  77.     )
  78.     {
  79.       ObjectIdCollection existingList;
  80.       if (m_dict.TryGetValue(key, out existingList))
  81.       {
  82.         if (existingList.Contains(toremove))
  83.         {
  84.           existingList.Remove(toremove);
  85.           m_dict.Remove(key);
  86.           m_dict.Add(key, existingList);
  87.         }
  88.       }
  89.     }
  90.     // Return the list of objects linked to
  91.     // the one passed in
  92.     public ObjectIdCollection GetLinkedObjects(
  93.       ObjectId from
  94.     )
  95.     {
  96.       ObjectIdCollection existingList;
  97.       m_dict.TryGetValue(from, out existingList);
  98.       return existingList;
  99.     }
  100.     // Check whether the dictionary contains
  101.     // a particular key
  102.     public bool Contains(ObjectId key)
  103.     {
  104.       return m_dict.ContainsKey(key);
  105.     }
  106.   }
  107.   /// <summary>
  108.   /// This class defines our commands and event callbacks.
  109.   /// </summary>
  110.   public class LinkingCommands
  111.   {
  112.     LinkedObjectManager m_linkManager;
  113.     ObjectIdCollection m_entitiesToUpdate;
  114.     public LinkingCommands()
  115.     {
  116.       Document doc =
  117.         Application.DocumentManager.MdiActiveDocument;
  118.       Database db = doc.Database;
  119.       db.ObjectModified +=
  120.         new ObjectEventHandler(OnObjectModified);
  121.       db.ObjectErased +=
  122.         new ObjectErasedEventHandler(OnObjectErased);
  123.       doc.CommandEnded +=
  124.         new CommandEventHandler(OnCommandEnded);
  125.       m_linkManager = new LinkedObjectManager();
  126.       m_entitiesToUpdate = new ObjectIdCollection();
  127.     }
  128.     ~LinkingCommands()
  129.     {
  130.       try
  131.       {
  132.         Document doc =
  133.           Application.DocumentManager.MdiActiveDocument;
  134.         Database db = doc.Database;
  135.         db.ObjectModified -=
  136.           new ObjectEventHandler(OnObjectModified);
  137.         db.ObjectErased -=
  138.           new ObjectErasedEventHandler(OnObjectErased);
  139.         doc.CommandEnded +=
  140.           new CommandEventHandler(OnCommandEnded);
  141.       }
  142.       catch(System.Exception)
  143.       {
  144.         // The document or database may no longer
  145.         // be available on unload
  146.       }
  147.     }
  148.     // Define "LINK" command
  149.     [CommandMethod("LINK")]
  150.     public void LinkEntities()
  151.     {
  152.       Document doc =
  153.         Application.DocumentManager.MdiActiveDocument;
  154.       Database db = doc.Database;
  155.       Editor ed = doc.Editor;
  156.       PromptEntityOptions opts =
  157.         new PromptEntityOptions(
  158.           "\nSelect first circle to link: "
  159.         );
  160.       opts.AllowNone = true;
  161.       opts.SetRejectMessage(
  162.         "\nOnly circles can be selected."
  163.       );
  164.       opts.AddAllowedClass(typeof(Circle), false);
  165.       PromptEntityResult res = ed.GetEntity(opts);
  166.       if (res.Status == PromptStatus.OK)
  167.       {
  168.         ObjectId from = res.ObjectId;
  169.         opts.Message =
  170.           "\nSelect second circle to link: ";
  171.         res = ed.GetEntity(opts);
  172.         if (res.Status == PromptStatus.OK)
  173.         {
  174.           ObjectId to = res.ObjectId;
  175.           m_linkManager.LinkObjects(from, to);
  176.           m_entitiesToUpdate.Add(from);
  177.         }
  178.       }
  179.     }
  180.     // Define callback for Database.ObjectModified event
  181.     private void OnObjectModified(
  182.       object sender, ObjectEventArgs e)
  183.     {
  184.       ObjectId id = e.DBObject.ObjectId;
  185.       if (m_linkManager.Contains(id) &&
  186.           !m_entitiesToUpdate.Contains(id))
  187.       {
  188.         m_entitiesToUpdate.Add(id);
  189.       }
  190.     }
  191.     // Define callback for Database.ObjectErased event
  192.     private void OnObjectErased(
  193.       object sender, ObjectErasedEventArgs e)
  194.     {
  195.       if (e.Erased)
  196.       {
  197.         m_linkManager.RemoveLinks(e.DBObject.ObjectId);
  198.       }
  199.     }
  200.     // Define callback for Document.CommandEnded event
  201.     private void OnCommandEnded(
  202.       object sender, CommandEventArgs e)
  203.     {
  204.       foreach (ObjectId id in m_entitiesToUpdate)
  205.       {
  206.         UpdateLinkedEntities(id);
  207.       }
  208.       m_entitiesToUpdate.Clear();
  209.     }
  210.     // Helper function for OnCommandEnded
  211.     private void UpdateLinkedEntities(ObjectId from)
  212.     {
  213.       Document doc =
  214.         Application.DocumentManager.MdiActiveDocument;
  215.       Editor ed = doc.Editor;
  216.       Database db = doc.Database;
  217.       ObjectIdCollection linked =
  218.         m_linkManager.GetLinkedObjects(from);
  219.       Transaction tr =
  220.         db.TransactionManager.StartTransaction();
  221.       using (tr)
  222.       {
  223.         try
  224.         {
  225.           Point3d firstCenter;
  226.           Point3d secondCenter;
  227.           double firstRadius;
  228.           double secondRadius;
  229.           Entity ent =
  230.             (Entity)tr.GetObject(from, OpenMode.ForRead);
  231.           if (GetCenterAndRadius(
  232.                 ent,
  233.                 out firstCenter,
  234.                 out firstRadius
  235.               )
  236.           )
  237.           {
  238.             foreach (ObjectId to in linked)
  239.             {
  240.               Entity ent2 =
  241.                 (Entity)tr.GetObject(to, OpenMode.ForRead);
  242.               if (GetCenterAndRadius(
  243.                     ent2,
  244.                     out secondCenter,
  245.                     out secondRadius
  246.                   )
  247.               )
  248.               {
  249.                 Vector3d vec = firstCenter - secondCenter;
  250.                 if (!vec.IsZeroLength())
  251.                 {
  252.                   // Only move the linked circle if it's not
  253.                   // already near enough               
  254.                   double apart =
  255.                     vec.Length - (firstRadius + secondRadius);
  256.                   if (apart < 0.0)
  257.                     apart = -apart;
  258.                   if (apart > 0.00001)
  259.                   {
  260.                     ent2.UpgradeOpen();
  261.                     ent2.TransformBy(
  262.                       Matrix3d.Displacement(
  263.                         vec.GetNormal() * apart
  264.                       )
  265.                     );
  266.                   }
  267.                 }
  268.               }
  269.             }
  270.           }
  271.         }
  272.         catch (System.Exception ex)
  273.         {
  274.           Autodesk.AutoCAD.Runtime.Exception ex2 =
  275.             ex as Autodesk.AutoCAD.Runtime.Exception;
  276.           if (ex2 != null &&
  277.               ex2.ErrorStatus != ErrorStatus.WasOpenForUndo)
  278.           {
  279.             ed.WriteMessage(
  280.               "\nAutoCAD exception: {0}", ex2
  281.             );
  282.           }
  283.           else if (ex2 == null)
  284.           {
  285.             ed.WriteMessage(
  286.               "\nSystem exception: {0}", ex
  287.             );
  288.           }
  289.         }
  290.         tr.Commit();
  291.       }
  292.     }
  293.     // Helper function to get the center and radius
  294.     // for all supported circular objects
  295.     private bool GetCenterAndRadius(
  296.       Entity ent,
  297.       out Point3d center,
  298.       out double radius
  299.     )
  300.     {
  301.       // For circles it's easy...
  302.       Circle circle = ent as Circle;
  303.       if (circle != null)
  304.       {
  305.         center = circle.Center;
  306.         radius = circle.Radius;
  307.         return true;
  308.       }
  309.       else
  310.       {
  311.         // Throw in some empty values...
  312.         // Returning false indicates the object
  313.         // passed in was not useable
  314.         center = Point3d.Origin;
  315.         radius = 0.0;
  316.         return false;
  317.       }
  318.     }
  319.   }
  320. }
Here's what happens when you execute the LINK command on some circles you've drawn...
Some circles:

After the LINK command has been used to link them together, two-by-two:


Now grip-move the head of the chain:

And here's the result - the chain moves to remain attached to the head:


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-6-12 07:37:00 | 显示全部楼层

November 29, 2006
Linking Circles, Part 2: Getting persistent
In the previous post we looked at some code that created chains of circles, linking them together using .NET events (a technique that can be used to maintain other types of geometric relationship, of course). In this post, we're going to extend our sample to support persistence of the link data in the AutoCAD database.

Firstly, here's the updated source file. Below I've posted the code, once again with line numbers - but this time the lines that have changed or been added since the previous post are marked in red. This should highlight the modified sections of the code.

Looking at the specific changes, the major updates are to our LinkedObjectManager class: between lines 124 and 454 there's some additional protocol to support persistence. Primarily the obviously named SaveToDatabase() and LoadFromDatabase(), but also some support functions: AddValidatedLinks(), which we use on loading data from the drawing to make sure only valid links get resurrected, and GetLinkDictionaryId(), which we use to identify (and create, if needed) the dictionary we're using to store the link data.

Some information on how the data is being stored: I decided to go ahead and use Xrecords to store the data. Xrecords are flexible, non-graphical data containers that can be stored in dictionaries (of type DBDictionary) in the DWG file. They are also supported natively by AutoCAD, so there's no need for a DBX module to help you access the data. DBDictionaries are basically persistent maps between keys and values. A simple "LINKXREC" gets suffixed by a counter ("0", "1", "2", etc.) to store our Xrecords - this way we know exactly where to look for them.

It's worth taking the trouble of creating nested dictionaries - an outer one for the "company", and an inner one for the "application". The outer one must, of course, be prefixed with your Registered Developer Symbol (RDS) to prevent conflicts with other applications. Having an inner dictionary just gives us greater flexibility if we later choose to extend the amount of custom data we store in the drawing file.

The rest of the changes are to add some simple commands - LOADLINKS and SAVELINKS - to call through to our new persistence protocol. There's also an event handler for BeginSave(), which will automatically put our data into the drawing file when it's about to be saved. This type of automatic persistence is clearly very convenient: an exercise I've left for the reader is to automatically load in the data when it exists. The idea would be to respond to a drawing load event (for instance), check whether our data is there (for which we have a very helpful function, GetLinkDictionaryId()) and then prompt the user whether our data should be loaded (or simply go and do it, depending on the extent to which you want to insulate your users from this kind of decision). The implementation is there, it should be fairly trivial to hook the pieces together.

Another note about the persistence of our data: it should be obvious by now, but we're only using the DBDictionary of Xrecords to store our data - at runtime we use an in-memory dictionary mapping ObjectIds to collections of ObjectIds. This means the data - as you create links between circles - could get out-of-sync with what is stored in the drawing, especially if we were just relying on a command being invoked to save the data.

If you're interested in checking out how the data is stored, you should look at the ArxDbg sample on the ObjectARX SDK (under samples/database/ARXDBG). This invaluable sample takes the lid off the structure of the drawing database, allowing you to see what is stored and where. The sample also contains some very useful code, showing how to use even some of the more obscure parts of ObjectARX.

Here's what we see when we use the SNOOPDB command from the ArxDbg sample to take a look at the contents of our custom dictionary:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-6-12 07:40:00 | 显示全部楼层
December 01, 2006
Linking Circles, Part 3: Automatic linking on circle creation
In the previous posts we looked at some code to link AutoCAD entities via .NET events, and how to persist the link data in the drawing file.
This post extends the previous code to automatically link circles into the head of the chain, as circles are drawn by the user. The changes to the project are relatively modest compared to last time. Once again, the source is both available for download and listed below with changed line-numbers in red.
Some notes on the changes:
First we declare some new variables in our command class: a boolean (m_autolink - line 463) which tells us whether automatic linking is "on" or "off", and an ObjectId (m_lastEntity - line 464), which we will use to find the most recently created, linked object. We could have made this a setting in our LinkedObjectManager class, should we have wanted to make this a persistent setting, for instance, but for simplicity's sake we'll leave it in the command class for now.
We have to register (lines 475-476) and unregister (lines 497-498) a handler for another event - OnObjectAppended(). It's via this callback that we're informed that a new object has been added to the drawing.
Next we define our AUTOLINK command (lines 547-563). This simply toggles the m_autolink setting between true and false (or on and off). We might have chosen to display the current setting and ask whether the user wanted to change it to on or off, but frankly that seemed like overkill. If you don't like what you've set it to, you can just call the command again. :-)
A minor change was needed to OnObjectErased(), to set the value of m_lastEntity to Null, should the entity be erased. This also gets caught by a change in the code right at the end, but it's cleaner coding to make the behaviour right here, also.
Next we have the guts of our implementation (such that it is), which is the OnObjectAppended() callback definition (lines 612-636). Here we check whether the object added is a circle, and if so, we either link it to the last one added (stored in m_lastEntity), or - if the value of m_lastEntity is Null, for whatever reason - then we simply make it the next object to be linked in, and leave it at that.
And finally there's a minor change I added to more elegantly support UNDO (which also applies to the code in the first two posts, although I won't go and update them now). Because we're not persisting the state of our links synchronously in the drawing database, they don't automatically participate in the undo mechanism (e.g. if the user uses the UNDO command, we would have to do a little extra work to recreate the correct "last object to link to" settings). Rather than implement the equivalent of our own undo mechanism, I decided not to bother, and simply made sure that when a link is to an erased object, we simply give up (without any error message). This shouldn't happen very often - as we have our OnObjectErased() callback, but you never know. It does mean that the "bad" links might continue to exist in our LinkedObjectManager, they just won't work. The next time the data is saved and reloaded, though, these links effectively get purged. To really make this a production-ready app, I feel a little more attention is needed in this area... that said, the foundation is certainly there for you to work from (just please test thoroughly for your specific situation, of course).
Now for the C# code:
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.ApplicationServices;
  6. using Autodesk.AutoCAD.DatabaseServices;
  7. using Autodesk.AutoCAD.EditorInput;
  8. using Autodesk.AutoCAD.Geometry;
  9. [assembly:
  10.   CommandClass(
  11.     typeof(
  12.       AsdkLinkingLibrary.LinkingCommands
  13.     )
  14.   )
  15. ]
  16. namespace AsdkLinkingLibrary
  17. {
  18.   /// <summary>
  19.   /// Utility class to manage and save links
  20.   /// between objects
  21.   /// </summary>
  22.   public class LinkedObjectManager
  23.   {
  24.     const string kCompanyDict =
  25.       "AsdkLinks";
  26.     const string kApplicationDict =
  27.       "AsdkLinkedObjects";
  28.     const string kXrecPrefix =
  29.       "LINKXREC";
  30.     Dictionary<ObjectId, ObjectIdCollection> m_dict;
  31.     // Constructor
  32.     public LinkedObjectManager()
  33.     {
  34.       m_dict =
  35.         new Dictionary<ObjectId,ObjectIdCollection>();
  36.     }
  37.     // Create a bi-directional link between two objects
  38.     public void LinkObjects(ObjectId from, ObjectId to)
  39.     {
  40.       CreateLink(from, to);
  41.       CreateLink(to, from);
  42.     }
  43.     // Helper function to create a one-way
  44.     // link between objects
  45.     private void CreateLink(ObjectId from, ObjectId to)
  46.     {
  47.       ObjectIdCollection existingList;
  48.       if (m_dict.TryGetValue(from, out existingList))
  49.       {
  50.         if (!existingList.Contains(to))
  51.         {
  52.           existingList.Add(to);
  53.           m_dict.Remove(from);
  54.           m_dict.Add(from, existingList);
  55.         }
  56.       }
  57.       else
  58.       {
  59.         ObjectIdCollection newList =
  60.           new ObjectIdCollection();
  61.         newList.Add(to);
  62.         m_dict.Add(from, newList);
  63.       }
  64.     }
  65.     // Remove bi-directional links from an object
  66.     public void RemoveLinks(ObjectId from)
  67.     {
  68.       ObjectIdCollection existingList;
  69.       if (m_dict.TryGetValue(from, out existingList))
  70.       {
  71.         m_dict.Remove(from);
  72.         foreach (ObjectId id in existingList)
  73.         {
  74.           RemoveFromList(id, from);
  75.         }
  76.       }
  77.     }
  78.     // Helper function to remove an object reference
  79.     // from a list (assumes the overall list should
  80.     // remain)
  81.     private void RemoveFromList(
  82.       ObjectId key,
  83.       ObjectId toremove
  84.     )
  85.     {
  86.       ObjectIdCollection existingList;
  87.       if (m_dict.TryGetValue(key, out existingList))
  88.       {
  89.         if (existingList.Contains(toremove))
  90.         {
  91.           existingList.Remove(toremove);
  92.           m_dict.Remove(key);
  93.           m_dict.Add(key, existingList);
  94.         }
  95.       }
  96.     }
  97.     // Return the list of objects linked to
  98.     // the one passed in
  99.     public ObjectIdCollection GetLinkedObjects(
  100.       ObjectId from
  101.     )
  102.     {
  103.       ObjectIdCollection existingList;
  104.       m_dict.TryGetValue(from, out existingList);
  105.       return existingList;
  106.     }
  107.     // Check whether the dictionary contains
  108.     // a particular key
  109.     public bool Contains(ObjectId key)
  110.     {
  111.       return m_dict.ContainsKey(key);
  112.     }
  113.     // Save the link information to a special
  114.     // dictionary in the database
  115.     public void SaveToDatabase(Database db)
  116.     {
  117.       Transaction tr =
  118.         db.TransactionManager.StartTransaction();
  119.       using (tr)
  120.       {
  121.         ObjectId dictId =
  122.           GetLinkDictionaryId(db, true);
  123.         DBDictionary dict =
  124.           (DBDictionary)tr.GetObject(
  125.             dictId,
  126.             OpenMode.ForWrite
  127.           );
  128.         int xrecCount = 0;
  129.         foreach (
  130.           KeyValuePair<ObjectId, ObjectIdCollection> kv
  131.           in m_dict
  132.         )
  133.         {
  134.           // Prepare the result buffer with our data
  135.           ResultBuffer rb =
  136.             new ResultBuffer(
  137.               new TypedValue(
  138.                 (int)DxfCode.SoftPointerId,
  139.                 kv.Key
  140.               )
  141.             );
  142.           int i = 1;
  143.           foreach (ObjectId id in kv.Value)
  144.           {
  145.             rb.Add(
  146.               new TypedValue(
  147.                 (int)DxfCode.SoftPointerId + i,
  148.                 id
  149.               )
  150.             );
  151.             i++;
  152.           }
  153.           // Update or create an xrecord to store the data
  154.           Xrecord xrec;
  155.           bool newXrec = false;
  156.           if (dict.Contains(
  157.                 kXrecPrefix + xrecCount.ToString()
  158.               )
  159.           )
  160.           {
  161.             // Open the existing object
  162.             DBObject obj =
  163.               tr.GetObject(
  164.                 dict.GetAt(
  165.                   kXrecPrefix + xrecCount.ToString()
  166.                 ),
  167.                 OpenMode.ForWrite
  168.               );
  169.             // Check whether it's an xrecord
  170.             xrec = obj as Xrecord;
  171.             if (xrec == null)
  172.             {
  173.               // Should never happen
  174.               // We only store xrecords in this dict
  175.               obj.Erase();
  176.               xrec = new Xrecord();
  177.               newXrec = true;
  178.             }
  179.           }
  180.           // No object existed - create a new one
  181.           else
  182.           {
  183.             xrec = new Xrecord();
  184.             newXrec = true;
  185.           }
  186.           xrec.XlateReferences = true;
  187.           xrec.Data = (ResultBuffer)rb;
  188.           if (newXrec)
  189.           {
  190.             dict.SetAt(
  191.               kXrecPrefix + xrecCount.ToString(),
  192.               xrec
  193.             );
  194.             tr.AddNewlyCreatedDBObject(xrec, true);
  195.           }
  196.           xrecCount++;
  197.         }
  198.         // Now erase the left-over xrecords
  199.         bool finished = false;
  200.         do
  201.         {
  202.           if (dict.Contains(
  203.                 kXrecPrefix + xrecCount.ToString()
  204.               )
  205.           )
  206.           {
  207.             DBObject obj =
  208.               tr.GetObject(
  209.                 dict.GetAt(
  210.                   kXrecPrefix + xrecCount.ToString()
  211.                 ),
  212.                 OpenMode.ForWrite
  213.               );
  214.             obj.Erase();
  215.           }
  216.           else
  217.           {
  218.             finished = true;
  219.           }
  220.           xrecCount++;
  221.         } while (!finished);
  222.         tr.Commit();
  223.       }
  224.     }
  225.     // Load the link information from a special
  226.     // dictionary in the database
  227.     public void LoadFromDatabase(Database db)
  228.     {
  229.       Document doc =
  230.         Application.DocumentManager.MdiActiveDocument;
  231.       Editor ed = doc.Editor;
  232.       Transaction tr =
  233.         db.TransactionManager.StartTransaction();
  234.       using (tr)
  235.       {
  236.         // Try to find the link dictionary, but
  237.         // do not create it if one isn't there
  238.         ObjectId dictId =
  239.           GetLinkDictionaryId(db, false);
  240.         if (dictId.IsNull)
  241.         {
  242.           ed.WriteMessage(
  243.             "\nCould not find link dictionary."
  244.           );
  245.           return;
  246.         }
  247.         // By this stage we can assume the dictionary exists
  248.         DBDictionary dict =
  249.           (DBDictionary)tr.GetObject(
  250.             dictId, OpenMode.ForRead
  251.           );
  252.         int xrecCount = 0;
  253.         bool done = false;
  254.         // Loop, reading the xrecords one-by-one
  255.         while (!done)
  256.         {
  257.           if (dict.Contains(
  258.                 kXrecPrefix + xrecCount.ToString()
  259.              )
  260.           )
  261.           {
  262.             ObjectId recId =
  263.               dict.GetAt(
  264.                 kXrecPrefix + xrecCount.ToString()
  265.               );
  266.             DBObject obj =
  267.               tr.GetObject(recId, OpenMode.ForRead);
  268.             Xrecord xrec = obj as Xrecord;
  269.             if (xrec == null)
  270.             {
  271.               ed.WriteMessage(
  272.                 "\nDictionary contains non-xrecords."
  273.               );
  274.               return;
  275.             }
  276.             int i = 0;
  277.             ObjectId from = new ObjectId();
  278.             ObjectIdCollection to =
  279.               new ObjectIdCollection();
  280.             foreach (TypedValue val in xrec.Data)
  281.             {
  282.               if (i == 0)
  283.                 from = (ObjectId)val.Value;
  284.               else
  285.               {
  286.                 to.Add((ObjectId)val.Value);
  287.               }
  288.               i++;
  289.             }
  290.             // Validate the link info and add it to our
  291.             // internal data structure
  292.             AddValidatedLinks(db, from, to);
  293.             xrecCount++;
  294.           }
  295.           else
  296.           {
  297.             done = true;
  298.           }
  299.         }
  300.         tr.Commit();
  301.       }
  302.     }
  303.     // Helper function to validate links before adding
  304.     // them to the internal data structure
  305.     private void AddValidatedLinks(
  306.       Database db,
  307.       ObjectId from,
  308.       ObjectIdCollection to
  309.     )
  310.     {
  311.       Document doc =
  312.         Application.DocumentManager.MdiActiveDocument;
  313.       Editor ed = doc.Editor;
  314.       Transaction tr =
  315.         db.TransactionManager.StartTransaction();
  316.       using (tr)
  317.       {
  318.         try
  319.         {
  320.           ObjectIdCollection newList =
  321.             new ObjectIdCollection();
  322.           // Open the "from" object
  323.           DBObject obj =
  324.             tr.GetObject(from, OpenMode.ForRead, false);
  325.           if (obj != null)
  326.           {
  327.             // Open each of the "to" objects
  328.             foreach (ObjectId id in to)
  329.             {
  330.               DBObject obj2;
  331.               try
  332.               {
  333.                 obj2 =
  334.                   tr.GetObject(id, OpenMode.ForRead, false);
  335.                 // Filter out the erased "to" objects
  336.                 if (obj2 != null)
  337.                 {
  338.                   newList.Add(id);
  339.                 }
  340.               }
  341.               catch (System.Exception)
  342.               {
  343.                 ed.WriteMessage(
  344.                   "\nFiltered out link to an erased object."
  345.                 );
  346.               }
  347.             }
  348.             // Only if the "from" object and at least
  349.             // one "to" object exist (and are unerased)
  350.             // do we add an entry for them
  351.             if (newList.Count > 0)
  352.             {
  353.               m_dict.Add(from, newList);
  354.             }
  355.           }
  356.         }
  357.         catch (System.Exception)
  358.         {
  359.           ed.WriteMessage(
  360.             "\nFiltered out link from an erased object."
  361.           );
  362.         }
  363.         tr.Commit();
  364.       }
  365.     }
  366.     // Helper function to get (optionally create)
  367.     // the nested dictionary for our xrecord objects
  368.     private ObjectId GetLinkDictionaryId(
  369.       Database db,
  370.       bool createIfNotExisting
  371.     )
  372.     {
  373.       ObjectId appDictId = ObjectId.Null;
  374.       Transaction tr =
  375.         db.TransactionManager.StartTransaction();
  376.       using (tr)
  377.       {
  378.         DBDictionary nod =
  379.           (DBDictionary)tr.GetObject(
  380.             db.NamedObjectsDictionaryId,
  381.             OpenMode.ForRead
  382.           );
  383.         // Our outer level ("company") dictionary
  384.         // does not exist
  385.         if (!nod.Contains(kCompanyDict))
  386.         {
  387.           if (!createIfNotExisting)
  388.             return ObjectId.Null;
  389.           // Create both the "company" dictionary...
  390.           DBDictionary compDict = new DBDictionary();
  391.           nod.UpgradeOpen();
  392.           nod.SetAt(kCompanyDict, compDict);
  393.           tr.AddNewlyCreatedDBObject(compDict, true);
  394.           // ... and the inner "application" dictionary.
  395.           DBDictionary appDict = new DBDictionary();
  396.           appDictId =
  397.             compDict.SetAt(kApplicationDict, appDict);
  398.           tr.AddNewlyCreatedDBObject(appDict, true);
  399.         }
  400.         else
  401.         {
  402.           // Our "company" dictionary exists...
  403.           DBDictionary compDict =
  404.             (DBDictionary)tr.GetObject(
  405.               nod.GetAt(kCompanyDict),
  406.               OpenMode.ForRead
  407.             );
  408.           /// So check for our "application" dictionary
  409.           if (!compDict.Contains(kApplicationDict))
  410.           {
  411.             if (!createIfNotExisting)
  412.               return ObjectId.Null;
  413.             // Create the "application" dictionary
  414.             DBDictionary appDict = new DBDictionary();
  415.             compDict.UpgradeOpen();
  416.             appDictId =
  417.               compDict.SetAt(kApplicationDict, appDict);
  418.             tr.AddNewlyCreatedDBObject(appDict, true);
  419.           }
  420.           else
  421.           {
  422.             // Both dictionaries already exist...
  423.             appDictId = compDict.GetAt(kApplicationDict);
  424.           }
  425.         }
  426.         tr.Commit();
  427.       }
  428.       return appDictId;
  429.     }
  430.   }
  431.   
  432.   /// <summary>
  433.   /// This class defines our commands and event callbacks.
  434.   /// </summary>
  435.   public class LinkingCommands
  436.   {
  437.     LinkedObjectManager m_linkManager;
  438.     ObjectIdCollection m_entitiesToUpdate;
  439.     bool m_autoLink = false;
  440.     ObjectId m_lastEntity = ObjectId.Null;
  441.     public LinkingCommands()
  442.     {
  443.       Document doc =
  444.         Application.DocumentManager.MdiActiveDocument;
  445.       Database db = doc.Database;
  446.       db.ObjectModified +=
  447.         new ObjectEventHandler(OnObjectModified);
  448.       db.ObjectErased +=
  449.         new ObjectErasedEventHandler(OnObjectErased);
  450.       db.ObjectAppended +=
  451.         new ObjectEventHandler(OnObjectAppended);
  452.       db.BeginSave +=
  453.         new DatabaseIOEventHandler(OnBeginSave);
  454.       doc.CommandEnded +=
  455.         new CommandEventHandler(OnCommandEnded);
  456.       m_linkManager = new LinkedObjectManager();
  457.       m_entitiesToUpdate = new ObjectIdCollection();
  458.     }
  459.     ~LinkingCommands()
  460.     {
  461.       try
  462.       {
  463.         Document doc =
  464.           Application.DocumentManager.MdiActiveDocument;
  465.         Database db = doc.Database;
  466.         db.ObjectModified -=
  467.           new ObjectEventHandler(OnObjectModified);
  468.         db.ObjectErased -=
  469.           new ObjectErasedEventHandler(OnObjectErased);
  470.         db.ObjectAppended -=
  471.           new ObjectEventHandler(OnObjectAppended);
  472.         db.BeginSave -=
  473.           new DatabaseIOEventHandler(OnBeginSave);
  474.         doc.CommandEnded +=
  475.           new CommandEventHandler(OnCommandEnded);
  476.       }
  477.       catch(System.Exception)
  478.       {
  479.         // The document or database may no longer
  480.         // be available on unload
  481.       }
  482.     }
  483.     // Define "LINK" command
  484.     [CommandMethod("LINK")]
  485.     public void LinkEntities()
  486.     {
  487.       Document doc =
  488.         Application.DocumentManager.MdiActiveDocument;
  489.       Database db = doc.Database;
  490.       Editor ed = doc.Editor;
  491.       PromptEntityOptions opts =
  492.         new PromptEntityOptions(
  493.           "\nSelect first circle to link: "
  494.         );
  495.       opts.AllowNone = true;
  496.       opts.SetRejectMessage(
  497.         "\nOnly circles can be selected."
  498.       );
  499.       opts.AddAllowedClass(typeof(Circle), false);
  500.       PromptEntityResult res = ed.GetEntity(opts);
  501.       if (res.Status == PromptStatus.OK)
  502.       {
  503.         ObjectId from = res.ObjectId;
  504.         opts.Message =
  505.           "\nSelect second circle to link: ";
  506.         res = ed.GetEntity(opts);
  507.         if (res.Status == PromptStatus.OK)
  508.         {
  509.           ObjectId to = res.ObjectId;
  510.           m_linkManager.LinkObjects(from, to);
  511.           m_lastEntity = to;
  512.           m_entitiesToUpdate.Add(from);
  513.         }
  514.       }
  515.     }
  516.     // Define "AUTOLINK" command
  517.     [CommandMethod("AUTOLINK")]
  518.     public void ToggleAutoLink()
  519.     {
  520.       Document doc =
  521.         Application.DocumentManager.MdiActiveDocument;
  522.       Editor ed = doc.Editor;
  523.       m_autoLink = !m_autoLink;
  524.       if (m_autoLink)
  525.       {
  526.         ed.WriteMessage("\nAutomatic linking turned on.");
  527.       }
  528.       else
  529.       {
  530.         ed.WriteMessage("\nAutomatic linking turned off.");
  531.       }
  532.     }
  533.     // Define "LOADLINKS" command
  534.     [CommandMethod("LOADLINKS")]
  535.     public void LoadLinkSettings()
  536.     {
  537.       Document doc =
  538.         Application.DocumentManager.MdiActiveDocument;
  539.       Database db = doc.Database;
  540.       m_linkManager.LoadFromDatabase(db);
  541.     }
  542.     // Define "SAVELINKS" command
  543.     [CommandMethod("SAVELINKS")]
  544.     public void SaveLinkSettings()
  545.     {
  546.       Document doc =
  547.         Application.DocumentManager.MdiActiveDocument;
  548.       Database db = doc.Database;
  549.       m_linkManager.SaveToDatabase(db);
  550.     }
  551.    
  552.     // Define callback for Database.ObjectModified event
  553.     private void OnObjectModified(
  554.       object sender, ObjectEventArgs e)
  555.     {
  556.       ObjectId id = e.DBObject.ObjectId;
  557.       if (m_linkManager.Contains(id) &&
  558.           !m_entitiesToUpdate.Contains(id))
  559.       {
  560.         m_entitiesToUpdate.Add(id);
  561.       }
  562.     }
  563.     // Define callback for Database.ObjectErased event
  564.     private void OnObjectErased(
  565.       object sender, ObjectErasedEventArgs e)
  566.     {
  567.       if (e.Erased)
  568.       {
  569.         ObjectId id = e.DBObject.ObjectId;
  570.         m_linkManager.RemoveLinks(id);
  571.         if (m_lastEntity == id)
  572.         {
  573.           m_lastEntity = ObjectId.Null;
  574.         }
  575.       }
  576.     }
  577.     // Define callback for Database.ObjectAppended event
  578.     void OnObjectAppended(object sender, ObjectEventArgs e)
  579.     {
  580.       Database db = sender as Database;
  581.       if (db != null)
  582.       {
  583.         if (m_autoLink)
  584.         {
  585.           if (e.DBObject.GetType() == typeof(Circle))
  586.           {
  587.             ObjectId from = e.DBObject.ObjectId;
  588.             if (m_lastEntity == ObjectId.Null)
  589.             {
  590.               m_lastEntity = from;
  591.             }
  592.             else
  593.             {
  594.               m_linkManager.LinkObjects(from, m_lastEntity);
  595.               m_lastEntity = from;
  596.               m_entitiesToUpdate.Add(from);
  597.             }
  598.           }
  599.         }
  600.       }
  601.     }
  602.     // Define callback for Database.BeginSave event
  603.     void OnBeginSave(object sender, DatabaseIOEventArgs e)
  604.     {
  605.       Database db = sender as Database;
  606.       if (db != null)
  607.       {
  608.         m_linkManager.SaveToDatabase(db);
  609.       }
  610.     }
  611.     // Define callback for Document.CommandEnded event
  612.     private void OnCommandEnded(
  613.       object sender, CommandEventArgs e)
  614.     {
  615.       foreach (ObjectId id in m_entitiesToUpdate)
  616.       {
  617.         UpdateLinkedEntities(id);
  618.       }
  619.       m_entitiesToUpdate.Clear();
  620.     }
  621.     // Helper function for OnCommandEnded
  622.     private void UpdateLinkedEntities(ObjectId from)
  623.     {
  624.       Document doc =
  625.         Application.DocumentManager.MdiActiveDocument;
  626.       Editor ed = doc.Editor;
  627.       Database db = doc.Database;
  628.       ObjectIdCollection linked =
  629.         m_linkManager.GetLinkedObjects(from);
  630.       Transaction tr =
  631.         db.TransactionManager.StartTransaction();
  632.       using (tr)
  633.       {
  634.         try
  635.         {
  636.           Point3d firstCenter;
  637.           Point3d secondCenter;
  638.           double firstRadius;
  639.           double secondRadius;
  640.           Entity ent =
  641.             (Entity)tr.GetObject(from, OpenMode.ForRead);
  642.           if (GetCenterAndRadius(
  643.                 ent,
  644.                 out firstCenter,
  645.                 out firstRadius
  646.               )
  647.           )
  648.           {
  649.             foreach (ObjectId to in linked)
  650.             {
  651.               Entity ent2 =
  652.                 (Entity)tr.GetObject(to, OpenMode.ForRead);
  653.               if (GetCenterAndRadius(
  654.                     ent2,
  655.                     out secondCenter,
  656.                     out secondRadius
  657.                   )
  658.               )
  659.               {
  660.                 Vector3d vec = firstCenter - secondCenter;
  661.                 if (!vec.IsZeroLength())
  662.                 {
  663.                   // Only move the linked circle if it's not
  664.                   // already near enough               
  665.                   double apart =
  666.                    vec.Length - (firstRadius + secondRadius);
  667.                   if (apart < 0.0)
  668.                     apart = -apart;
  669.                   if (apart > 0.00001)
  670.                   {
  671.                     ent2.UpgradeOpen();
  672.                     ent2.TransformBy(
  673.                       Matrix3d.Displacement(
  674.                         vec.GetNormal() * apart
  675.                       )
  676.                     );
  677.                   }
  678.                 }
  679.               }
  680.             }
  681.           }
  682.         }
  683.         catch (System.Exception ex)
  684.         {
  685.           Autodesk.AutoCAD.Runtime.Exception ex2 =
  686.             ex as Autodesk.AutoCAD.Runtime.Exception;
  687.           if (ex2 != null &&
  688.               ex2.ErrorStatus !=
  689.                 ErrorStatus.WasOpenForUndo &&
  690.               ex2.ErrorStatus !=
  691.                 ErrorStatus.WasErased
  692.           )
  693.           {
  694.             ed.WriteMessage(
  695.               "\nAutoCAD exception: {0}", ex2
  696.             );
  697.           }
  698.           else if (ex2 == null)
  699.           {
  700.             ed.WriteMessage(
  701.               "\nSystem exception: {0}", ex
  702.             );
  703.           }
  704.         }
  705.         tr.Commit();
  706.       }
  707.     }
  708.     // Helper function to get the center and radius
  709.     // for all supported circular objects
  710.     private bool GetCenterAndRadius(
  711.       Entity ent,
  712.       out Point3d center,
  713.       out double radius
  714.     )
  715.     {
  716.       // For circles it's easy...
  717.       Circle circle = ent as Circle;
  718.       if (circle != null)
  719.       {
  720.         center = circle.Center;
  721.         radius = circle.Radius;
  722.         return true;
  723.       }
  724.       else
  725.       {
  726.         // Throw in some empty values...
  727.         // Returning false indicates the object
  728.         // passed in was not useable
  729.         center = Point3d.Origin;
  730.         radius = 0.0;
  731.         return false;
  732.       }
  733.     }
  734.   }
  735. }
Let's take a quick look at this code running. Here's an existing chain that we've created using LINK. We then use AUTOLINK to toggle the automatic linking to on, and start creating circles:
















And that's it for this post. Next time we'll look at adding support for other object types, including 3D (woohoo!).
 楼主| 发表于 2009-6-12 07:50:00 | 显示全部楼层
二、过滤Windows消息
May 30, 2008
Filtering Windows messages inside AutoCAD using .NET
Back when I joined Autodesk in 1995, I worked in European Developer Support with one of the most talented programmers I've met, Markus Kraus. One of Markus' contributions to the R13 ARX SDK (or maybe it was R14?) was a sample called pretranslate, which remained on the SDK up until ObjectARX 2008, under samples/editor/mfcsamps/pretranslate (it was removed from the 2009 SDK when we archived a number of aging samples).
Anyway, with AutoCAD 2009 the API that makes this sample possible has been added to the .NET API, so in homage to Markus' original sample (which I have fond memories of demoing during a number of events around Europe), I decided to translate the original C++ sample to C#.
Here's the C# code:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.Runtime;
  3. using System.Windows.Interop;
  4. using System;
  5. namespace PreTranslate
  6. {
  7.   public class Commands
  8.   {
  9.     // Keys
  10.     const int MK_SHIFT = 4;
  11.     const int MK_CONTROL = 8;
  12.     // Keyboard messages
  13.     const int WM_KEYDOWN = 256;
  14.     const int WM_KEYUP = 257;
  15.     const int WM_CHAR = 258;
  16.     const int WM_SYSKEYDOWN = 260;
  17.     const int WM_SYSKEYUP = 261;
  18.     // Mouse messages
  19.     const int WM_MOUSEMOVE = 512;
  20.     const int WM_LBUTTONDOWN = 513;
  21.     const int WM_LBUTTONUP = 514;
  22.     static long MakeLong(int LoWord, int HiWord)
  23.     {
  24.       return (HiWord << 16) | (LoWord & 0xffff);
  25.     }
  26.     static IntPtr MakeLParam(int LoWord, int HiWord)
  27.     {
  28.       return (IntPtr)MakeLong(LoWord,HiWord);
  29.     }
  30.     static int HiWord(int Number)
  31.     {
  32.       return (Number >> 16) & 0xffff;
  33.     }
  34.     static int LoWord(int Number)
  35.     {
  36.       return Number & 0xffff;
  37.     }
  38.     // State used by the VhmouseHandler to filter on
  39.     // the vertical or the horizontal
  40.     bool vMode;
  41.     bool hMode;
  42.     int ptx;
  43.     int pty;
  44.     // Commands to add/remove message handlers
  45.     [CommandMethod("caps")]
  46.     public void Caps()
  47.     {
  48.       Application.PreTranslateMessage +=
  49.         new PreTranslateMessageEventHandler(CapsHandler);
  50.     }
  51.     [CommandMethod("uncaps")]
  52.     public void UnCaps()
  53.     {
  54.       Application.PreTranslateMessage -=
  55.         new PreTranslateMessageEventHandler(CapsHandler);
  56.     }
  57.     [CommandMethod("vhmouse")]
  58.     public void Vhmouse()
  59.     {
  60.       Application.PreTranslateMessage +=
  61.         new PreTranslateMessageEventHandler(VhmouseHandler);
  62.     }
  63.     [CommandMethod("unvhmouse")]
  64.     public void UnVhmouse()
  65.     {
  66.       Application.PreTranslateMessage -=
  67.         new PreTranslateMessageEventHandler(VhmouseHandler);
  68.     }
  69.     [CommandMethod("watchCC")]
  70.     public void WatchCC()
  71.     {
  72.       Application.PreTranslateMessage +=
  73.         new PreTranslateMessageEventHandler(WatchCCHandler);
  74.     }
  75.     [CommandMethod("unwatchCC")]
  76.     public void UnWatchCC()
  77.     {
  78.       Application.PreTranslateMessage -=
  79.         new PreTranslateMessageEventHandler(WatchCCHandler);
  80.     }
  81.     [CommandMethod("noX")]
  82.     public void NoX()
  83.     {
  84.       Application.PreTranslateMessage +=
  85.         new PreTranslateMessageEventHandler(NoXHandler);
  86.     }
  87.     [CommandMethod("yes")]
  88.     public void YesX()
  89.     {
  90.       Application.PreTranslateMessage -=
  91.         new PreTranslateMessageEventHandler(NoXHandler);
  92.     }
  93.     // The event handlers themselves...
  94.     // Force alphabetic character entry to uppercase
  95.     void CapsHandler(
  96.       object sender,
  97.       PreTranslateMessageEventArgs e
  98.     )
  99.     {
  100.       // For every lowercase character message,
  101.       // reduce it my 32 (which forces it to
  102.       // uppercase in ASCII)
  103.       if (e.Message.message == WM_CHAR &&
  104.           (e.Message.wParam.ToInt32() >= 97 &&
  105.           e.Message.wParam.ToInt32() <= 122))
  106.       {
  107.         MSG msg = e.Message;
  108.         msg.wParam =
  109.           (IntPtr)(e.Message.wParam.ToInt32() - 32);
  110.         e.Message = msg;
  111.       }
  112.     }
  113.     // Force mouse movement to either horizontal or
  114.     // vertical
  115.     void VhmouseHandler(
  116.       object sender,
  117.       PreTranslateMessageEventArgs e
  118.     )
  119.     {
  120.       // Only look at mouse messages
  121.       if (e.Message.message == WM_MOUSEMOVE ||
  122.           e.Message.message == WM_LBUTTONDOWN ||
  123.           e.Message.message == WM_LBUTTONUP)
  124.       {
  125.         // If the left mousebutton is pressed and we are
  126.         // filtering horizontal or vertical movement,
  127.         // make the position the one we're storing
  128.         if ((e.Message.message == WM_LBUTTONDOWN ||
  129.             e.Message.message == WM_LBUTTONUP)
  130.             && (vMode ||  hMode))
  131.         {
  132.           MSG msg = e.Message;
  133.           msg.lParam = MakeLParam(ptx, pty);
  134.           e.Message = msg;
  135.           return;
  136.         }
  137.         // If the Control key is pressed
  138.         if (e.Message.wParam.ToInt32() == MK_CONTROL)
  139.         {
  140.           // If we're already in "vertical" mode,
  141.           // set the horizontal component of our location
  142.           // to the one we've stored
  143.           // Otherwise we set the internal "x" value
  144.           // (as this is the first time through)
  145.           if (vMode)
  146.           {
  147.             MSG msg = e.Message;
  148.             msg.lParam =
  149.               MakeLParam(
  150.                 ptx,
  151.                 HiWord(e.Message.lParam.ToInt32())
  152.               );
  153.             e.Message = msg;
  154.             pty = HiWord(e.Message.lParam.ToInt32());
  155.           }
  156.           else
  157.             ptx = LoWord(e.Message.lParam.ToInt32());
  158.           vMode = true;
  159.           hMode = false;
  160.         }
  161.         // If the Shift key is pressed
  162.         else if (e.Message.wParam.ToInt32() == MK_SHIFT)
  163.         {
  164.           // If we're already in "horizontal" mode,
  165.           // set the vertical component of our location
  166.           // to the one we've stored
  167.           // Otherwise we set the internal "y" value
  168.           // (as this is the first time through)
  169.           if (hMode)
  170.           {
  171.             MSG msg = e.Message;
  172.             msg.lParam =
  173.               MakeLParam(
  174.                 LoWord(e.Message.lParam.ToInt32()),
  175.                 pty
  176.               );
  177.             e.Message = msg;
  178.             ptx = LoWord(e.Message.lParam.ToInt32());
  179.           }
  180.           else
  181.             pty = HiWord(e.Message.lParam.ToInt32());
  182.           hMode = true;
  183.           vMode = false;
  184.         }
  185.         else
  186.           // Something else was pressed,
  187.           // so cancel our filtering
  188.           vMode = hMode = false;
  189.       }
  190.     }
  191.     // Watch for Ctrl-C, and display a message
  192.     void WatchCCHandler(
  193.       object sender,
  194.       PreTranslateMessageEventArgs e
  195.     )
  196.     {
  197.       // Check for the Ctrl-C Windows message
  198.       if (e.Message.message == WM_CHAR &&
  199.           e.Message.wParam.ToInt32() == 3)
  200.       {
  201.         Document doc =
  202.           Application.DocumentManager.MdiActiveDocument;
  203.         doc.Editor.WriteMessage(
  204.           "\nCtrl-C is pressed"
  205.         );
  206.       }
  207.     }
  208.     // Filter out use of the letter x/X
  209.     void NoXHandler(
  210.       object sender,
  211.       PreTranslateMessageEventArgs e
  212.     )
  213.     {
  214.       // If lowercase or uppercase x is pressed,
  215.       // filter the message by setting the
  216.       // Handled property to true
  217.       if (e.Message.message == WM_CHAR &&
  218.           (e.Message.wParam.ToInt32() == 120 ||
  219.           e.Message.wParam.ToInt32() == 88))
  220.         e.Handled = true;
  221.     }
  222.   }
  223. }
To be able to use the System.Windows.Interop namespace, you'll need to add a project reference to WindowsBase.dll. Strangely this can take some finding - at least on my system it wasn't included in the base assembly list. To find it, I browsed to:
C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll
The application defines a number of commands, which are described in this text from the original sample's ReadMe:
This sample shows how to pretranslate AutoCAD messages
before they're processed by AutoCAD.
In order to pre-processe AutoCAD messages, a hook function
needs to be installed. The following commands install
different hook functions.

- vhmouse/unvhmouse
  Installs/uninstalls a hook that makes the mouse move only in a
  vertical direction if <CTRL> key is pressed, and only in a
  horizontal direction if the <SHIFT> key is pressed.

- caps/uncaps
  Installs/uninstalls a hook that capitalizes all
  letters typed in the command window.

- noX/yes
  Installs/uninstalls a hook that filters out the
  letters 'x' or 'X'.

- watchCC/unwatchCC
  Installs/uninstalls a hook that watchs
  for <CTRL>+C key combination to be pressed.
Here's what happens when we run the various commands:
  1. Command: caps
  2. Command: UNCAPS
  3. Command: vhmouse
  4. Command: unvhmouse
  5. Command: watchcc
  6. Command:
  7. Ctrl-C is pressed
  8. Command:
  9. Command: _copyclip
  10. Select objects: *Cancel*
  11. Command: nox
  12. Command: yes
复制代码
While you can't see all the effects of the various commands from this dump of the command-line, here are some comments and pointers, should you try this sample:
The Shift or Caps Lock keys was not used at all during entry of the command-names
During the vhmouse command, move the mouse around and use the Shift and Ctrl keys to force the movement to horizontal or vertical
The Ctrl key now shows an entity selection cursor in AutoCAD, so I should probably have changed to another key for this, but anyway
Ctrl-C now launches COPYCLIP, but we see the message first
The yes command should obviously be called yesx, but then we can't enter the "x" character after running the nox command. :-)
As a final note: I don't recommend filtering commonly-used keystrokes in your application - your users really won't thank you - but this fun little sample at least shows you the capabilities of the PreTranslate mechanism.

 楼主| 发表于 2009-6-12 07:59:00 | 显示全部楼层
三、多实体相互锚定
August 11, 2008
Anchoring AutoCAD entities to each other using .NET
The code in the following two posts was provided by Jeremy Tammik, from our DevTech team in Europe, who presented it at an advanced custom entity workshop he delivered recently in Prague to rave reviews. I've formatted the code to fit the blog and added some commentary plus steps to see it working. Thank you, Jeremy!
Those of you who are familiar with the workings of AutoCAD Architecture - and especially the Object Modeling Framework - will know of the very cool ability for entities to be anchored to one another. This works because graphical ACA classes derive from a "geo" class, which exposes basic location information in a generic way. While AutoCAD entities don't provide this generic location information, it is, however, possible to implement your own anchoring by depending on the location information exposed by specific classes.
The below example does just this: it takes the simple example of anchoring a circle to a line. This technique is especially useful in .NET as it allows us to build in intelligence for standard AutoCAD entities without the need to implement (much more complex) custom entities. Which is why Jeremy was showing it at a custom entity workshop. :-)
Here's the C# code:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using Autodesk.AutoCAD.ApplicationServices;
  5. using Autodesk.AutoCAD.DatabaseServices;
  6. using Autodesk.AutoCAD.EditorInput;
  7. using Autodesk.AutoCAD.Geometry;
  8. using Autodesk.AutoCAD.Runtime;
  9. namespace Anchor
  10. {
  11.   /// <summary>
  12.   /// Anchor command.
  13.   ///
  14.   /// Demonstrate a simple anchoring system.
  15.   ///
  16.   /// In OMF, an anchor is implemented as a custom object
  17.   /// which keeps track of the object ids of the host object
  18.   /// and the object anchored to the host.
  19.   ///
  20.   /// Here, we implement a simpler mechanism, which maintains
  21.   /// lists of host objects and anchored objects with mappings
  22.   /// to each other.
  23.   ///
  24.   /// The host objects are lines, and the anchored objects are
  25.   /// circles. Any number of circles can be anchored to a line,
  26.   /// but a circle can only be anchored to one line at a time.
  27.   ///
  28.   /// The main command prompts the user to select a circle to
  29.   /// anchor and a line to host it. From then on, the circle
  30.   /// will remain anchored on that line, regardsless how the
  31.   /// user tries to move either the line or the circle.
  32.   /// Currently, supported manipulations are the MOVE and
  33.   /// GRIP_STRETCH commands.
  34.   ///
  35.   /// The implementation is similar to the simpler Reactor
  36.   /// sample, and the same principles about cascaded reactors
  37.   /// apply.
  38.   ///
  39.   /// We make the command a non-static method, so that each
  40.   /// document has its own instance of the command class.
  41.   /// </summary>
  42.   public class CmdAnchor
  43.   {
  44.     static List<string> _commandNames =
  45.       new List<string>(
  46.         new string[] { "MOVE", "GRIP_STRETCH" }
  47.       );
  48.     private Document _doc;
  49.     private Dictionary<ObjectId, List<ObjectId>>
  50.       _mapHostToAnchored;
  51.     private Dictionary<ObjectId, ObjectId> _mapAnchoredToHost;
  52.     private static ObjectIdCollection _ids;
  53.     private static List<double> _pos;
  54.     Editor Ed
  55.     {
  56.       get
  57.       {
  58.         return _doc.Editor;
  59.       }
  60.     }
  61.     public CmdAnchor()
  62.     {
  63.       _doc =
  64.         Application.DocumentManager.MdiActiveDocument;
  65.       _doc.CommandWillStart +=
  66.         new CommandEventHandler(doc_CommandWillStart);
  67.       _mapHostToAnchored =
  68.         new Dictionary<ObjectId, List<ObjectId>>();
  69.       _mapAnchoredToHost =
  70.         new Dictionary<ObjectId, ObjectId>();
  71.       _ids = new ObjectIdCollection();
  72.       _pos = new List<double>();
  73.       Ed.WriteMessage(
  74.         "Anchors initialised for '{0}'. ",
  75.         _doc.Name
  76.       );
  77.     }
  78.     bool selectEntity(Type t, out ObjectId id)
  79.     {
  80.       id = ObjectId.Null;
  81.       string name = t.Name.ToLower();
  82.       string prompt =
  83.         string.Format("Please select a {0}: ", name);
  84.       string msg =
  85.         string.Format(
  86.           "Selected entity is not a {0}, please try again...",
  87.           name
  88.         );
  89.       PromptEntityOptions optEnt =
  90.         new PromptEntityOptions(prompt);
  91.       optEnt.SetRejectMessage(msg);
  92.       optEnt.AddAllowedClass(t, true);
  93.       PromptEntityResult resEnt =
  94.         Ed.GetEntity(optEnt);
  95.       if (PromptStatus.OK == resEnt.Status)
  96.       {
  97.         id = resEnt.ObjectId;
  98.       }
  99.       return !id.IsNull;
  100.     }
  101.     /// <summary>
  102.     /// Command to define an anchor between a selected host
  103.     /// line and an anchored circle.
  104.     /// </summary>
  105.     [CommandMethod("ANCHOR")]
  106.     public void Anchor()
  107.     {
  108.       ObjectId hostId, anchoredId;
  109.       if (selectEntity(typeof(Line), out hostId)
  110.         && selectEntity(typeof(Circle), out anchoredId))
  111.       {
  112.         // Check for previously stored anchors:
  113.         if (_mapAnchoredToHost.ContainsKey(anchoredId))
  114.         {
  115.           Ed.WriteMessage("Previous anchor removed.");
  116.           ObjectId oldHostId =
  117.             _mapAnchoredToHost[anchoredId];
  118.           _mapAnchoredToHost.Remove(anchoredId);
  119.           _mapHostToAnchored[oldHostId].Remove(anchoredId);
  120.         }
  121.         // Add new anchor data:
  122.         if (!_mapHostToAnchored.ContainsKey(hostId))
  123.         {
  124.           _mapHostToAnchored[hostId] =
  125.             new List<ObjectId>();
  126.         }
  127.         _mapHostToAnchored[hostId].Add(anchoredId);
  128.         _mapAnchoredToHost.Add(anchoredId, hostId);
  129.         // Ensure that anchored object is located on host:
  130.         Transaction t =
  131.           _doc.Database.TransactionManager.StartTransaction();
  132.         using (t)
  133.         {
  134.           Line line =
  135.             t.GetObject(hostId, OpenMode.ForRead)
  136.               as Line;
  137.           Circle circle =
  138.             t.GetObject(anchoredId, OpenMode.ForWrite)
  139.               as Circle;
  140.           Point3d ps = line.StartPoint;
  141.           Point3d pe = line.EndPoint;
  142.           LineSegment3d segment =
  143.             new LineSegment3d(ps, pe);
  144.           Point3d p = circle.Center;
  145.           circle.Center =
  146.             segment.GetClosestPointTo(p).Point;
  147.           t.Commit();
  148.         }
  149.       }
  150.     }
  151.     void doc_CommandWillStart(
  152.       object sender,
  153.       CommandEventArgs e
  154.     )
  155.     {
  156.       if (_commandNames.Contains(e.GlobalCommandName))
  157.       {
  158.         _ids.Clear();
  159.         _pos.Clear();
  160.         _doc.Database.ObjectOpenedForModify +=
  161.           new ObjectEventHandler(_db_ObjectOpenedForModify);
  162.         _doc.CommandCancelled +=
  163.           new CommandEventHandler(_doc_CommandEnded);
  164.         _doc.CommandEnded +=
  165.           new CommandEventHandler(_doc_CommandEnded);
  166.         _doc.CommandFailed +=
  167.           new CommandEventHandler(_doc_CommandEnded);
  168.       }
  169.     }
  170.     void removeEventHandlers()
  171.     {
  172.       _doc.CommandCancelled -=
  173.         new CommandEventHandler(_doc_CommandEnded);
  174.       _doc.CommandEnded -=
  175.         new CommandEventHandler(_doc_CommandEnded);
  176.       _doc.CommandFailed -=
  177.         new CommandEventHandler(_doc_CommandEnded);
  178.       _doc.Database.ObjectOpenedForModify -=
  179.         new ObjectEventHandler(_db_ObjectOpenedForModify);
  180.     }
  181.     void _doc_CommandEnded(
  182.       object sender,
  183.       CommandEventArgs e
  184.     )
  185.     {
  186.       // Remove database reactor before restoring positions
  187.       removeEventHandlers();
  188.       rollbackLocations();
  189.     }
  190.     void saveLocation(
  191.       ObjectId hostId,
  192.       ObjectId anchoredId,
  193.       bool hostModified
  194.     )
  195.     {
  196.       if (!_ids.Contains(anchoredId))
  197.       {
  198.         // If the host was moved, remember the location of
  199.         // the anchored object on the host so we can restore
  200.         // it.
  201.         // If the anchored object was moved, we do not need
  202.         // to remember its location of the anchored object,
  203.         // because we will simply snap back the the host
  204.         // afterwards.
  205.         double a = double.NaN;
  206.         if (hostModified)
  207.         {
  208.           Transaction t =
  209.             _doc.Database.TransactionManager.StartTransaction();
  210.           using (t)
  211.           {
  212.             Line line =
  213.               t.GetObject(hostId, OpenMode.ForRead)
  214.                 as Line;
  215.             Circle circle =
  216.               t.GetObject(anchoredId, OpenMode.ForRead)
  217.                 as Circle;
  218.             {
  219.               Point3d p = circle.Center;
  220.               Point3d ps = line.StartPoint;
  221.               Point3d pe = line.EndPoint;
  222.               double lineLength = ps.DistanceTo(pe);
  223.               double circleOffset = ps.DistanceTo(p);
  224.               a = circleOffset / lineLength;
  225.             }
  226.             t.Commit();
  227.           }
  228.         }
  229.         _ids.Add(anchoredId);
  230.         _pos.Add(a);
  231.       }
  232.     }
  233.     void _db_ObjectOpenedForModify(
  234.       object sender,
  235.       ObjectEventArgs e
  236.     )
  237.     {
  238.       ObjectId id = e.DBObject.Id;
  239.       if (_mapAnchoredToHost.ContainsKey(id))
  240.       {
  241.         Debug.Assert(
  242.           e.DBObject is Circle,
  243.           "Expected anchored object to be a circle"
  244.         );
  245.         saveLocation(_mapAnchoredToHost[id], id, false);
  246.       }
  247.       else if (_mapHostToAnchored.ContainsKey(id))
  248.       {
  249.         Debug.Assert(
  250.           e.DBObject is Line,
  251.           "Expected host object to be a line"
  252.         );
  253.         foreach (ObjectId id2 in _mapHostToAnchored[id])
  254.         {
  255.           saveLocation(id, id2, true);
  256.         }
  257.       }
  258.     }
  259.     void rollbackLocations()
  260.     {
  261.       Debug.Assert(
  262.         _ids.Count == _pos.Count,
  263.         "Expected same number of ids and locations"
  264.       );
  265.       Transaction t =
  266.         _doc.Database.TransactionManager.StartTransaction();
  267.       using (t)
  268.       {
  269.         int i = 0;
  270.         foreach (ObjectId id in _ids)
  271.         {
  272.           Circle circle =
  273.             t.GetObject(id, OpenMode.ForWrite)
  274.               as Circle;
  275.           Line line =
  276.             t.GetObject(
  277.               _mapAnchoredToHost[id],
  278.               OpenMode.ForRead
  279.             ) as Line;
  280.           Point3d ps = line.StartPoint;
  281.           Point3d pe = line.EndPoint;
  282.           double a = _pos[i++];
  283.           if (a.Equals(double.NaN))
  284.           {
  285.             LineSegment3d segment =
  286.               new LineSegment3d(ps, pe);
  287.             Point3d p = circle.Center;
  288.             circle.Center =
  289.               segment.GetClosestPointTo(p).Point;
  290.           }
  291.           else
  292.           {
  293.             circle.Center = ps + a * (pe - ps);
  294.           }
  295.         }
  296.         t.Commit();
  297.       }
  298.     }
  299.   }
  300. }
To see how it works, draw a circle and a line:




Call the ANCHOR command, selecting the line and then the circle. The circle gets moved such that its centre is at the closest point on the line:




Now we can grip-stretch the top point on the line:




And the circle snaps back onto the line:




If we grip-stretch the circle away from the line...




And the circle then snaps back to be on the line, but at a different point (the closest to the one selected):




If you're interested in other posts demonstrating the use of reactors to anchor entities, this series of posts shows how to link circles together: Linking Circles Parts 1, 2, 3, 4 & 5.
 楼主| 发表于 2009-6-12 08:06:00 | 显示全部楼层
四、撤销AutoCad命令对特定实体的影响
August 13, 2008
Rolling back the effect of AutoCAD commands using .NET
Another big thank you to Jeremy Tammik, from our DevTech team in Europe, for providing this elegant sample. This is another one Jeremy presented at the recent advanced custom entity workshop in Prague. I have added some initial commentary as well as some steps to see the code working. Jeremy also provided the code for the last post.
We sometimes want to stop entities from being modified in certain ways, and there are a few different approaches possible, for instance: at the simplest - and least granular - level, we can place entities on locked layers or veto certain commands using an editor reactor.  Or we can go all-out and implement custom objects that have complete control over their behaviour. The below technique provides a nice balance between control and simplicity: it makes use of a Document event to check when a particular command is being called, a Database event to cache the information we wish to restore and finally another Document event to restore it. In this case it's all about location (or should I say "location, location, location" ? :-). We're caching an object's state before the MOVE command (which changes an object's position in the model), but if we wanted to roll back the effect of other commands, we would probably want to cache other properties.
Here's the C# code:
  1. using System.Diagnostics;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.Geometry;
  5. using Autodesk.AutoCAD.Runtime;
  6. namespace Reactor
  7. {
  8.   /// <summary>
  9.   /// Reactor command.
  10.   ///
  11.   /// Demonstrate a simple object reactor, as well as
  12.   /// cascaded event handling.
  13.   ///
  14.   /// In this sample, the MOVE command is cancelled for
  15.   /// all red circles. This is achieved by attaching an
  16.   /// editor reactor and watching for the MOVE command begin.
  17.   /// When triggered, the reactor attaches an object reactor
  18.   /// to the database and watches for red circles. If any are
  19.   /// detected, their object id and original position are
  20.   /// stored. When the command ends, the positions are
  21.   /// restored and the object reactor removed again.
  22.   ///
  23.   /// Reactors create overhead, so we should add them only
  24.   /// when needed and remove them as soon as possible
  25.   /// afterwards.
  26.   /// </summary>
  27.   public class CmdReactor
  28.   {
  29.     private static Document _doc;
  30.     private static ObjectIdCollection _ids =
  31.       new ObjectIdCollection();
  32.     private static Point3dCollection _pts =
  33.       new Point3dCollection();
  34.     [CommandMethod("REACTOR")]
  35.     static public void Reactor()
  36.     {
  37.       _doc =
  38.         Application.DocumentManager.MdiActiveDocument;
  39.       _doc.CommandWillStart +=
  40.         new CommandEventHandler(doc_CommandWillStart);
  41.     }
  42.     static void doc_CommandWillStart(
  43.       object sender,
  44.       CommandEventArgs e
  45.     )
  46.     {
  47.       if (e.GlobalCommandName == "MOVE")
  48.       {
  49.         _ids.Clear();
  50.         _pts.Clear();
  51.         _doc.Database.ObjectOpenedForModify +=
  52.           new ObjectEventHandler(_db_ObjectOpenedForModify);
  53.         _doc.CommandCancelled +=
  54.           new CommandEventHandler(_doc_CommandEnded);
  55.         _doc.CommandEnded +=
  56.           new CommandEventHandler(_doc_CommandEnded);
  57.         _doc.CommandFailed +=
  58.           new CommandEventHandler(_doc_CommandEnded);
  59.       }
  60.     }
  61.     static void removeEventHandlers()
  62.     {
  63.       _doc.CommandCancelled -=
  64.         new CommandEventHandler(_doc_CommandEnded);
  65.       _doc.CommandEnded -=
  66.         new CommandEventHandler(_doc_CommandEnded);
  67.       _doc.CommandFailed -=
  68.         new CommandEventHandler(_doc_CommandEnded);
  69.       _doc.Database.ObjectOpenedForModify -=
  70.         new ObjectEventHandler(_db_ObjectOpenedForModify);
  71.     }
  72.     static void _doc_CommandEnded(
  73.       object sender,
  74.       CommandEventArgs e
  75.     )
  76.     {
  77.       // Remove database reactor before restoring positions
  78.       removeEventHandlers();
  79.       rollbackLocations();
  80.     }
  81.     static void _db_ObjectOpenedForModify(
  82.       object sender,
  83.       ObjectEventArgs e
  84.     )
  85.     {
  86.       Circle circle = e.DBObject as Circle;
  87.       if (null != circle && 1 == circle.ColorIndex)
  88.       {
  89.         // In AutoCAD 2007, OpenedForModify is called only
  90.         // once by MOVE.
  91.         // In 2008, OpenedForModify is called multiple
  92.         // times by the MOVE command ... we are only
  93.         // interested in the first call, because
  94.         // in the second one, the object location
  95.         // has already been changed:
  96.         if (!_ids.Contains(circle.ObjectId))
  97.         {
  98.           _ids.Add(circle.ObjectId);
  99.           _pts.Add(circle.Center);
  100.         }
  101.       }
  102.     }
  103.     static void rollbackLocations()
  104.     {
  105.       Debug.Assert(
  106.         _ids.Count == _pts.Count,
  107.         "Expected same number of ids and locations"
  108.       );
  109.       Transaction t =
  110.         _doc.Database.TransactionManager.StartTransaction();
  111.       using (t)
  112.       {
  113.         int i = 0;
  114.         foreach (ObjectId id in _ids)
  115.         {
  116.           Circle circle =
  117.             t.GetObject(id, OpenMode.ForWrite) as Circle;
  118.           circle.Center = _pts[i++];
  119.         }
  120.         t.Commit();
  121.       }
  122.     }
  123.   }
  124. }
To see the code at work, draw some circles and make some of them red:




Now run the REACTOR command and try to MOVE all the circles:




Although all the circles are dragged during the move, once we complete the command we can see that the red circles have remained in the same location (or have, in fact, had their location rolled back). The other circles have been moved, as expected.



 楼主| 发表于 2009-6-12 08:17:00 | 显示全部楼层
五、阻止特定图块的炸开
August 18, 2008
Preventing an AutoCAD block from being exploded using .NET
In response to these recent posts, I received a comment from Nick:
By any chance would it be possible to provide an example to prevent a user from using the EXPLODE command for a given block name?
I delved into the ADN knowledgebase and came across this helpful ObjectARX DevNote, which I used to create a .NET module to address the above question.
Here's the C# code, which should contain enough comments to make it self-explanatory:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. namespace ExplosionPrevention
  6. {
  7.   public class Commands
  8.   {
  9.     private Document _doc;
  10.     private Database _db;
  11.     private ObjectIdCollection _blkDefs =
  12.       new ObjectIdCollection();
  13.     private ObjectIdCollection _blkRefs =
  14.       new ObjectIdCollection();
  15.     private ObjectIdCollection _blkConts =
  16.       new ObjectIdCollection();
  17.     private bool _handlers = false;
  18.     private bool _exploding = false;
  19.     [CommandMethod("STOPEX")]
  20.     public void StopBlockFromExploding()
  21.     {
  22.       _doc =
  23.         Application.DocumentManager.MdiActiveDocument;
  24.       _db = _doc.Database;
  25.       if (!_handlers)
  26.       {
  27.         AddEventHandlers();
  28.         _handlers = true;
  29.       }
  30.       // Get the name of the block to protect
  31.       PromptStringOptions pso =
  32.         new PromptStringOptions(
  33.           "\nEnter block name: "
  34.         );
  35.       pso.AllowSpaces = false;
  36.       PromptResult pr =
  37.         _doc.Editor.GetString(pso);
  38.       if (pr.Status != PromptStatus.OK)
  39.         return;
  40.       Transaction tr =
  41.         _db.TransactionManager.StartTransaction();
  42.       using (tr)
  43.       {
  44.         // Make sure the block definition exists
  45.         BlockTable bt =
  46.           (BlockTable)
  47.             tr.GetObject(
  48.               _db.BlockTableId,
  49.               OpenMode.ForRead
  50.             );
  51.         if (bt.Has(pr.StringResult))
  52.         {
  53.           // Collect information about the block...
  54.           // 1. the block definition
  55.           ObjectId blkId =
  56.             bt[pr.StringResult];
  57.           _blkDefs.Add(blkId);
  58.           BlockTableRecord btr =
  59.             (BlockTableRecord)
  60.               tr.GetObject(
  61.                 blkId,
  62.                 OpenMode.ForRead
  63.               );
  64.           // 2. the block's contents
  65.           foreach (ObjectId id in btr)
  66.             _blkConts.Add(id);
  67.           // 3. the block's references
  68.           ObjectIdCollection blkRefs =
  69.             btr.GetBlockReferenceIds(true, true);
  70.           foreach (ObjectId id in blkRefs)
  71.             _blkRefs.Add(id);
  72.         }
  73.         tr.Commit();
  74.       }
  75.     }
  76.     private void AddEventHandlers()
  77.     {
  78.       // When a block reference is added, we need to
  79.       // check whether it's for a block we care about
  80.       // and add it to the list, if so
  81.       _db.ObjectAppended +=
  82.         delegate(object sender, ObjectEventArgs e)
  83.         {
  84.           BlockReference br =
  85.             e.DBObject as BlockReference;
  86.           if (br != null)
  87.           {
  88.             if (_blkDefs.Contains(br.BlockTableRecord))
  89.               _blkRefs.Add(br.ObjectId);
  90.           }
  91.         };
  92.       // Conversely we need to remove block references
  93.       // that as they're erased
  94.       _db.ObjectErased +=
  95.         delegate(object sender, ObjectErasedEventArgs e)
  96.         {
  97.           // This is called during as part of the cloning
  98.           // process, so let's check that's not happening
  99.           if (!_exploding)
  100.           {
  101.             BlockReference br =
  102.               e.DBObject as BlockReference;
  103.             if (br != null)
  104.             {
  105.               // If we're erasing, remove this block
  106.               // reference from the list, otherwise if
  107.               // we're unerasing we will want to add it
  108.               // back in
  109.               if (e.Erased)
  110.               {
  111.                 if (_blkRefs.Contains(br.ObjectId))
  112.                   _blkRefs.Remove(br.ObjectId);
  113.               }
  114.               else
  115.               {
  116.                 if (_blkDefs.Contains(br.BlockTableRecord))
  117.                   _blkRefs.Add(br.ObjectId);
  118.               }
  119.             }
  120.           }
  121.         };
  122.       // This is where we fool AutoCAD into thinking the
  123.       // block contents have already been cloned
  124.       _db.BeginDeepClone +=
  125.         delegate(object sender, IdMappingEventArgs e)
  126.         {
  127.           // Only for the explode context
  128.           if (e.IdMapping.DeepCloneContext !=
  129.               DeepCloneType.Explode)
  130.             return;
  131.           // We add IDs to the map to stop the
  132.           // block contents from being cloned
  133.           foreach (ObjectId id in _blkConts)
  134.             e.IdMapping.Add(
  135.               new IdPair(id, id, true, true, true)
  136.             );
  137.         };
  138.       // And this is where we remove the mapping entries
  139.       _db.BeginDeepCloneTranslation +=
  140.         delegate(object sender, IdMappingEventArgs e)
  141.         {
  142.           // Only for the explode context
  143.           if (e.IdMapping.DeepCloneContext !=
  144.               DeepCloneType.Explode)
  145.             return;
  146.           // Set the flag for our CommandEnded handler
  147.           _exploding = true;
  148.           // Remove the entries we added on BeginDeepClone
  149.           foreach (ObjectId id in _blkConts)
  150.             e.IdMapping.Delete(id);
  151.         };
  152.       // As the command ends we unerase the block references
  153.       _doc.CommandEnded +=
  154.         delegate(object sender, CommandEventArgs e)
  155.         {
  156.           if (e.GlobalCommandName == "EXPLODE" && _exploding)
  157.           {
  158.             // By this point the block contents should not have
  159.             // been cloned, but the blocks have been erased
  160.             Transaction tr =
  161.               _db.TransactionManager.StartTransaction();
  162.             using (tr)
  163.             {
  164.               // So we need to unerase each of the erased
  165.               // block references
  166.               foreach (ObjectId id in _blkRefs)
  167.               {
  168.                 DBObject obj =
  169.                   tr.GetObject(
  170.                     id,
  171.                     OpenMode.ForRead,
  172.                     true
  173.                   );
  174.                 // Only unerase it if it's needed
  175.                 if (obj.IsErased)
  176.                 {
  177.                   obj.UpgradeOpen();
  178.                   obj.Erase(false);
  179.                 }
  180.               }
  181.               tr.Commit();
  182.             }
  183.             _exploding = false;
  184.           }
  185.         };
  186.     }
  187.   }
  188. }
The STOPEX command takes a block name and then gathers (and stores) information about a block: its ObjectId, the IDs of its contents and its various block references. I've added some logic to handle creation of new block references (e.g. via INSERT), and erasure of ones that are no longer needed. I haven't put anything in to deal with redefinition of blocks (if the contents of blocks change then explosion may not be prevented properly), but this is left as an exercise for the reader.
Let's define and insert a series of three blocks: LINES, ARCS and CIRCLES (no prizes for guessing which is which :-):
Now we run the STOPEX command on the LINES and CIRCLES blocks:
  1. Command: STOPEX
  2. Enter block name: circles
  3. Command: STOPEX
  4. Enter block name: lines
  5. Command: EXPLODE
  6. Select objects: all
  7. 9 found
  8. Select objects:
  9. Command: Specify opposite corner:
复制代码
Selecting the "exploded" blocks, we see that only the ARCS blocks have actually been exploded:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-6-13 16:37:00 | 显示全部楼层
六、使用任务对话框[Task Dialogs]
November 10, 2008
Implementing task dialogs inside AutoCAD using .NET - Part 1
This is a topic I've been meaning to get to for some time... as I finally had to research it for a side project I'm working on, I decided to go ahead and post my findings here.
AutoCAD 2009 makes heavy use of task dialogs, which are basically message-boxes on steroids. MSDN contains documentation on Microsoft's implementation of task dialogs, although our implementation is a little different.
Why bother with these new task dialogs? They provide a way of asking more user-friendly questions using actual actions as answers rather than yes/no/cancel etc. It's a bit like the way I now often seem to be asked questions such as "who packed this bag?" when flying, these days, rather than "did you pack this bag yourself?" - you have to think a little more, but that usually increases the chances of getting accurate information. Alright, perhaps the airline security analogy wasn't all that appropriate, after all, but hopefully you get the idea.
In this first part of the two-part series we're going to look at the basic steps to add task dialogs to our projects, showing a very basic example that gives an idea of their capabilities, and in the next part we'll see a much more concrete, real-world implementation example.
The first thing to do to make use of task dialogs in your application is to add a reference to the AdWindows.dll assembly. You should find it in AutoCAD 2009 (but not previous versions), in the root application folder. As usual when adding AutoCAD's managed assemblies to your project, remember to set the "Copy Local" flag to false, to avoid problems later. Once we've added this reference we're able to use the Autodesk.Windows namespace (not to be confused with Autodesk.AutoCAD.Windows, which is AutoCAD-specific).
Here's our initial C# code to show a very basic task dialog, without doing very much with the information provided:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.EditorInput;
  3. using Autodesk.AutoCAD.Runtime;
  4. using Autodesk.Windows;
  5. namespace TaskDialogs
  6. {
  7.   public class Commands
  8.   {
  9.     [CommandMethod("Task1")]
  10.     public static void TestingTaskDialogOptions()
  11.     {
  12.       Editor ed =
  13.         Application.DocumentManager.MdiActiveDocument.Editor;
  14.       // Create the task dialog itself
  15.       TaskDialog td = new TaskDialog();
  16.       // Set the various textual settings
  17.       td.WindowTitle = "The title";
  18.       td.MainInstruction = "Something has happened.";
  19.       td.ContentText =
  20.         "Here's some text, with a " +
  21.         "<A HREF="http://adn.autodesk.com">" +
  22.         "link to the ADN site</A>";
  23.       td.VerificationText = "Verification text";
  24.       td.FooterText =
  25.         "The footer with a "+
  26.         "<A HREF="http://blogs.autodesk.com/through" +
  27.         "-the-interface">link to Kean's blog</A>";
  28.       td.EnableHyperlinks = true;
  29.       td.EnableVerificationHandler = true;
  30.       // And those for collapsed/expanded text
  31.       td.CollapsedControlText =
  32.         "This control text can be expanded.";
  33.       td.ExpandedControlText
  34.         = "This control text has been expanded..." +
  35.           "\nTo span multiple lines.";
  36.       td.ExpandedText = "This footer text has been expanded.";
  37.       td.ExpandFooterArea = true;
  38.       td.ExpandedByDefault = false;
  39.       // Set some standard icons and display of the progress bar
  40.       td.MainIcon = TaskDialogIcon.Shield;
  41.       td.FooterIcon = TaskDialogIcon.Information;
  42.       td.ShowProgressBar = true;
  43.       // A marquee progress bas just loops,
  44.       // it has no range  fixed upfront
  45.       //td.ShowMarqueeProgressBar = true;
  46.       // Now we add out task action buttons
  47.       td.UseCommandLinks = true;
  48.       td.Buttons.Add(
  49.         new TaskDialogButton(
  50.           1,
  51.           "This is one course of action."
  52.         )
  53.       );
  54.       td.Buttons.Add(
  55.         new TaskDialogButton(
  56.           2,
  57.           "Here is another course of action."
  58.         )
  59.       );
  60.       td.Buttons.Add(
  61.         new TaskDialogButton(
  62.           3,
  63.           "And would you believe we have a third!"
  64.         )
  65.       );
  66.       // Set the default to be the third
  67.       td.DefaultButton = 3;
  68.       // And some radio buttons, too
  69.       td.RadioButtons.Add(new TaskDialogButton(4, "Yes"));
  70.       td.RadioButtons.Add(new TaskDialogButton(5, "No"));
  71.       td.RadioButtons.Add(new TaskDialogButton(6, "Maybe"));
  72.       // Set the default to be the second
  73.       td.DefaultRadioButton = 5;
  74.       // Allow the dialog to be cancelled
  75.       td.AllowDialogCancellation = false;
  76.       // Implement a callback for UI event notification
  77.       td.Callback =
  78.           delegate(
  79.             ActiveTaskDialog atd,
  80.             TaskDialogEventArgs e,
  81.             object sender
  82.           )
  83.           {
  84.             ed.WriteMessage(
  85.               "\nButton ID: {0}",
  86.               e.ButtonId
  87.             );
  88.             ed.WriteMessage(
  89.               "\nNotification: {0}",
  90.               e.Notification
  91.             );
  92.             if (e.Notification ==
  93.               TaskDialogNotification.VerificationClicked)
  94.             {
  95.               atd.SetProgressBarRange(0, 100);
  96.               atd.SetProgressBarPosition(80);
  97.             }
  98.             else if (e.Notification ==
  99.               TaskDialogNotification.HyperlinkClicked)
  100.             {
  101.               ed.WriteMessage(" " + e.Hyperlink);
  102.             }
  103.             ed.WriteMessage("\n");
  104.             // Returning true will prevent the dialog from
  105.             // being closed
  106.             return false;
  107.           };
  108.       td.Show(Application.MainWindow.Handle);
  109.     }
  110.   }
  111. }
When we execute the TASK1 command, we can see this dialog gets displayed:

This dialog exercises most of the UI options available in the TaskDialog class. You'll see a progress bar (which gets modified via the ActiveTaskDialog passed into the notification callback - try checking the "Verification text" box to see that happen) as well as option buttons, action buttons and some expandable text.
Here's how the dialog looks with the text expanded and the verification checkbox ticked:

It's worth playing around with the code to see how the notification is provided to the application, and how the ActiveTaskDialog can be used from there to modify the state of the dialog. One thing you'll notice is that the hyperlinks don't automatically result in a browser being launched, which overall is a good thing, as the links can then be set up to result in other actions (the application has all the information it needs to know what was clicked in the notification callback - if it wants to launch a browser to the clicked URL, it is free to do so).
Now that we've explored the various options open to us, in the next post we'll look at a more real-world example of using a task dialog to help manage potentially time-consuming application behaviour.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-6-13 16:41:00 | 显示全部楼层
November 14, 2008
Implementing task dialogs inside AutoCAD using .NET - Part 2
In this previous post we looked at a basic task dialog inside AutoCAD and exercised its various capabilities without showing how they might be used in a real application. This post goes beyond that to show how you might make use of the TaskDialog class to provide your users with relevant information at runtime that helps them decide how best to proceed in certain situations, effectively increasing your application's usability.
The specific scenario is this: if the user selects a lot of entities - too many for our command to handle quickly - we want to show a dialog that allows the user to decide whether to proceed anyway, to select fewer entities or to cancel completely from the command. For the sake of argument we're going to set a threshold of 1,000 entities being "a lot", although it may well be that for your various operations 100 or 100,000 is a better number).
Here's some C# code that asks the user to select entities and then either performs an operation on them or presents a number of options to the user:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.EditorInput;
  3. using Autodesk.AutoCAD.Runtime;
  4. using Autodesk.Windows;
  5. namespace TaskDialogs
  6. {
  7.   public class Commands
  8.   {
  9.     // In a real application we would retrieve this
  10.     // persistent setting from somewhere
  11.     static bool neveragain = false;
  12.     enum DecisionOptions
  13.     {
  14.       DoOperation,
  15.       Reselect,
  16.       Cancel
  17.     };
  18.     [CommandMethod("Task2")]
  19.     public static void MoreRealisticTask()
  20.     {
  21.       Editor ed =
  22.         Application.DocumentManager.MdiActiveDocument.Editor;
  23.       bool done = false;
  24.       while (!done)
  25.       {
  26.         PromptSelectionResult psr = ed.GetSelection();
  27.         if (psr.Status != PromptStatus.OK)
  28.           done = true;
  29.         else
  30.         {
  31.           DecisionOptions decision = DecisionOptions.DoOperation;
  32.           if (psr.Value.Count > 1000 && !neveragain)
  33.           {
  34.             TaskDialog td = new TaskDialog();
  35.             td.Width = 200;
  36.             td.WindowTitle =
  37.               "Many objects selected";
  38.             td.MainInstruction =
  39.               psr.Value.Count.ToString() +
  40.               " objects have been selected, " +
  41.               "which may lead to a time-consuming operation.";
  42.             td.UseCommandLinks = true;
  43.             td.Buttons.Add(
  44.               new TaskDialogButton(
  45.                 0,
  46.                 "Perform the operation on the selected objects."
  47.               )
  48.             );
  49.             td.Buttons.Add(
  50.               new TaskDialogButton(
  51.                 1,
  52.                 "Reselect objects to edit"
  53.               )
  54.             );
  55.             td.Buttons.Add(
  56.               new TaskDialogButton(
  57.                 2,
  58.                 "Do nothing and cancel the command"
  59.               )
  60.             );
  61.             td.VerificationText =
  62.               "Do not ask me this again";
  63.             td.Callback =
  64.               delegate(
  65.                 ActiveTaskDialog atd,
  66.                 TaskDialogEventArgs e,
  67.                 object sender
  68.               )
  69.               {
  70.                 if (e.Notification ==
  71.                       TaskDialogNotification.ButtonClicked)
  72.                 {
  73.                   switch (e.ButtonId)
  74.                   {
  75.                     case 0:
  76.                       decision = DecisionOptions.DoOperation;
  77.                       break;
  78.                     case 1:
  79.                       decision = DecisionOptions.Reselect;
  80.                       break;
  81.                     case 2:
  82.                       decision = DecisionOptions.Cancel;
  83.                       break;
  84.                   }
  85.                 }
  86.                 if (e.Notification ==
  87.                       TaskDialogNotification.VerificationClicked)
  88.                 {
  89.                   neveragain = true;
  90.                 }
  91.                 return false;
  92.               };
  93.             td.Show(Application.MainWindow.Handle);
  94.           }
  95.           switch (decision)
  96.           {
  97.             case DecisionOptions.DoOperation:
  98.               // Perform the operation anyway...
  99.               ed.WriteMessage(
  100.                 "\nThis is where we do something " +
  101.                 "with our {0} entities.",
  102.                 psr.Value.Count
  103.               );
  104.               done = true;
  105.               break;
  106.             case DecisionOptions.Reselect:
  107.               done = false;
  108.               break;
  109.             // Includes DecisionOptions.Cancel...
  110.             default:
  111.               done = true;
  112.               break;
  113.           }
  114.         }
  115.       }
  116.       // This is where we would store the value of neveragain
  117.       // in our persistent application settings
  118.     }
  119.   }
  120. }
Here's what happens when we run the TASK2 command and select 2,500 entities:

The behaviour should be pretty predictable - you can reselect again and again, until you either:
Go ahead and perform the operation (action 1)
Reselect fewer than the threshold of 1000 entities (action 2)
Give up and cancel the command (action 3)
Decide not to be asked the question again (the checkbox at the bottom)
If you select the checkbox and reselect (action 2), for instance, you won't then be asked the question again during that session, even if you go ahead and select 100,000 entities. One thing the application doesn't do is to save the value of this setting - you would need to store it somewhere in your application settings (probably in the Registry or a configuration file of some kind), and ideally provide the user with some ability to re-enable the task dialog, should they decide it's a good idea to have it, after all.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-6-13 16:52:00 | 显示全部楼层
七、在选项对话框中加入自定义项目
November 19, 2008
Adding a custom tab to AutoCAD's options dialog using .NET - Part 1
A big thanks to Viru Aithal, from our DevTech India team, for providing the code that inspired this post.
Update: it turns out I didn't look deeply enough into the origins of the code behind this post. The code that inspired Viru's code that inspired mine came from our old friend Mike Tuersley, who's delivering a class on customizing the Options dialog at this year's AU (in just over a week). Thanks, Mike! :-)
One way that applications often want to integrate with AutoCAD is via the dialog displayed by the OPTIONS command. Luckily it's relatively easy to add your own custom tab to this dialog using .NET, allowing users to view and modify your application's settings. I'm going to take a similar approach to this topic as I did with task dialogs: part 1 (this post) focuses on a very simple implementation to show the basic capabilities of the mechanism, and part 2 will look at a more "real world" implementation that goes as far as providing access to and storing some actual (well, realistic, at least) application settings.
The first step when implementing your custom tab is to add a User Control to your project. As this activity goes beyond just copy & pasting code, here's a sample project demonstrating the technique shown in this post.
Once you have a User Control in your project (ours has the default name UserControl1), you can design it to add in some custom controls - whether buttons, checkboxes, combos, or more complex controls such as a property grid (something I'll show in the next post). Here's the design I settled for, containing just a few, simple controls, each added with its default name:

You can clearly adjust the layout, as you wish, with anchor points, docking, etc. It's worth setting the "modifier" for each of the controls you've added to public or internal, assuming you want to access their state directly from elsewhere in the project. The other way is to expose them via public properties, but that's really up to you.
Here's the code that goes behind this dialog, the important activity being to set the "dirty" flag for the control when a particular component control is selected:
  1. using System;
  2. using System.Windows.Forms;
  3. using Autodesk.AutoCAD.ApplicationServices;
  4. namespace OptionsDlg
  5. {
  6.   public partial class UserControl1 : UserControl
  7.   {
  8.     public UserControl1()
  9.     {
  10.       InitializeComponent();
  11.     }
  12.     private void button1_Click(
  13.       object sender,
  14.       EventArgs e
  15.     )
  16.     {
  17.       TabbedDialogExtension.SetDirty(this, true);
  18.     }
  19.     private void checkBox1_CheckedChanged(
  20.       object sender,
  21.       EventArgs e
  22.     )
  23.     {
  24.       TabbedDialogExtension.SetDirty(this, true);
  25.     }
  26.     private void comboBox1_SelectedIndexChanged(
  27.       object sender,
  28.       EventArgs e
  29.     )
  30.     {
  31.       TabbedDialogExtension.SetDirty(this, true);
  32.     }
  33.   }
  34. }
Now for our rest of the implementation. We're going to add our custom tab on load of the application (see these two previous posts for the approach for running code on application load), so there's no need to implement a command. Here's the C# code:
  1. using Autodesk.AutoCAD.Runtime;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. [assembly:
  5.   ExtensionApplication(
  6.     typeof(OneNeedsOptions.Initialization)
  7.   )
  8. ]
  9. namespace OneNeedsOptions
  10. {
  11.   class Initialization : IExtensionApplication
  12.   {
  13.     static OptionsDlg.UserControl1 _uc;
  14.     public void Initialize()
  15.     {
  16.       Application.DisplayingOptionDialog +=
  17.         new TabbedDialogEventHandler(
  18.           Application_DisplayingOptionDialog
  19.         );
  20.     }
  21.     public void Terminate()
  22.     {
  23.       Application.DisplayingOptionDialog -=
  24.         new TabbedDialogEventHandler(
  25.           Application_DisplayingOptionDialog
  26.         );
  27.     }
  28.     private static void ButtonPressedMessage(string name)
  29.     {
  30.       Editor ed =
  31.         Application.DocumentManager.MdiActiveDocument.Editor;
  32.       ed.WriteMessage(name + " button pressed.\n");
  33.     }
  34.     private static void ShowSettings()
  35.     {
  36.       if (_uc != null)
  37.       {
  38.         Editor ed =
  39.           Application.DocumentManager.MdiActiveDocument.Editor;
  40.         ed.WriteMessage(
  41.           "Settings were: checkbox contains {0}," +
  42.           " combobox contains {1}\n",
  43.           _uc.checkBox1.Checked,
  44.           _uc.comboBox1.Text
  45.         );
  46.       }
  47.     }
  48.     private static void OnOK()
  49.     {
  50.       ButtonPressedMessage("OK");
  51.       ShowSettings();
  52.     }
  53.     private static void OnCancel()
  54.     {
  55.       ButtonPressedMessage("Cancel");
  56.     }
  57.     private static void OnHelp()
  58.     {
  59.       ButtonPressedMessage("Help");
  60.     }
  61.     private static void OnApply()
  62.     {
  63.       ButtonPressedMessage("Apply");
  64.       ShowSettings();
  65.     }
  66.     private static void Application_DisplayingOptionDialog(
  67.       object sender,
  68.       TabbedDialogEventArgs e
  69.     )
  70.     {
  71.       if (_uc == null)
  72.       {
  73.         _uc = new OptionsDlg.UserControl1();
  74.         TabbedDialogExtension tde =
  75.           new TabbedDialogExtension(
  76.             _uc,
  77.             new TabbedDialogAction(OnOK),
  78.             new TabbedDialogAction(OnCancel),
  79.             new TabbedDialogAction(OnHelp),
  80.             new TabbedDialogAction(OnApply)
  81.           );
  82.         e.AddTab("My Custom Settings", tde);
  83.       }
  84.     }
  85.   }
  86. }
When we build the application, load it into AutoCAD and run the OPTIONS command, we should see our custom settings appear in our new tab:

As the various settings in the dialog get modified (even the button being clicked), you'll see the "Apply" button get enabled - a direct result of the settings on the control being considered "dirty". As you select the various buttons belonging to the dialog you should see messages on the command-line, as the respective events get fired.
In the next post we'll extend this example to provide access to persistent properties, as you would want to do from a real application.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2024-11-23 13:06 , Processed in 0.231297 second(s), 25 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表