明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 18585|回复: 28

[Kean专集] Kean专题(9)—Object_Properties

   关闭 [复制链接]
发表于 2009-5-26 17:04:00 | 显示全部楼层 |阅读模式
本帖最后由 作者 于 2009-5-26 17:46:39 编辑

原帖:http://through-the-interface.typepad.com/through_the_interface/object_properties
一、使用图元属性
September 20, 2006
Accessing object properties in an AutoCAD .NET application
This post takes the sample code shown in the last entry, converts it to VB.NET and adds in some code to access specific entity properties rather than simply calling list().
I won't step through the code this time, but here's the section of code that gets and dumps out the various entity properties:
  1. Dim ent As Entity = CType(tr.GetObject(objId, OpenMode.ForRead), Entity)
  2. ' This time access the properties directly
  3. ed.WriteMessage(vbLf + "Type:         " + ent.GetType().ToString)
  4. ed.WriteMessage(vbLf + "  Handle:     " + ent.Handle().ToString)
  5. ed.WriteMessage(vbLf + "  Layer:      " + ent.Layer().ToString)
  6. ed.WriteMessage(vbLf + "  Linetype:   " + ent.Linetype().ToString)
  7. ed.WriteMessage(vbLf + "  Lineweight: " + ent.LineWeight().ToString)
  8. ed.WriteMessage(vbLf + "  ColorIndex: " + ent.ColorIndex().ToString)
  9. ed.WriteMessage(vbLf + "  Color:      " + ent.Color().ToString)
  10. ent.Dispose()
We're using the ToString property to help generate a string to display through the AutoCAD command-line - otherwise the code should be fairly obvious.
Here's the complete VB.NET sample:
  1. Imports Autodesk.AutoCAD
  2. Imports Autodesk.AutoCAD.Runtime
  3. Imports Autodesk.AutoCAD.ApplicationServices
  4. Imports Autodesk.AutoCAD.DatabaseServices
  5. Imports Autodesk.AutoCAD.EditorInput
  6. Namespace SelectionTest
  7.   Public Class PickfirstTestCmds
  8.     ' Must have UsePickSet specified
  9.     <CommandMethod("PFT", _
  10.       (CommandFlags.UsePickSet _
  11.         Or CommandFlags.Redraw _
  12.         Or CommandFlags.Modal))> _
  13.     Public Shared Sub PickFirstTest()
  14.       Dim doc As Document = _
  15.         Application.DocumentManager.MdiActiveDocument
  16.       Dim ed As Editor = doc.Editor
  17.       Try
  18.         Dim selectionRes As PromptSelectionResult
  19.         selectionRes = ed.SelectImplied
  20.         ' If there's no pickfirst set available...
  21.         If (selectionRes.Status = PromptStatus.Error) Then
  22.           ' ... ask the user to select entities
  23.           Dim selectionOpts As PromptSelectionOptions
  24.           selectionOpts = New PromptSelectionOptions
  25.           selectionOpts.MessageForAdding = _
  26.             vbLf & "Select objects to list: "
  27.           selectionRes = ed.GetSelection(selectionOpts)
  28.         Else
  29.           ' If there was a pickfirst set, clear it
  30.           ed.SetImpliedSelection(Nothing)
  31.         End If
  32.         ' If the user has not cancelled...
  33.         If (selectionRes.Status = PromptStatus.OK) Then
  34.           ' ... take the selected objects one by one
  35.           Dim tr As Transaction = _
  36.             doc.TransactionManager.StartTransaction
  37.           Try
  38.             Dim objIds() As ObjectId = _
  39.               selectionRes.Value.GetObjectIds
  40.             For Each objId As ObjectId In objIds
  41.               Dim ent As Entity = _
  42.                 CType(tr.GetObject(objId, OpenMode.ForRead), Entity)
  43.               ' This time access the properties directly
  44.               ed.WriteMessage(vbLf + "Type:        " + _
  45.                 ent.GetType().ToString)
  46.               ed.WriteMessage(vbLf + "  Handle:    " + _
  47.                 ent.Handle().ToString)
  48.               ed.WriteMessage(vbLf + "  Layer:      " + _
  49.                 ent.Layer().ToString)
  50.               ed.WriteMessage(vbLf + "  Linetype:  " + _
  51.                 ent.Linetype().ToString)
  52.               ed.WriteMessage(vbLf + "  Lineweight: " + _
  53.                 ent.LineWeight().ToString)
  54.               ed.WriteMessage(vbLf + "  ColorIndex: " + _
  55.                 ent.ColorIndex().ToString)
  56.               ed.WriteMessage(vbLf + "  Color:      " + _
  57.                 ent.Color().ToString)
  58.               ent.Dispose()
  59.             Next
  60.             ' Although no changes were made, use Commit()
  61.             ' as this is much quicker than rolling back
  62.             tr.Commit()
  63.           Catch ex As Autodesk.AutoCAD.Runtime.Exception
  64.             ed.WriteMessage(ex.Message)
  65.             tr.Abort()
  66.           End Try
  67.         End If
  68.       Catch ex As Autodesk.AutoCAD.Runtime.Exception
  69.         ed.WriteMessage(ex.Message)
  70.       End Try
  71.     End Sub
  72.   End Class
  73. End Namespace
 楼主| 发表于 2009-5-26 17:12:00 | 显示全部楼层
二、改变块内图元的颜色
February 02, 2007
Changing the colour of nested AutoCAD entities through .NET
Someone asked me earlier today how to iteratively change the color of entities inside blocks.
The following code uses a recursive helper function to iterate down through the contents of a block, changing the various entities (other than block references, for which we simply recurse) to a specified colour.
Here's the C# code:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.Colors;
  6. namespace BlockTest
  7. {
  8.   public class BlockCmds
  9.   {
  10.     [CommandMethod("CC")]
  11.     public void ChangeColor()
  12.     {
  13.       Document doc =
  14.         Application.DocumentManager.MdiActiveDocument;
  15.       Database db = doc.Database;
  16.       Editor ed = doc.Editor;
  17.       PromptIntegerResult pr =
  18.         ed.GetInteger(
  19.           "\nEnter color index to change all entities to: "
  20.         );
  21.       if (pr.Status == PromptStatus.OK)
  22.       {
  23.         short newColorIndex = (short)pr.Value;
  24.         ObjectId msId;
  25.         Transaction tr =
  26.           doc.TransactionManager.StartTransaction();
  27.         using (tr)
  28.         {
  29.           BlockTable bt =
  30.             (BlockTable)tr.GetObject(
  31.               db.BlockTableId,
  32.               OpenMode.ForRead
  33.             );
  34.           msId =
  35.             bt[BlockTableRecord.ModelSpace];
  36.           // Not needed, but quicker than aborting
  37.           tr.Commit();
  38.         }
  39.         int count =
  40.           ChangeNestedEntitiesToColor(msId, newColorIndex);
  41.         ed.Regen();
  42.         ed.WriteMessage(
  43.           "\nChanged {0} entit{1} to color {2}.",
  44.           count,
  45.           count == 1 ? "y" : "ies",
  46.           newColorIndex
  47.         );
  48.       }
  49.     }
  50.     private int ChangeNestedEntitiesToColor(
  51.       ObjectId btrId, short colorIndex)
  52.     {
  53.       int changedCount = 0;
  54.       Document doc =
  55.         Application.DocumentManager.MdiActiveDocument;
  56.       Database db = doc.Database;
  57.       Editor ed = doc.Editor;
  58.       Transaction tr =
  59.         doc.TransactionManager.StartTransaction();
  60.       using (tr)
  61.       {
  62.         BlockTableRecord btr =
  63.           (BlockTableRecord)tr.GetObject(
  64.             btrId,
  65.             OpenMode.ForRead
  66.           );
  67.         foreach (ObjectId entId in btr)
  68.         {
  69.           Entity ent =
  70.             tr.GetObject(entId, OpenMode.ForRead)
  71.             as Entity;
  72.           if (ent != null)
  73.           {
  74.             BlockReference br = ent as BlockReference;
  75.             if (br != null)
  76.             {
  77.               // Recurse for nested blocks
  78.               changedCount +=
  79.                 ChangeNestedEntitiesToColor(
  80.                   br.BlockTableRecord,
  81.                   colorIndex
  82.                 );
  83.             }
  84.             else
  85.             {
  86.               if (ent.ColorIndex != colorIndex)
  87.               {
  88.                 changedCount++;
  89.                 // Entity is only open for read
  90.                 ent.UpgradeOpen();
  91.                 ent.ColorIndex = colorIndex;
  92.                 ent.DowngradeOpen();
  93.               }
  94.             }
  95.           }
  96.         }
  97.         tr.Commit();
  98.       }
  99.       return changedCount;
  100.     }
  101.   }
  102. }
 楼主| 发表于 2009-5-26 17:16:00 | 显示全部楼层
三、利用反射改变图元的属性
February 05, 2007
Using .NET reflection with AutoCAD to change object properties - Part 1
I ended up having a fun weekend, in spite of the circumstances. My wife and eldest son were both sick with a cold (which, inevitably enough, I now feel I’m coming down with myself), so we mostly stayed around at home. So I got to spend a little time working on an idea that came to me after my last post.
While the code I provided last time does pretty much exactly what was asked of it (allowing the person who requested it to change the colour of every entity in the modelspace – whether in nested blocks or not – for them to be coloured “by block”), it occurred to me that to be a really useful tool we should go one step further and enable two things:
a) Allow the user to specify the kind of object to apply the change to
b) Allow the user to select the specific property that should be changed
What we'd actually end up with, doing this, is a CHPROP on steroids - a command-line interface to provide deep object property modification (down through nested blocks), on any kind of object (as long as it - or one of its ancestor classes - provides a .NET interface). This is cool functionality for people needing to go and massage data, though clearly is potentially quite a scary tool in the wrong hands (thank goodness for the undo mechanism!).
The specific programming problem we need to solve comes down to runtime querying/execution of code and is quite interesting: one that’s easy to solve in LISP (thanks to the combination of the (read) and (eval) functions) and in COM (using type information to call through to IDispatch-declared functions exposed via the v-table), but is almost impossible in C++. With ObjectARX you can use class factories to create instances of objects that were not compiled in (we do this when we load a drawing – we find out the class name of each object being read in, call its static class factory method available in the class’ corresponding AcRxClass object, and pass the information we read in to the dwgInFields() function to resurrect a valid instance of the object). But it’s much harder to query at runtime for a particular method to be called – you could hardcode it for the built-in protocol, but any new objects would cause a problem.
But anyway – all this to say that the way to do it in .NET is to use our old friend Reflection (I love it when a couple of recent topics converge like this, although I wish I could say it was all part of some grand plan… :-)
So, there are four things we actually need to use reflection for in this sample:
  1. 1) Get a System.Type from a string:
  2. System.Type objType = System.Type.GetType(typeName);
  3. 2) Using the Type object get a PropertyInfo from a string:
  4. System.Reflection.PropertyInfo propInfo = objType.GetProperty(propName);
  5. 3) Check whether an entity is of a particular type:
  6. res = objType.IsInstanceOfType(ent);
  7. 4) Access the property on a particular entity:
  8. Get...
  9. val = objType.InvokeMember(propName, BindingFlags.GetProperty, null, ent, args);
  10. Set...
  11. objType.InvokeMember(propName, BindingFlags.SetProperty, null, ent, args);
复制代码
That's basically all there is to it, but I'm going to drag this out over a couple of posts, as the code does get quite involved.
This first post is going to focus on the user-input aspect of the code - querying the user for the various bits of information we need, and getting the type & property information using reflection. So it really only looks at the first two items on the above list.
Here's the C# code itself - the main user-input function is called  SelectClassPropertyAndValue(), and I've defined a simple TEST command to simply call it and return the results.
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.Colors;
  6. using System.Reflection;
  7. namespace PropertyChanger
  8. {
  9.   public class PropertyChangerCmds
  10.   {
  11.     [CommandMethod("TEST")]
  12.     public void TestInput()
  13.     {
  14.       Document doc =
  15.         Application.DocumentManager.MdiActiveDocument;
  16.       Editor ed = doc.Editor;
  17.       System.Type objType;
  18.       string propName;
  19.       object newPropValue;
  20.       bool recurse;
  21.       if (SelectClassPropertyAndValue(
  22.             out objType,
  23.             out propName,
  24.             out newPropValue,
  25.             out recurse
  26.           )
  27.         )
  28.       {
  29.         ed.WriteMessage(
  30.           "\nType selected: " + objType.Name +
  31.           "\nProperty selected: " + propName +
  32.           "\nValue selected: " + newPropValue +
  33.           "\nRecurse chosen: " + recurse
  34.         );
  35.       }
  36.       else
  37.       {
  38.         ed.WriteMessage(
  39.           "\nFunction returned false."
  40.         );
  41.       }
  42.     }
  43.     private bool SelectClassPropertyAndValue(
  44.       out System.Type objType,
  45.       out string propName,
  46.       out object newPropValue,
  47.       out bool recurse)
  48.     {
  49.       Document doc =
  50.         Application.DocumentManager.MdiActiveDocument;
  51.       Editor ed = doc.Editor;
  52.       objType = null;
  53.       propName = "";
  54.       newPropValue = null;
  55.       recurse = true;
  56.       // Let's first get the class to query for
  57.       PromptResult ps =
  58.         ed.GetString(
  59.           "\nEnter type of objects to look for: "
  60.         );
  61.       if (ps.Status == PromptStatus.OK)
  62.       {
  63.         string typeName = ps.StringResult;
  64.         // Use reflection to get the type from the string
  65.         objType =
  66.           System.Type.GetType(
  67.             typeName,
  68.             false,        // Do not throw an exception
  69.             true          // Case-insensitive search
  70.           );
  71.         // If we didn't find it, try prefixing with
  72.         // "Autodesk.AutoCAD.DatabaseServices."
  73.         if (objType == null)
  74.         {
  75.           objType =
  76.             System.Type.GetType(
  77.               "Autodesk.AutoCAD.DatabaseServices." +
  78.               typeName + ", acdbmgd",
  79.               false,      // Do not throw an exception
  80.               true        // Case-insensitive search
  81.             );
  82.         }
  83.         if (objType == null)
  84.         {
  85.           ed.WriteMessage(
  86.             "\nType " + typeName + " not found."
  87.           );
  88.         }
  89.         else
  90.         {
  91.           // If we have a valid type then let's
  92.           // first list its writable properties
  93.           ListProperties(objType);
  94.           // Prompt for a property
  95.           ps = ed.GetString(
  96.             "\nEnter property to modify: "
  97.           );
  98.           if (ps.Status == PromptStatus.OK)
  99.           {
  100.             propName = ps.StringResult;
  101.             // Make sure the property exists...
  102.             System.Reflection.PropertyInfo propInfo =
  103.               objType.GetProperty(propName);
  104.             if (propInfo == null)
  105.             {
  106.               ed.WriteMessage(
  107.                 "\nProperty " +
  108.                 propName +
  109.                 " for type " +
  110.                 typeName +
  111.                 " not found."
  112.               );
  113.             }
  114.             else
  115.             {
  116.               if (!propInfo.CanWrite)
  117.               {
  118.                 ed.WriteMessage(
  119.                   "\nProperty " +
  120.                   propName +
  121.                   " of type " +
  122.                   typeName +
  123.                   " is not writable."
  124.                 );
  125.               }
  126.               else
  127.               {
  128.                 // If the property is writable...
  129.                 // ask for the new value
  130.                 System.Type propType = propInfo.PropertyType;
  131.                 string prompt =
  132.                       "\nEnter new value of " +
  133.                       propName +
  134.                       " property for all objects of type " +
  135.                       typeName +
  136.                       ": ";
  137.                 // Only certain property types are currently
  138.                 // supported: Int32, Double, String, Boolean
  139.                 switch (propType.ToString())
  140.                 {
  141.                   case "System.Int32":
  142.                     PromptIntegerResult pir =
  143.                       ed.GetInteger(prompt);
  144.                     if (pir.Status == PromptStatus.OK)
  145.                       newPropValue = pir.Value;
  146.                     break;
  147.                   case "System.Double":
  148.                     PromptDoubleResult pdr =
  149.                       ed.GetDouble(prompt);
  150.                     if (pdr.Status == PromptStatus.OK)
  151.                       newPropValue = pdr.Value;
  152.                     break;
  153.                   case "System.String":
  154.                     PromptResult psr =
  155.                       ed.GetString(prompt);
  156.                     if (psr.Status == PromptStatus.OK)
  157.                       newPropValue = psr.StringResult;
  158.                     break;
  159.                   case "System.Boolean":
  160.                     PromptKeywordOptions pko =
  161.                       new PromptKeywordOptions(
  162.                       prompt);
  163.                     pko.Keywords.Add("True");
  164.                     pko.Keywords.Add("False");
  165.                     PromptResult pkr =
  166.                       ed.GetKeywords(pko);
  167.                     if (pkr.Status == PromptStatus.OK)
  168.                     {
  169.                       if (pkr.StringResult == "True")
  170.                         newPropValue = true;
  171.                       else
  172.                         newPropValue = false;
  173.                     }
  174.                     break;
  175.                   default:
  176.                     ed.WriteMessage(
  177.                       "\nProperties of type " +
  178.                       propType.ToString() +
  179.                       " are not currently supported."
  180.                     );
  181.                     break;
  182.                 }
  183.                 if (newPropValue != null)
  184.                 {
  185.                   PromptKeywordOptions pko =
  186.                     new PromptKeywordOptions(
  187.                       "\nChange properties in nested blocks: "
  188.                     );
  189.                   pko.AllowNone = true;
  190.                   pko.Keywords.Add("Yes");
  191.                   pko.Keywords.Add("No");
  192.                   pko.Keywords.Default = "Yes";
  193.                   PromptResult pkr =
  194.                     ed.GetKeywords(pko);
  195.                   if (pkr.Status == PromptStatus.None |
  196.                       pkr.Status == PromptStatus.OK)
  197.                   {
  198.                     if (pkr.Status == PromptStatus.None |
  199.                         pkr.StringResult == "Yes")
  200.                       recurse = true;
  201.                     else
  202.                       recurse = false;
  203.                     return true;
  204.                   }
  205.                 }
  206.               }
  207.             }
  208.           }
  209.         }
  210.       }
  211.       return false;
  212.     }
  213.     private void ListProperties(System.Type objType)
  214.     {
  215.       Document doc =
  216.         Application.DocumentManager.MdiActiveDocument;
  217.       Editor ed = doc.Editor;
  218.       ed.WriteMessage(
  219.         "\nWritable properties for " +
  220.         objType.Name +
  221.         ": "
  222.       );
  223.       PropertyInfo[] propInfos =
  224.         objType.GetProperties();
  225.       foreach (PropertyInfo propInfo in propInfos)
  226.       {
  227.         if (propInfo.CanWrite)
  228.         {
  229.           ed.WriteMessage(
  230.             "\n  " +
  231.             propInfo.Name +
  232.             " : " +
  233.             propInfo.PropertyType
  234.           );
  235.         }
  236.       }
  237.       ed.WriteMessage("\n");
  238.     }
  239.   }
  240. }
There are a few points I should make about this code:
GetType() needs an assembly qualified name. The above code makes two calls to this function, one without the "Autodesk.AutoCAD.DatabaseServices." prefix and the ", acdbmgd" suffix, in case we want to get another type of class, but if that fails then the prefix/suffix get added.
To make it easier for the user to select a writable property, I've implemented a separate ListProperties() function that iterates through the available properties and provides the name and type for each one that's writable.
Only properties of these datatypes are currently supported: System.Int32, System.Double, System.String, System.Boolean. It should be simple enough to support other datatypes (Vector3d, Point3d etc.), if you have the time and the inclination.
That's about it for the UI portion of the code... let's take a look at what happens when this code runs:
  1. Command: TEST
  2. Enter type of objects to look for: Circle
  3. Writable properties for Circle:
  4.   Normal : Autodesk.AutoCAD.Geometry.Vector3d
  5.   Thickness : System.Double
  6.   Radius : System.Double
  7.   Center : Autodesk.AutoCAD.Geometry.Point3d
  8.   EndPoint : Autodesk.AutoCAD.Geometry.Point3d
  9.   StartPoint : Autodesk.AutoCAD.Geometry.Point3d
  10.   MaterialMapper : Autodesk.AutoCAD.GraphicsInterface.Mapper
  11.   MaterialId : Autodesk.AutoCAD.DatabaseServices.ObjectId
  12.   Material : System.String
  13.   ReceiveShadows : System.Boolean
  14.   CastShadows : System.Boolean
  15.   LineWeight : Autodesk.AutoCAD.DatabaseServices.LineWeight
  16.   Visible : System.Boolean
  17.   LinetypeScale : System.Double
  18.   LinetypeId : Autodesk.AutoCAD.DatabaseServices.ObjectId
  19.   Linetype : System.String
  20.   LayerId : Autodesk.AutoCAD.DatabaseServices.ObjectId
  21.   Layer : System.String
  22.   PlotStyleNameId : Autodesk.AutoCAD.DatabaseServices.PlotStyleDescriptor
  23.   PlotStyleName : System.String
  24.   Transparency : Autodesk.AutoCAD.Colors.Transparency
  25.   ColorIndex : System.Int32
  26.   Color : Autodesk.AutoCAD.Colors.Color
  27.   HasSaveVersionOverride : System.Boolean
  28.   XData : Autodesk.AutoCAD.DatabaseServices.ResultBuffer
  29.   MergeStyle : Autodesk.AutoCAD.DatabaseServices.DuplicateRecordCloning
  30.   OwnerId : Autodesk.AutoCAD.DatabaseServices.ObjectId
  31.   AutoDelete : System.Boolean
  32. Enter property to modify: Radius
  33. Enter new value of Radius property for all objects of type Circle: 3.14159
  34. Change properties in nested blocks [Yes/No] <Yes>: Y
  35. Type selected: Circle
  36. Property selected: Radius
  37. Value selected: 3.14159
  38. Recurse chosen: True
复制代码
In the next post we'll hook this up with some code to actually call the properties on various objects, recursing through block definitions, as needed.

 楼主| 发表于 2009-5-26 17:20:00 | 显示全部楼层
February 07, 2007
Using .NET reflection with AutoCAD to change object properties - Part 2
In the last post we looked at some code that combined user-input from the AutoCAD command-line with .NET reflection to determine an object type, a property belonging to that type, and the value to which we want to change that property on instances of that type (phew!). Here's where we look at hooking that up with some code to work recursively through the drawing database and make the changes to the actual objects.
Firstly, let's look at some C# code to check the type of an entity, and then - as appropriate - to query the property's value and to set it to a new value, if it doesn't match what it needs to be set to:
  1. // Function to change an individual entity's property
  2. private int ChangeSingleProperty(
  3.   Entity ent,
  4.   System.Type objType,
  5.   string propName,
  6.   object newValue)
  7. {
  8.   int changedCount = 0;
  9.   // Are we dealing with an entity we care about?
  10.   if (objType.IsInstanceOfType(ent))
  11.   {
  12.     // Check the existing value
  13.     object res =
  14.       objType.InvokeMember(
  15.         propName,
  16.         BindingFlags.GetProperty,
  17.         null,
  18.         ent,
  19.         new object[0]
  20.       );
  21.     // If it is not the same then change it
  22.     if (!res.Equals(newValue))
  23.     {
  24.       // Entity is only open for read
  25.       ent.UpgradeOpen();
  26.       object[] args = new object[1];
  27.       args[0] = newValue;
  28.       res =
  29.         objType.InvokeMember(
  30.           propName,
  31.           BindingFlags.SetProperty,
  32.           null,
  33.           ent,
  34.           args
  35.         );
  36.       changedCount++;
  37.       ent.DowngradeOpen();
  38.     }
  39.   }
  40.   return changedCount;
  41. }
The previous post mentioned the guts of this function as two uses we intended to make of reflection (items 3 & 4 in the list, if you remember), but I'd suggest looking at the MSDN documentation on Type.IsInstanceOfType() and on Type.InvokeMember() for more information on how these functions work.
The rest of the code is relatively close to what was shown in the last post but one. I've approached things a little differently here, though:
Recursion is now optional - we use a flag that gets set by the user.
We have two versions of ChangePropertyOfEntitiesOfType() - the main function to change the property on a set of objects: one takes the ID of a container block table record and opens it, passing the list of contained objects through to the other version, which simply takes a list of object IDs. I could have duplicated some of the code for performance purposes, but it seemed cleaner for now to take the performance hit and reduce code duplication/maintainance.
There are three commands defined (and I've done what I can to share implementations across them):
CHPS - CHange the Property on Selected entities
CHPM - CHange the Property on the contents of the Modelspace
CHPP - CHange the Property on the contents of the Paperspace
That's about all there is to it. Here's the full C# source:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using System.Reflection;
  6. namespace PropertyChanger
  7. {
  8.   public class PropertyChangerCmds
  9.   {
  10.     [CommandMethod("CHPS",
  11.       CommandFlags.Modal |
  12.       CommandFlags.Redraw |
  13.       CommandFlags.UsePickSet)
  14.     ]
  15.     public void ChangePropertyOnSelectedEntities()
  16.     {
  17.       Document doc =
  18.         Application.DocumentManager.MdiActiveDocument;
  19.       Editor ed = doc.Editor;
  20.       try
  21.       {
  22.         PromptSelectionResult psr =
  23.           ed.GetSelection();
  24.         if (psr.Status == PromptStatus.OK)
  25.         {
  26.           System.Type objType;
  27.           string propName;
  28.           object newPropValue;
  29.           bool recurse;
  30.           if (SelectClassPropertyAndValue(
  31.                 out objType,
  32.                 out propName,
  33.                 out newPropValue,
  34.                 out recurse
  35.               )
  36.             )
  37.           {
  38.             int count =
  39.               ChangePropertyOfEntitiesOfType(
  40.                 psr.Value.GetObjectIds(),
  41.                 objType,
  42.                 propName,
  43.                 newPropValue,
  44.                 recurse);
  45.             // Update the display, and print the count
  46.             ed.Regen();
  47.             ed.WriteMessage(
  48.               "\nChanged " +
  49.               count + " object" +
  50.               (count == 1 ? "" : "s") +
  51.               " of type " +
  52.               objType.Name +
  53.               " to have a " +
  54.               propName + " of " +
  55.               newPropValue + "."
  56.             );
  57.           }
  58.         }
  59.       }
  60.       catch (System.Exception ex)
  61.       {
  62.         ed.WriteMessage(
  63.           "Exception: " + ex
  64.         );
  65.       }
  66.     }
  67.     [CommandMethod("CHPM")]
  68.     public void ChangePropertyOnModelSpaceContents()
  69.     {
  70.       ChangePropertyOnSpaceContents(
  71.         BlockTableRecord.ModelSpace
  72.       );
  73.     }
  74.     [CommandMethod("CHPP")]
  75.     public void ChangePropertyOnPaperSpaceContents()
  76.     {
  77.       ChangePropertyOnSpaceContents(
  78.         BlockTableRecord.PaperSpace
  79.       );
  80.     }
  81.     private void ChangePropertyOnSpaceContents(
  82.       string spaceName
  83.     )
  84.     {
  85.       Document doc =
  86.         Application.DocumentManager.MdiActiveDocument;
  87.       Editor ed = doc.Editor;
  88.       try
  89.       {
  90.         System.Type objType;
  91.         string propName;
  92.         object newPropValue;
  93.         bool recurse;
  94.         if (SelectClassPropertyAndValue(
  95.               out objType,
  96.               out propName,
  97.               out newPropValue,
  98.               out recurse
  99.             )
  100.           )
  101.         {
  102.           ObjectId spaceId;
  103.           Transaction tr =
  104.             doc.TransactionManager.StartTransaction();
  105.           using (tr)
  106.           {
  107.             BlockTable bt =
  108.               (BlockTable)tr.GetObject(
  109.                 doc.Database.BlockTableId,
  110.                 OpenMode.ForRead
  111.               );
  112.             spaceId = bt[spaceName];
  113.             // Not needed, but quicker than aborting
  114.             tr.Commit();
  115.           }
  116.           // Call our recursive function to set the new
  117.           // value in our nested objects
  118.           int count =
  119.             ChangePropertyOfEntitiesOfType(
  120.               spaceId,
  121.               objType,
  122.               propName,
  123.               newPropValue,
  124.               recurse);
  125.           // Update the display, and print the count
  126.           ed.Regen();
  127.           ed.WriteMessage(
  128.             "\nChanged " +
  129.             count + " object" +
  130.             (count == 1 ? "" : "s") +
  131.             " of type " +
  132.             objType.Name +
  133.             " to have a " +
  134.             propName + " of " +
  135.             newPropValue + "."
  136.           );
  137.         }
  138.       }
  139.       catch (System.Exception ex)
  140.       {
  141.         ed.WriteMessage(
  142.           "Exception: " + ex
  143.         );
  144.       }
  145.     }
  146.     private bool SelectClassPropertyAndValue(
  147.       out System.Type objType,
  148.       out string propName,
  149.       out object newPropValue,
  150.       out bool recurse)
  151.     {
  152.       Document doc =
  153.         Application.DocumentManager.MdiActiveDocument;
  154.       Editor ed = doc.Editor;
  155.       objType = null;
  156.       propName = "";
  157.       newPropValue = null;
  158.       recurse = true;
  159.       // Let's first get the class to query for
  160.       PromptResult ps =
  161.         ed.GetString(
  162.           "\nEnter type of objects to look for: "
  163.         );
  164.       if (ps.Status == PromptStatus.OK)
  165.       {
  166.         string typeName = ps.StringResult;
  167.         // Use reflection to get the type from the string
  168.         objType =
  169.           System.Type.GetType(
  170.             typeName,
  171.             false,        // Do not throw an exception
  172.             true          // Case-insensitive search
  173.           );
  174.         // If we didn't find it, try prefixing with
  175.         // "Autodesk.AutoCAD.DatabaseServices."
  176.         if (objType == null)
  177.         {
  178.           objType =
  179.             System.Type.GetType(
  180.               "Autodesk.AutoCAD.DatabaseServices." +
  181.               typeName + ", acdbmgd",
  182.               false,      // Do not throw an exception
  183.               true        // Case-insensitive search
  184.             );
  185.         }
  186.         if (objType == null)
  187.         {
  188.           ed.WriteMessage(
  189.             "\nType " + typeName + " not found."
  190.           );
  191.         }
  192.         else
  193.         {
  194.           // If we have a valid type then let's
  195.           // first list its writable properties
  196.           ListProperties(objType);
  197.           // Prompt for a property
  198.           ps = ed.GetString(
  199.             "\nEnter property to modify: "
  200.           );
  201.           if (ps.Status == PromptStatus.OK)
  202.           {
  203.             propName = ps.StringResult;
  204.             // Make sure the property exists...
  205.             System.Reflection.PropertyInfo propInfo =
  206.               objType.GetProperty(propName);
  207.             if (propInfo == null)
  208.             {
  209.               ed.WriteMessage(
  210.                 "\nProperty " +
  211.                 propName +
  212.                 " for type " +
  213.                 typeName +
  214.                 " not found."
  215.               );
  216.             }
  217.             else
  218.             {
  219.               if (!propInfo.CanWrite)
  220.               {
  221.                 ed.WriteMessage(
  222.                   "\nProperty " +
  223.                   propName +
  224.                   " of type " +
  225.                   typeName +
  226.                   " is not writable."
  227.                 );
  228.               }
  229.               else
  230.               {
  231.                 // If the property is writable...
  232.                 // ask for the new value
  233.                 System.Type propType = propInfo.PropertyType;
  234.                 string prompt =
  235.                       "\nEnter new value of " +
  236.                       propName +
  237.                       " property for all objects of type " +
  238.                       typeName +
  239.                       ": ";
  240.                 // Only certain property types are currently
  241.                 // supported: Int32, Double, String, Boolean
  242.                 switch (propType.ToString())
  243.                 {
  244.                   case "System.Int32":
  245.                     PromptIntegerResult pir =
  246.                       ed.GetInteger(prompt);
  247.                     if (pir.Status == PromptStatus.OK)
  248.                       newPropValue = pir.Value;
  249.                     break;
  250.                   case "System.Double":
  251.                     PromptDoubleResult pdr =
  252.                       ed.GetDouble(prompt);
  253.                     if (pdr.Status == PromptStatus.OK)
  254.                       newPropValue = pdr.Value;
  255.                     break;
  256.                   case "System.String":
  257.                     PromptResult psr =
  258.                       ed.GetString(prompt);
  259.                     if (psr.Status == PromptStatus.OK)
  260.                       newPropValue = psr.StringResult;
  261.                     break;
  262.                   case "System.Boolean":
  263.                     PromptKeywordOptions pko =
  264.                       new PromptKeywordOptions(
  265.                       prompt);
  266.                     pko.Keywords.Add("True");
  267.                     pko.Keywords.Add("False");
  268.                     PromptResult pkr =
  269.                       ed.GetKeywords(pko);
  270.                     if (pkr.Status == PromptStatus.OK)
  271.                     {
  272.                       if (pkr.StringResult == "True")
  273.                         newPropValue = true;
  274.                       else
  275.                         newPropValue = false;
  276.                     }
  277.                     break;
  278.                   default:
  279.                     ed.WriteMessage(
  280.                       "\nProperties of type " +
  281.                       propType.ToString() +
  282.                       " are not currently supported."
  283.                     );
  284.                     break;
  285.                 }
  286.                 if (newPropValue != null)
  287.                 {
  288.                   PromptKeywordOptions pko =
  289.                     new PromptKeywordOptions(
  290.                       "\nChange properties in nested blocks: "
  291.                     );
  292.                   pko.AllowNone = true;
  293.                   pko.Keywords.Add("Yes");
  294.                   pko.Keywords.Add("No");
  295.                   pko.Keywords.Default = "Yes";
  296.                   PromptResult pkr =
  297.                     ed.GetKeywords(pko);
  298.                   if (pkr.Status == PromptStatus.None |
  299.                       pkr.Status == PromptStatus.OK)
  300.                   {
  301.                     if (pkr.Status == PromptStatus.None |
  302.                         pkr.StringResult == "Yes")
  303.                       recurse = true;
  304.                     else
  305.                       recurse = false;
  306.                     return true;
  307.                   }
  308.                 }
  309.               }
  310.             }
  311.           }
  312.         }
  313.       }
  314.       return false;
  315.     }
  316.     private void ListProperties(System.Type objType)
  317.     {
  318.       Document doc =
  319.         Application.DocumentManager.MdiActiveDocument;
  320.       Editor ed = doc.Editor;
  321.       ed.WriteMessage(
  322.         "\nWritable properties for " +
  323.         objType.Name +
  324.         ": "
  325.       );
  326.       PropertyInfo[] propInfos =
  327.         objType.GetProperties();
  328.       foreach (PropertyInfo propInfo in propInfos)
  329.       {
  330.         if (propInfo.CanWrite)
  331.         {
  332.           ed.WriteMessage(
  333.             "\n  " +
  334.             propInfo.Name +
  335.             " : " +
  336.             propInfo.PropertyType
  337.           );
  338.         }
  339.       }
  340.       ed.WriteMessage("\n");
  341.     }
  342.     // Version of the function that takes a container ID
  343.     private int ChangePropertyOfEntitiesOfType(
  344.       ObjectId btrId,
  345.       System.Type objType,
  346.       string propName,
  347.       object newValue,
  348.       bool recurse
  349.     )
  350.     {
  351.       // We simply open the container, extract the IDs
  352.       // and pass them to another version of the function...
  353.       // If efficiency is an issue, then this could be
  354.       // streamlined (i.e. duplicated, rather than factored)
  355.       ObjectIdCollection btrContents =
  356.         new ObjectIdCollection();
  357.       Document doc =
  358.         Application.DocumentManager.MdiActiveDocument;
  359.       Transaction tr =
  360.         doc.TransactionManager.StartTransaction();
  361.       using (tr)
  362.       {
  363.         BlockTableRecord btr =
  364.           (BlockTableRecord)tr.GetObject(
  365.             btrId,
  366.             OpenMode.ForRead
  367.           );
  368.         foreach (ObjectId entId in btr)
  369.         {
  370.           btrContents.Add(entId);
  371.         }
  372.         tr.Commit();
  373.       }
  374.       ObjectId[] ids = new ObjectId[btrContents.Count];
  375.       btrContents.CopyTo(ids, 0);
  376.       // Call the other version of this function
  377.       return ChangePropertyOfEntitiesOfType(
  378.         ids,
  379.         objType,
  380.         propName,
  381.         newValue,
  382.         recurse
  383.       );
  384.     }
  385.     // Version of the function that takes a list of ents
  386.     private int ChangePropertyOfEntitiesOfType(
  387.       ObjectId[] objIds,
  388.       System.Type objType,
  389.       string propName,
  390.       object newValue,
  391.       bool recurse
  392.     )
  393.     {
  394.       int changedCount = 0;
  395.       Document doc =
  396.         Application.DocumentManager.MdiActiveDocument;
  397.       Transaction tr =
  398.         doc.TransactionManager.StartTransaction();
  399.       using (tr)
  400.       {
  401.         foreach (ObjectId entId in objIds)
  402.         {
  403.           Entity ent =
  404.             tr.GetObject(entId, OpenMode.ForRead)
  405.             as Entity;
  406.           // Change each entity, one by one
  407.           if (ent != null)
  408.           {
  409.             changedCount +=
  410.               ChangeSingleProperty(
  411.                 ent,
  412.                 objType,
  413.                 propName,
  414.                 newValue
  415.               );
  416.           }
  417.           // If we are to recurse and it's a blockref...
  418.           if (recurse)
  419.           {
  420.             BlockReference br = ent as BlockReference;
  421.             if (br != null)
  422.             {
  423.               // ...then recurse
  424.               changedCount +=
  425.                 ChangePropertyOfEntitiesOfType(
  426.                   br.BlockTableRecord,
  427.                   objType,
  428.                   propName,
  429.                   newValue,
  430.                   recurse
  431.                 );
  432.             }
  433.           }
  434.         }
  435.         tr.Commit();
  436.       }
  437.       return changedCount;
  438.     }
  439.     // Function to change an individual entity's property
  440.     private int ChangeSingleProperty(
  441.       Entity ent,
  442.       System.Type objType,
  443.       string propName,
  444.       object newValue)
  445.     {
  446.       int changedCount = 0;
  447.       // Are we dealing with an entity we care about?
  448.       if (objType.IsInstanceOfType(ent))
  449.       {
  450.         // Check the existing value
  451.         object res =
  452.           objType.InvokeMember(
  453.             propName,
  454.             BindingFlags.GetProperty,
  455.             null,
  456.             ent,
  457.             new object[0]
  458.           );
  459.         // If it is not the same then change it
  460.         if (!res.Equals(newValue))
  461.         {
  462.           // Entity is only open for read
  463.           ent.UpgradeOpen();
  464.           object[] args = new object[1];
  465.           args[0] = newValue;
  466.           res =
  467.             objType.InvokeMember(
  468.               propName,
  469.               BindingFlags.SetProperty,
  470.               null,
  471.               ent,
  472.               args
  473.             );
  474.           changedCount++;
  475.           ent.DowngradeOpen();
  476.         }
  477.       }
  478.       return changedCount;
  479.     }
  480.   }
  481. }
I'll leave it as an exercise for the reader to see what can be done with the code... a few parting tips/comments:
If you want to change a subset of objects you can either select them using CHPS and then further by object type (e.g. "BlockReference"), or you can stick to the generic "Entity" type to change the entire selection.
You can toggle the "Visible" property using this code, which can be a bit scary for users (which is my queue to reiterate this point: the tool is for people who understand something about the drawing database structure and AutoCAD's object model as exposed through .NET... I'm providing the code as a demonstration of the technique for people doing development work, not for people to build and use as a standard day-to-day tool to replace or complement CHPROP).
OK - disclaimer over... enjoy! :-)

 楼主| 发表于 2009-5-26 17:27:00 | 显示全部楼层
四、使用Com Api获取图元的属性
February 21, 2007
Using the COM interface for AutoCAD objects from .NET
This question came in a couple of weeks ago from csharpbird, and I promised myself to turn it into a post:
The Hatch class of the current AutoCAD version does not provide the OriginPoint property.How to get/set the OriginPoint of the Hatch object using P/Invoke?
The reason I wanted to delay making an official post is that I have good news (that I couldn't talk about until the product was announced): the Autodesk.AutoCAD.DatabaseServices.Hatch object now has an Origin property in AutoCAD 2008. :-)
But this does raise an interesting topic - how to get at information that isn't available through the managed interface? P/Invoke is an option for access certain ObjectARX functions, but as stated in this previous post, it cannot be used with instance members, such as AcDbHatch methods.
This particular property (a Hatch's Origin) has been exposed for a few releases through COM; Property Palette support for object properties sometimes drives their COM exposure more quickly than their "managed" exposure (although increasingly the .NET API is there right from the beginning).
Anyway, here's some C# code for AutoCAD 2007 that accesses the COM interface for a hatch object, returning the Origin property. The code first gets the object as a managed hatch, simply to show a technique for going from a managed object to a COM object:
[Update: this is Version 1 of the code, posted before Albert's comment, below. Scroll down for the more elegant Version 2...]
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.Interop;
  6. using System.Runtime.InteropServices;
  7. namespace HatchQuery
  8. {
  9.   public class HatchQueryCommands
  10.   {
  11.     [CommandMethod("QH")
  12.     ]
  13.     public void QueryHatchOrigin()
  14.     {
  15.       Document doc =
  16.         Application.DocumentManager.MdiActiveDocument;
  17.       Editor ed = doc.Editor;
  18.       try
  19.       {
  20.         PromptEntityResult per =
  21.           ed.GetEntity("\nSelect hatch object: ");
  22.         if (per.Status == PromptStatus.OK)
  23.         {
  24.           Transaction tr =
  25.             doc.TransactionManager.StartTransaction();
  26.           using (tr)
  27.           {
  28.             // Open up the object
  29.             DBObject obj =
  30.               tr.GetObject(per.ObjectId, OpenMode.ForRead);
  31.             // Make sure we have a managed hatch object
  32.             Hatch hat = obj as Hatch;
  33.             if (hat != null)
  34.             {
  35.               // Now let's pretend we had our managed hatch,
  36.               // and need to use COM to get at its Origin
  37.               Autodesk.AutoCAD.Interop.AcadApplication oAcad =
  38.                 (AcadApplication)Marshal.GetActiveObject(
  39.                   "AutoCAD.Application.17"
  40.                 );
  41.               // Let's use the COM API to get a COM reference
  42.               // to the hatch (as a base object)
  43.               object obj2 =
  44.                 oAcad.ActiveDocument.ObjectIdToObject(
  45.                   obj.ObjectId.OldId
  46.                 );
  47.               // Let's convert that object reference to
  48.               // a COM hatch
  49.               Autodesk.AutoCAD.Interop.Common.AcadHatch oHat =
  50.                 obj2 as
  51.                 Autodesk.AutoCAD.Interop.Common.AcadHatch;
  52.               if (oHat != null)
  53.               {
  54.                 // Now let's get the Origin as an array
  55.                 // of doubles, and print the contents
  56.                 double[] orig = (double[])oHat.Origin;
  57.                 ed.WriteMessage(
  58.                   "\nHere's the hatch origin: X: " +
  59.                   orig[0] + ", Y: " + orig[1]
  60.                 );
  61.               }
  62.             }
  63.           }
  64.         }
  65.       }
  66.       catch (System.Exception ex)
  67.       {
  68.         ed.WriteMessage(
  69.           "Exception: " + ex
  70.         );
  71.       }
  72.     }
  73.   }
  74. }
Version 2:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using Autodesk.AutoCAD.Interop;
  6. using System.Runtime.InteropServices;
  7. namespace HatchQuery
  8. {
  9.   public class HatchQueryCommands
  10.   {
  11.     [CommandMethod("QH")
  12.     ]
  13.     public void QueryHatchOrigin()
  14.     {
  15.       Document doc =
  16.         Application.DocumentManager.MdiActiveDocument;
  17.       Editor ed = doc.Editor;
  18.       try
  19.       {
  20.         PromptEntityResult per =
  21.           ed.GetEntity("\nSelect hatch object: ");
  22.         if (per.Status == PromptStatus.OK)
  23.         {
  24.           Transaction tr =
  25.             doc.TransactionManager.StartTransaction();
  26.           using (tr)
  27.           {
  28.             // Open up the object
  29.             DBObject obj =
  30.               tr.GetObject(per.ObjectId, OpenMode.ForRead);
  31.             // Make sure we have a managed hatch object
  32.             Hatch hat = obj as Hatch;
  33.             if (hat != null)
  34.             {
  35.               // a COM hatch
  36.               Autodesk.AutoCAD.Interop.Common.AcadHatch oHat =
  37.                 obj.AcadObject as
  38.                 Autodesk.AutoCAD.Interop.Common.AcadHatch;
  39.               if (oHat != null)
  40.               {
  41.                 // Now let's get the Origin as an array
  42.                 // of doubles, and print the contents
  43.                 double[] orig = (double[])oHat.Origin;
  44.                 ed.WriteMessage(
  45.                   "\nHere's the hatch origin: X: " +
  46.                   orig[0] + ", Y: " + orig[1]
  47.                 );
  48.               }
  49.             }
  50.           }
  51.         }
  52.       }
  53.       catch (System.Exception ex)
  54.       {
  55.         ed.WriteMessage(
  56.           "Exception: " + ex
  57.         );
  58.       }
  59.     }
  60.   }
  61. }
And here's what happens when you run the code and select a hatch with an origin of 50,30:
  1. Command: QH
  2. Select hatch object:
  3. Here's the hatch origin: X: 50, Y: 30
复制代码
 楼主| 发表于 2009-5-26 17:29:00 | 显示全部楼层
五、获取三维实体的类型
May 29, 2007
Getting the type of an AutoCAD solid using .NET
Now this may seem like a very trivial post, but this was actually a significant problem for my team when preparing the material for the Autodesk Component Technologies presentation we delivered at last year's DevDays tour.
Basically the "type" (or really "sub-type") of a Solid3d (an AcDb3DSolid in ObjectARX) is not exposed through ObjectARX and therefore neither is it exposed through the managed interface. It is possible to get at the information from C++ using the Brep API, but this is currently not available from .NET (and yes, we are aware that many of you would like to see this point addressed, although I can't commit to when it will happen).
But there is some good news: the property is exposed through COM, so we can simply get a COM interface for our solid and query the data through that. Thanks again for Fenton Webb, a member of DevTech Americas, for the tip of using COM rather than worrying about the Brep API.
Here's some C# code demonstrating the technique:
  1. using Autodesk.AutoCAD.Runtime;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.EditorInput;
  5. using Autodesk.AutoCAD.Interop.Common;
  6. namespace SolidTest
  7. {
  8.   public class Cmds
  9.   {
  10.     [CommandMethod("GST")]
  11.     static public void GetSolidType()
  12.     {
  13.       Document doc =
  14.         Application.DocumentManager.MdiActiveDocument;
  15.       Editor ed = doc.Editor;
  16.       Database db = doc.Database;
  17.       PromptEntityResult per =
  18.         ed.GetEntity("\nSelect a 3D solid: ");
  19.       if (per.Status == PromptStatus.OK)
  20.       {
  21.         Transaction tr =
  22.           db.TransactionManager.StartTransaction();
  23.         using (tr)
  24.         {
  25.           DBObject obj =
  26.             tr.GetObject(per.ObjectId, OpenMode.ForRead);
  27.           Solid3d sol = obj as Solid3d;
  28.           if (sol != null)
  29.           {
  30.             Acad3DSolid oSol =
  31.               (Acad3DSolid)sol.AcadObject;
  32.             ed.WriteMessage(
  33.               "\nSolid is of type "{0}"",
  34.               oSol.SolidType
  35.             );
  36.           }
  37.           tr.Commit();
  38.         }
  39.       }
  40.     }
  41.   }
  42. }
And here's what we see when we run the GST command, selecting a number of Solid3d objects, one by one:
  1. Command: GST
  2. Select a 3D solid:
  3. Solid is of type "Box"
  4. Command:  GST
  5. Select a 3D solid:
  6. Solid is of type "Sphere"
  7. Command:  GST
  8. Select a 3D solid:
  9. Solid is of type "Wedge"
  10. Command:  GST
  11. Select a 3D solid:
  12. Solid is of type "Cylinder"
  13. Command:  GST
  14. Select a 3D solid:
  15. Solid is of type "Cone"
  16. Command:  GST
  17. Select a 3D solid:
  18. Solid is of type "Torus"
复制代码
 楼主| 发表于 2009-5-26 17:33:00 | 显示全部楼层
六、在模式对话框中显示图元属性
June 25, 2007
Using a modal .NET dialog to display AutoCAD object properties
Firstly, a big thanks for all your comments on the first anniversary post. It's good to know that people are finding this blog useful, and I hope the flow of ideas (internal and external) doesn't dry up anytime soon. So keep the comments coming! :-)
This post is going to start a sequence of posts that look at how to integrate forms into AutoCAD. This post looks at modal forms, and later on we'll look more at modeless forms and - in particular - palettes.
Just to be clear, these posts will focus on the basic integration - the more advanced activity of detailed property display (etc.) are left as an exercise for the reader. For example, in today's code we're simply going to get the type of an object and put that text into our dialog. Nothing very complex, but it shows the basic interaction between AutoCAD objects and WinForms.
Before we get started, I should very quickly define "modal" and "modeless", for those that aren't familiar with the terminology. Modal dialogs take exclusive control of an application's user-input functions, while modeless dialogs can co-exist with other modeless dialogs and user-input handling. Which means that the two types of dialog have different issues to deal with (in terms of how the assumptions they make on accessing data etc.).
So next we need to create our form... I'm not going to step through the process here - we'll focus on the code - so to make life easier I've packaged up the source here.
Here's the C# code for the command class. It's very simple: it checks the pickfirst selection and uses the first object as input for the form (assigning the object ID to the form, which will then go away and retrieve the object's type). At this stage we're just supporting one object - we're not going through the effort of determining shared properties across objects etc. We then use Application.ShowModalDialog() to show the form inside AutoCAD.
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using System;
  6. using CustomDialogs;
  7. namespace CustomDialogs
  8. {
  9.   public class Commands
  10.   {
  11.     [CommandMethod("vt",CommandFlags.UsePickSet)]
  12.     public void ViewType()
  13.     {
  14.       Editor ed =
  15.         Application.DocumentManager.MdiActiveDocument.Editor;
  16.       TypeViewerForm tvf = new TypeViewerForm();
  17.       PromptSelectionResult psr =
  18.         ed.GetSelection();
  19.       if (psr.Value.Count > 0)
  20.       {
  21.         ObjectId selId = psr.Value[0].ObjectId;
  22.         tvf.SetObjectId(selId);
  23.       }
  24.       if (psr.Value.Count > 1)
  25.       {
  26.         ed.WriteMessage(
  27.           "\nMore than one object was selected: only using the first.\n"
  28.         );
  29.       }
  30.       Application.ShowModalDialog(null, tvf, false);
  31.     }
  32.   }
  33. }
The form itself is a little more complex (but barely). It contains a function that can be used to set the active object (by its ID, as mentioned above) which goes away and opens the object, getting its type. The form also contains a "browse" button, which can be used to change the actively selected object.
Here's the C# code for the form:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.ComponentModel;
  8. using System.Data;
  9. using System.Drawing;
  10. using System.Text;
  11. using System.Windows.Forms;
  12. namespace CustomDialogs
  13. {
  14.   public partial class TypeViewerForm : Form
  15.   {
  16.     public TypeViewerForm()
  17.     {
  18.       InitializeComponent();
  19.     }
  20.     public void SetObjectText(string text)
  21.     {
  22.       typeTextBox.Text = text;
  23.     }
  24.     public void SetObjectId(ObjectId id)
  25.     {
  26.       if (id == ObjectId.Null)
  27.       {
  28.         SetObjectText("");
  29.       }
  30.       else
  31.       {
  32.         Document doc =
  33.           Autodesk.AutoCAD.ApplicationServices.
  34.             Application.DocumentManager.MdiActiveDocument;
  35.         Transaction tr =
  36.           doc.TransactionManager.StartTransaction();
  37.         using (tr)
  38.         {
  39.           DBObject obj = tr.GetObject(id, OpenMode.ForRead);
  40.           SetObjectText(obj.GetType().ToString());
  41.           tr.Commit();
  42.         }
  43.       }
  44.     }
  45.     private void closeButton_Click(object sender, EventArgs e)
  46.     {
  47.       this.Close();
  48.     }
  49.     private void browseButton_Click(object sender, EventArgs e)
  50.     {
  51.       DocumentCollection dm =
  52.         Autodesk.AutoCAD.ApplicationServices.
  53.           Application.DocumentManager;
  54.       Editor ed =
  55.         dm.MdiActiveDocument.Editor;
  56.       Hide();
  57.       PromptEntityResult per =
  58.         ed.GetEntity("\nSelect entity: ");
  59.       if (per.Status == PromptStatus.OK)
  60.       {
  61.         SetObjectId(per.ObjectId);
  62.       }
  63.       else
  64.       {
  65.         SetObjectId(ObjectId.Null);
  66.       }
  67.       Show();
  68.     }
  69.   }
  70. }
Here's what happens when you run the VT command and select a circle:

As an aside... I'm going on holiday on Wednesday for a week. We have our annual football (meaning soccer, of course) tournament being held this year in the UK. Teams from Autodesk offices from around the world (it used to have a European focus, but it's gained in popularity in the last few years) will fly in and take part. I'll be playing for one of the Swiss teams, and it'll certainly be lots of fun to catch up with old friends from other locations. The tournament is at the weekend, but I'm taking some days off either side to catch up with family and friends. I doubt I'll have time to queue up additional posts for while I'm away, so this is just to let you know that things may go a little quiet for the next week or so.

本帖子中包含更多资源

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

x
 楼主| 发表于 2009-5-26 17:37:00 | 显示全部楼层
七、在无模式对话框中显示图元属性
June 29, 2007
Using a modeless .NET dialog to display AutoCAD object properties
In this previous post we looked at creating a simple modal dialog and using it to display object properties. This post looks at the structural changes you need to make to your application for the same dialog to be used modelessly. In a later post we'll look at the benefits you get from leveraging the Palette system for modeless interaction inside AutoCAD.
Firstly, let's think about the interaction paradigm needed by a modeless dialog. A few things come to mind:
There is no longer a need to hide and show the dialog around selection
Rather than asking the user to select an entity, it's neater to respond to standard selection events
We no longer need a "browse" button
We now need to be more careful about document access
Our command automatically locked the document (and had sole access) in the modal example
We should now lock it "manually" when we access it
So we can already simplify our dialog class - here's some modified C# code, with the browse button removed and document-locking in place:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.ComponentModel;
  8. using System.Data;
  9. using System.Drawing;
  10. using System.Text;
  11. using System.Windows.Forms;
  12. namespace CustomDialogs
  13. {
  14.   public partial class TypeViewerForm : Form
  15.   {
  16.     public TypeViewerForm()
  17.     {
  18.       InitializeComponent();
  19.     }
  20.     public void SetObjectText(string text)
  21.     {
  22.       typeTextBox.Text = text;
  23.     }
  24.     public void SetObjectId(ObjectId id)
  25.     {
  26.       if (id == ObjectId.Null)
  27.       {
  28.         SetObjectText("");
  29.       }
  30.       else
  31.       {
  32.         Document doc =
  33.           Autodesk.AutoCAD.ApplicationServices.
  34.             Application.DocumentManager.MdiActiveDocument;
  35.         DocumentLock loc =
  36.           doc.LockDocument();
  37.         using (loc)
  38.         {
  39.           Transaction tr =
  40.             doc.TransactionManager.StartTransaction();
  41.           using (tr)
  42.           {
  43.             DBObject obj = tr.GetObject(id, OpenMode.ForRead);
  44.             SetObjectText(obj.GetType().ToString());
  45.             tr.Commit();
  46.           }
  47.         }
  48.       }
  49.     }
  50.     private void closeButton_Click(object sender, EventArgs e)
  51.     {
  52.       this.Close();
  53.     }
  54.   }
  55. }
So which event should we respond to, to find out when objects are selected? In this case I chose a PointMonitor - this class tells you a lot of really useful information about the current selection process. It also has the advantage of picking up the act of hovering over objects - no need for selection to actually happen. One other fun option would have been to use a Database event (ObjectAppended) to display information about objects as they are added to the drawing.
A few other comments about the code:
Predictably enough we now use ShowModelessDialog rather than ShowModalDialog()
We have our form as a member variable of the class, as its lifespan goes beyond the command we use to show it
I've also removed the selection code; we're no longer asking for objects to be selected
Here's the updated command implementation:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using System;
  6. using CustomDialogs;
  7. namespace CustomDialogs
  8. {
  9.   public class Commands
  10.   {
  11.     TypeViewerForm tvf;
  12.     public Commands()
  13.     {
  14.       tvf = new TypeViewerForm();
  15.       Document doc =
  16.         Application.DocumentManager.MdiActiveDocument;
  17.       Editor ed = doc.Editor;
  18.       ed.PointMonitor +=
  19.         new PointMonitorEventHandler(OnMonitorPoint);
  20.     }
  21.     ~Commands()
  22.     {
  23.       try
  24.       {
  25.         tvf.Dispose();
  26.         Document doc =
  27.           Application.DocumentManager.MdiActiveDocument;
  28.         Editor ed = doc.Editor;
  29.         ed.PointMonitor -=
  30.           new PointMonitorEventHandler(OnMonitorPoint);
  31.       }
  32.       catch(System.Exception)
  33.       {
  34.         // The editor may no longer
  35.         // be available on unload
  36.       }
  37.     }
  38.     private void OnMonitorPoint(
  39.       object sender,
  40.       PointMonitorEventArgs e
  41.     )
  42.     {
  43.       FullSubentityPath[] paths =
  44.         e.Context.GetPickedEntities();
  45.       if (paths.Length <= 0)
  46.       {
  47.         tvf.SetObjectId(ObjectId.Null);
  48.         return;
  49.       };
  50.       ObjectId[] objs = paths[0].GetObjectIds();
  51.       if (objs.Length <= 0)
  52.       {
  53.         tvf.SetObjectId(ObjectId.Null);
  54.         return;
  55.       };
  56.       // Set the "selected" object to be the last in the list
  57.       tvf.SetObjectId(objs[objs.Length - 1]);
  58.     }
  59.     [CommandMethod("vt",CommandFlags.UsePickSet)]
  60.     public void ViewType()
  61.     {
  62.       Application.ShowModelessDialog(null, tvf, false);
  63.     }
  64.   }
  65. }
And here's the source project for this version of the application. When you run the application you may experience issues with the dialog getting/retaining focus - this is generally a problem with modeless dialogs that has been addressed automatically by the Palette class, something we'll take a look at in a future post.

 楼主| 发表于 2009-5-31 21:50:00 | 显示全部楼层
七、在面板中显示图元属性
July 16, 2007
Using a palette from .NET to display properties of multiple AutoCAD objects
After a brief interlude we're back on the series of posts showing how to implement basic user-interfaces inside AutoCAD using .NET. Here's the series so far:
Using a modal .NET dialog to display AutoCAD object properties
Using a modeless .NET dialog to display AutoCAD object properties
Using a modeless .NET dialog to display properties of multiple AutoCAD objects
In this post we're going to swap out the modeless form we've been using in the last few posts in the series and replace it with an instance of AutoCAD's in-built palette class (Autodesk.AutoCAD.Windows.PaletteSet).
Firstly, why bother? Well, the PaletteSet class is realy cool: it provides docking, auto-hide, transparency and fixes the annoying focus-related issues we see with normal modeless dialogs.
And the best is that you get all this basically for free - the implementation work needed is really minimal. I started by copying liberal amounts of code from the DockingPalette sample on the ObjectARX SDK, and then deleted what wasn't needed for this project (which turned out to be most of it).
Here's the updated Command implementation. This really has very minor changes, as the palette implementation is all hidden inside the new TypeViewerPalette class.
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. using System;
  6. using CustomDialogs;
  7. namespace CustomDialogs
  8. {
  9.   public class Commands : IExtensionApplication
  10.   {
  11.     static TypeViewerPalette tvp;
  12.     public void Initialize()
  13.     {
  14.       tvp = new TypeViewerPalette();
  15.       DocumentCollection dm =
  16.         Application.DocumentManager;
  17.       dm.DocumentCreated +=
  18.         new DocumentCollectionEventHandler(OnDocumentCreated);
  19.       foreach (Document doc in dm)
  20.       {
  21.         doc.Editor.PointMonitor +=
  22.           new PointMonitorEventHandler(OnMonitorPoint);
  23.       }
  24.     }
  25.     public void Terminate()
  26.     {
  27.       try
  28.       {
  29.         DocumentCollection dm =
  30.           Application.DocumentManager;
  31.         if (dm != null)
  32.         {
  33.           Editor ed = dm.MdiActiveDocument.Editor;
  34.           ed.PointMonitor -=
  35.             new PointMonitorEventHandler(OnMonitorPoint);
  36.         }
  37.       }
  38.       catch (System.Exception)
  39.       {
  40.         // The editor may no longer
  41.         // be available on unload
  42.       }
  43.     }
  44.     private void OnDocumentCreated(
  45.       object sender,
  46.       DocumentCollectionEventArgs e
  47.     )
  48.     {
  49.       e.Document.Editor.PointMonitor +=
  50.         new PointMonitorEventHandler(OnMonitorPoint);
  51.     }
  52.     private void OnMonitorPoint(
  53.       object sender,
  54.       PointMonitorEventArgs e
  55.     )
  56.     {
  57.       FullSubentityPath[] paths =
  58.         e.Context.GetPickedEntities();
  59.       if (paths.Length <= 0)
  60.       {
  61.         tvp.SetObjectId(ObjectId.Null);
  62.         return;
  63.       };
  64.       ObjectIdCollection idc = new ObjectIdCollection();
  65.       foreach (FullSubentityPath path in paths)
  66.       {
  67.         // Just add the first ID in the list from each path
  68.         ObjectId[] ids = path.GetObjectIds();
  69.         idc.Add(ids[0]);
  70.       }
  71.       tvp.SetObjectIds(idc);
  72.     }
  73.     [CommandMethod("vt",CommandFlags.UsePickSet)]
  74.     public void ViewType()
  75.     {
  76.       tvp.Show();
  77.     }
  78.   }
  79. }
As for the TypeViewerPalette class: I started by migrating the SetObjectId()/SetObjectText() protocol across from the old TypeViewerForm class - the most complicated part of which involved exposing the contents of our palette (which we define and load as a User Control) via a member variable that can be accessed from SetObjectText(). Other than that it was all just copy & paste.
Here's the C# code:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Interop;
  5. using Autodesk.AutoCAD.Interop.Common;
  6. using Autodesk.AutoCAD.Windows;
  7. using TypeViewer;
  8. namespace CustomDialogs
  9. {
  10.   public class TypeViewerPalette
  11.   {
  12.     // We cannot derive from PaletteSet
  13.     // so we contain it
  14.     static PaletteSet ps;
  15.     // We need to make the textbox available
  16.     // via a static member
  17.     static TypeViewerControl tvc;
  18.     public TypeViewerPalette()
  19.     {
  20.       tvc = new TypeViewerControl();
  21.     }
  22.     public void Show()
  23.     {
  24.       if (ps == null)
  25.       {
  26.         ps = new PaletteSet("Type Viewer");
  27.         ps.Style =
  28.           PaletteSetStyles.NameEditable |
  29.           PaletteSetStyles.ShowPropertiesMenu |
  30.           PaletteSetStyles.ShowAutoHideButton |
  31.           PaletteSetStyles.ShowCloseButton;
  32.         ps.MinimumSize =
  33.           new System.Drawing.Size(300, 300);
  34.         ps.Add("Type Viewer 1", tvc);
  35.       }
  36.       ps.Visible = true;
  37.     }
  38.     public void SetObjectText(string text)
  39.     {
  40.       tvc.typeTextBox.Text = text;
  41.     }
  42.     public void SetObjectIds(ObjectIdCollection ids)
  43.     {
  44.       if (ids.Count < 0)
  45.       {
  46.         SetObjectText("");
  47.       }
  48.       else
  49.       {
  50.         Document doc =
  51.           Autodesk.AutoCAD.ApplicationServices.
  52.             Application.DocumentManager.MdiActiveDocument;
  53.         DocumentLock loc =
  54.           doc.LockDocument();
  55.         using (loc)
  56.         {
  57.           string info =
  58.             "Number of objects: " +
  59.             ids.Count.ToString() + "\r\n";
  60.           Transaction tr =
  61.             doc.TransactionManager.StartTransaction();
  62.           using (tr)
  63.           {
  64.             foreach (ObjectId id in ids)
  65.             {
  66.               Entity ent =
  67.                 (Entity)tr.GetObject(id, OpenMode.ForRead);
  68.               Solid3d sol = ent as Solid3d;
  69.               if (sol != null)
  70.               {
  71.                 Acad3DSolid oSol =
  72.                   (Acad3DSolid)sol.AcadObject;
  73.                 // Put in a try-catch block, as it's possible
  74.                 // for solids to not support this property,
  75.                 // it seems (better safe than sorry)
  76.                 try
  77.                 {
  78.                   string solidType = oSol.SolidType;
  79.                   info +=
  80.                     ent.GetType().ToString() +
  81.                     " (" + solidType + ") : " +
  82.                     ent.ColorIndex.ToString() + "\r\n";
  83.                 }
  84.                 catch (System.Exception)
  85.                 {
  86.                   info +=
  87.                     ent.GetType().ToString() +
  88.                     " : " +
  89.                     ent.ColorIndex.ToString() + "\r\n";
  90.                 }
  91.               }
  92.               else
  93.               {
  94.                 info +=
  95.                   ent.GetType().ToString() +
  96.                   " : " +
  97.                   ent.ColorIndex.ToString() + "\r\n";
  98.               }
  99.             }
  100.             tr.Commit();
  101.           }
  102.           SetObjectText(info);
  103.         }
  104.       }
  105.     }
  106.     public void SetObjectId(ObjectId id)
  107.     {
  108.       if (id == ObjectId.Null)
  109.       {
  110.         SetObjectText("");
  111.       }
  112.       else
  113.       {
  114.         Document doc =
  115.           Autodesk.AutoCAD.ApplicationServices.
  116.             Application.DocumentManager.MdiActiveDocument;
  117.         DocumentLock loc =
  118.           doc.LockDocument();
  119.         using (loc)
  120.         {
  121.           Transaction tr =
  122.             doc.TransactionManager.StartTransaction();
  123.           using (tr)
  124.           {
  125.             DBObject obj =
  126.               tr.GetObject(id, OpenMode.ForRead);
  127.             SetObjectText(obj.GetType().ToString());
  128.             tr.Commit();
  129.           }
  130.         }
  131.       }
  132.     }
  133.   }
  134. }
Here's what you get when you run the VT command and manipulate the palette's docking/transparency before hovering over a set of drawing objects:

本帖子中包含更多资源

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

x
 楼主| 发表于 2009-5-31 21:53:00 | 显示全部楼层
本帖最后由 作者 于 2009-6-1 11:31:42 编辑

八、线型创建
January 07, 2008
Creating an AutoCAD linetype programmatically using .NET
Happy New Year, everyone!
I had a very relaxing break over the holiday period: during the better part of two weeks I managed to stay away from my PC and even my BlackBerry, which I admit I'm generally not very good at ignoring. So now I'm easing back into the swing of things, remembering how to type and open modelspaces, among other things. :-)
To save my brain from unnecessary stress during the first few weeks of 2008, I've decided to take inspiration from the ADN website. Today's post is based on this DevNote, which shows how to create a custom linetype using ObjectARX.
Here's some equivalent code in C#:
  1. using Autodesk.AutoCAD.Runtime;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.Geometry;
  5. using Autodesk.AutoCAD.EditorInput;
  6. namespace Linetype
  7. {
  8.   public class Commands
  9.   {
  10.     [CommandMethod("CL")]
  11.     public void CreateLinetype()
  12.     {
  13.       Document doc =
  14.         Application.DocumentManager.MdiActiveDocument;
  15.       Database db = doc.Database;
  16.       Editor ed = doc.Editor;
  17.       Transaction tr =
  18.         db.TransactionManager.StartTransaction();
  19.       using (tr)
  20.       {
  21.         // Get the linetype table from the drawing
  22.         LinetypeTable lt =
  23.           (LinetypeTable)tr.GetObject(
  24.             db.LinetypeTableId,
  25.             OpenMode.ForWrite
  26.           );
  27.         // Create our new linetype table record...
  28.         LinetypeTableRecord ltr =
  29.           new LinetypeTableRecord();
  30.         // ... and set its properties
  31.         ltr.AsciiDescription = "T E S T -";
  32.         ltr.PatternLength = 0.75;
  33.         ltr.NumDashes = 2;
  34.         ltr.SetDashLengthAt(0, 0.5);
  35.         ltr.SetDashLengthAt(1,-0.25);
  36.         ltr.Name = "TESTLINTEYPE";
  37.         // Add the new linetype to the linetype table
  38.         ObjectId ltId = lt.Add(ltr);
  39.         tr.AddNewlyCreatedDBObject(ltr,true);
  40.         // Create a test line with this linetype
  41.         BlockTable bt =
  42.           (BlockTable)tr.GetObject(
  43.             db.BlockTableId,
  44.             OpenMode.ForRead
  45.           );
  46.         BlockTableRecord btr =
  47.           (BlockTableRecord)tr.GetObject(
  48.             bt[BlockTableRecord.ModelSpace],
  49.             OpenMode.ForWrite
  50.           );
  51.         Line ln =
  52.           new Line(
  53.             new Point3d(0, 0, 0),
  54.             new Point3d(10, 10, 0)
  55.           );
  56.         ln.SetDatabaseDefaults(db);
  57.         ln.LinetypeId = ltId;
  58.         btr.AppendEntity(ln);
  59.         tr.AddNewlyCreatedDBObject(ln, true);
  60.         tr.Commit();
  61.       }
  62.     }
  63.   }
  64. }
Once you've run the CL command, Zoom Extents to see a line that has been created with our new custom linetype.

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

本版积分规则

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

GMT+8, 2024-12-23 21:27 , Processed in 0.195882 second(s), 25 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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