雪山飞狐_lzh 发表于 2009-6-1 11:20:00

九、建立一个包含文字的复杂线型
January 09, 2008
Creating a complex AutoCAD linetype containing text using .NET
In my last post we saw some code to create a simple linetype using .NET. As a comment on that post, Mark said:
Kean, i tried you code and it works great and it also got me thinking... is it possible to programmitically add text in as well? I've tried using ltr.SetTextAt(1, "TEST") but so far i've had no luck, any suggestions???
It turned out to be quite a bit more complicated to make a linetype containing text than merely calling SetTextAt() on one of the segments. In order to understand what properties needed setting, I first loaded the HOT_WATER_SUPPLY linetype from acad.lin (using the LINETYPE command):

I then looked at the contents of the linetype table using ArxDbg (the ObjectARX SDK sample that is very helpful for understanding drawing structure). Here's what the SNOOPDB command - defined by the ArxDbg application - showed for the loaded linetype:
From there it was fairly straightforward to determine the code needed to create our own complex linetype containing text segments. I decided to call the new linetype "COLD_WATER_SUPPLY", and have it resemble the original in every way but placing "CW" in the middle segment, rather than "HW" (with the descriptions updated to match, of course). As I've simply copied the properties of an existing linetype, please don't ask me to explain what they all mean. :-)
Here's the C# code:
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.EditorInput;
namespace Linetype
{
public class Commands
{
   
    public void CreateComplexLinetype()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      Transaction tr =
      db.TransactionManager.StartTransaction();
      using (tr)
      {
      // We'll use the textstyle table to access
      // the "Standard" textstyle for our text
      // segment
      TextStyleTable tt =
          (TextStyleTable)tr.GetObject(
            db.TextStyleTableId,
            OpenMode.ForRead
          );
      // Get the linetype table from the drawing
      LinetypeTable lt =
          (LinetypeTable)tr.GetObject(
            db.LinetypeTableId,
            OpenMode.ForWrite
          );
      // Create our new linetype table record...
      LinetypeTableRecord ltr =
          new LinetypeTableRecord();
      // ... and set its properties
      ltr.Name = "COLD_WATER_SUPPLY";
      ltr.AsciiDescription =
          "Cold water supply ---- CW ---- CW ---- CW ----";
      ltr.PatternLength = 0.9;
      ltr.NumDashes = 3;
      // Dash #1
      ltr.SetDashLengthAt(0, 0.5);
      // Dash #2
      ltr.SetDashLengthAt(1, -0.2);
      ltr.SetShapeStyleAt(1, tt["Standard"]);
      ltr.SetShapeNumberAt(1, 0);
      ltr.SetShapeOffsetAt(1, new Vector2d(-0.1,-0.05));
      ltr.SetShapeScaleAt(1, 0.1);
      ltr.SetShapeIsUcsOrientedAt(1, false);
      ltr.SetShapeRotationAt(1, 0);
      ltr.SetTextAt(1, "CW");
      // Dash #3
      ltr.SetDashLengthAt(2, -0.2);
      // Add the new linetype to the linetype table
      ObjectId ltId = lt.Add(ltr);
      tr.AddNewlyCreatedDBObject(ltr, true);
      // Create a test line with this linetype
      BlockTable bt =
          (BlockTable)tr.GetObject(
            db.BlockTableId,
            OpenMode.ForRead
          );
      BlockTableRecord btr =
          (BlockTableRecord)tr.GetObject(
            bt,
            OpenMode.ForWrite
          );
      Line ln =
          new Line(
            new Point3d(0, 0, 0),
            new Point3d(10, 10, 0)
          );
      ln.SetDatabaseDefaults(db);
      ln.LinetypeId = ltId;
      btr.AppendEntity(ln);
      tr.AddNewlyCreatedDBObject(ln, true);
      tr.Commit();
      }
    }
}
}And here's the result of calling the CCL command and zooming in on the created line:

雪山飞狐_lzh 发表于 2009-6-1 11:21:00

<p>January 11, 2008<br/>Understanding the properties of textual linetype segments in AutoCAD<br/>In the last post we looked at using .NET to define complex linetypes containing text segments. In the post I admitted to not knowing specifics about the properties used to create the text segment in the linetype, and, in the meantime, an old friend took pity on me and came to the rescue. :-)</p><p>Mike Kehoe, who I've known for many years since we worked together in the Guildford office of Autodesk UK, sent me some information that I've reproduced below. Mike now works for Micro Concepts Ltd., an Autodesk reseller, developer and training centre. He originally wrote the below description in the R12/12 timeframe, but apparently most of it remains valid; and while it refers to the text string used to define a linetype in a .lin file, these are also mostly properties that are exposed via the .NET interface.</p><p>Example: Using Text within a Linetype.<br/>A,.5,-.2,["MK",STANDARD,S=.2,R=0.0,X=-0.1,Y=-.1],-.2</p><p>The key elements for defining the TEXT are as follows:</p><p>"MK" - These are the letters that will be printed along the line.</p><p>STANDARD -This tells AutoCAD what text style to apply to the text.&nbsp; NB: This is optional. When no style is defined AutoCAD will use the current text style – TextStyle holds the setting for the current text style.</p><p></p><p>S=.2 - This is the text scaling factor. However, there are 2 options: (1) when the text style's height is 0, then S defines the height; in this case, 0.2 units; or (2) when the text style's height parameter is non-zero, the height is found by multiplying the text style's height by this number; in this case, the linetype would place the text at 20% of the height defined in the text style.</p><p>R=0.0 - This rotates the text relative to the direction of the line; e.g.: 0.0 means there is no rotation. NB: This is optional. When no rotation is defined AutoCAD will assume zero degrees. The default measurement is degrees; NB: you can use r to specify radians, g for grads, or d for degrees, such as R=150g.</p><p></p><p>A=0.0&nbsp; - This rotates the text relative to the x-axis ("A" is short for Absolute); this ensures the text is always oriented in the same direction, no matter the direction of the line. The rotation is always performed within the text baseline and capital height. That's so that you don't get text rotated way off near the orbit of Pluto.</p><p></p><p>X=-0.1 - This setting moves the text just in the x-direction from the linetype definition vertex.</p><p>Y=-0.1 – This setting moves the text in the y-direction from the linetype definition vertex.<br/>These 2 settings can be used to center the text in the line. The units are defined from the linetype scale factor, which is stored in system variable LtScale.</p><p>Thanks for the information, Mike!</p><p></p>

雪山飞狐_lzh 发表于 2009-6-5 14:01:00

本帖最后由 作者 于 2009-6-7 15:39:46 编辑

十、使用AutoCad的颜色、线型、线宽对话框
February 08, 2008
Using standard AutoCAD dialogs to select colors, linetypes and lineweights with .NET
AutoCAD has a number of handy dialogs available in the "Autodesk.AutoCAD.Windows" namespace. The following code shows how to use three, in particular: ColorDialog, LinetypeDialog and LineWeightDialog. These three classes allow you to very easily implement user-interfaces selecting their corresponding properties.
Here's the C# code:
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Windows;
namespace AutoCADDialogs
{
public class Commands
{
   
    public void ShowColorDialog()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      ColorDialog cd = new ColorDialog();
      System.Windows.Forms.DialogResult dr =
      cd.ShowDialog();
      if (dr == System.Windows.Forms.DialogResult.OK)
      {
      ed.WriteMessage(
          "\nColor selected: " +
          cd.Color.ToString()
      );
      }
    }
   
    public void ShowLinetypeDialog()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      LinetypeDialog ltd = new LinetypeDialog();
      System.Windows.Forms.DialogResult dr =
      ltd.ShowDialog();
      if (dr == System.Windows.Forms.DialogResult.OK)
      {
      ed.WriteMessage(
          "\nLinetype selected: " +
          ltd.Linetype.ToString()
      );
      }
    }
   
    public void ShowLineWeightDialog()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      LineWeightDialog lwd = new LineWeightDialog();
      System.Windows.Forms.DialogResult dr =
      lwd.ShowDialog();
      if (dr == System.Windows.Forms.DialogResult.OK)
      {
      ed.WriteMessage(
          "\nLineweight selected: " +
          lwd.LineWeight.ToString()
      );
      }
    }
}
}
When we run the CDL command, we have three tabs available:



Here's the dialog shown by the LTDL command:

And the LWDL command:

And here is the command-line output for selecting each of the above items:
Command: CDL
Color selected: 91
Command: CDL
Color selected: 56,166,199
Command: CDL
Color selected: DIC 266
Command: LTDL
Linetype selected: (2130316464)
Command: LWDL
Lineweight selected: LineWeight009In the next post we'll look at at how to use these dialogs in a more realistic way.


雪山飞狐_lzh 发表于 2009-6-5 14:02:00

February 12, 2008
Modifying the color, linetype and lineweight of an AutoCAD entity using standard dialogs from .NET
In the last post we saw code to display and use AutoCAD's built-in colour, linetype and lineweight dialogs. In this post we extend that by using each of them in sequence to display various properties of an entity, allowing the user to modify them.
While this is slightly more "real world" than the last post, it doesn't really make sense to implement a command such as the one below (the property palette is much better, for instance :-). The main purpose is to show how to set the initial values in the various dialogs and afterwards to make use of the values the user selects.
Here's the C# code:
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Windows;
namespace EntityProperties
{
public class Commands
{
   
    public void SetPropertiesOnEntity()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      PromptEntityResult per =
      ed.GetEntity(
          "\nSelect entity to modify: "
      );
      if (per.Status != PromptStatus.OK)
      return;
      Transaction tr =
      db.TransactionManager.StartTransaction();
      using (tr)
      {
      Entity ent =
          (Entity)
            tr.GetObject(
            per.ObjectId,
            OpenMode.ForRead
            );
      ColorDialog cd = new ColorDialog();
      cd.Color = ent.Color;
      System.Windows.Forms.DialogResult dr;
      dr = cd.ShowDialog();
      if (dr != System.Windows.Forms.DialogResult.OK)
          return;
      LinetypeDialog ltd = new LinetypeDialog();
      ltd.Linetype = ent.LinetypeId;
      dr = ltd.ShowDialog();
      if (dr != System.Windows.Forms.DialogResult.OK)
          return;
      LineWeightDialog lwd = new LineWeightDialog();
      lwd.LineWeight = ent.LineWeight;
      dr = lwd.ShowDialog();
      if (dr != System.Windows.Forms.DialogResult.OK)
          return;
      ent.UpgradeOpen();
      if (ent.Color != cd.Color)
          ent.Color = cd.Color;
      if (ent.LinetypeId != ltd.Linetype)
          ent.LinetypeId = ltd.Linetype;
      if (ent.LineWeight != lwd.LineWeight)
          ent.LineWeight = lwd.LineWeight;
      tr.Commit();
      }
    }
}
}
Notice that we "return" from the code in a number of places, should the user cancel one of the dialogs. This is perfectly safe, as we're "using" the transaction object, and once it leaves scope it will automatically be disposed (and therefore aborted).
Running the SPE command will display in sequence the dialogs shown in the previous post, and use the user's input to change the state of the selected entity.

雪山飞狐_lzh 发表于 2009-6-5 14:03:00

本帖最后由 作者 于 2009-6-7 16:10:16 编辑

十一、在AutoCad2009中使用新的.NetApi从文件中提取XML数据
April 07, 2008
Extracting XML data from drawings using a new .NET API in AutoCAD 2009
This post is the latest in the series of closer looks at the new APIs in AutoCAD 2009. It covers the Data Extraction API, a new .NET API allowing you to drive the Data Extraction feature inside AutoCAD.
There is a very thorough C# sample included on the ObjectARX SDK (under samples/dotNet/DataExtraction), so I thought I'd focus on a much simpler example where we extract data from a single drawing.
Here's the C# code:
using System.Collections.Generic;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DataExtraction;
namespace DataExtraction
{
public class Commands
{
    const string path =
      @"c:\Program Files\Autodesk\AutoCAD 2009\Sample\";
    const string fileName =
      "Visualization - Aerial.dwg";
    const string outputXmlFile =
      @"c:\temp\data-extract.xml";
   
    public void extractData()
    {
      if (!System.IO.File.Exists(path + fileName))
      {
      Document doc =
          Application.DocumentManager.MdiActiveDocument;
      Editor ed =
          doc.Editor;
      ed.WriteMessage("\nFile does not exist.");
      return;
      }
      // Create some settings for the extraction
      IDxExtractionSettings es =
      new DxExtractionSettings();
      IDxDrawingDataExtractor de =
      es.DrawingDataExtractor;
      de.Settings.ExtractFlags =
      ExtractFlags.ModelSpaceOnly |
      ExtractFlags.XrefDependent |
      ExtractFlags.Nested;
      // Add a single file to the settings
      IDxFileReference fr =
      new DxFileReference(path, path + fileName);
      de.Settings.DrawingList.AddFile(fr);
      // Scan the drawing for object types & their properties
      de.DiscoverTypesAndProperties(path);
      List<IDxTypeDescriptor> types =
      de.DiscoveredTypesAndProperties;
      // Select all the types and properties for extraction
      // by adding them one-by-one to these two lists
      List<string> selTypes = new List<string>();
      List<string> selProps = new List<string>();
      foreach (IDxTypeDescriptor type in types)
      {
      selTypes.Add(type.GlobalName);
      foreach (
          IDxPropertyDescriptor pr in type.Properties
      )
      {
          if (!selProps.Contains(pr.GlobalName))
            selProps.Add(pr.GlobalName);
      }
      }
      // Pass this information to the extractor
      de.Settings.SetSelectedTypesAndProperties(
      types,
      selTypes,
      selProps
      );
      // Now perform the extraction itself
      de.ExtractData(path);
      // Get the results of the extraction
      System.Data.DataTable dataTable =
      de.ExtractedData;
      // Output the extracted data to an XML file
      if (dataTable.Rows.Count > 0)
      {
      dataTable.TableName = "My_Data_Extract";
      dataTable.WriteXml(outputXmlFile);
      }
    }
}
}
Here are the first few objects from the output file found in c:\temp\data-extract.xml after running the EXTD command:
<?xml version="1.0" standalone="yes"?>
<DocumentElement>
<My_Data_Extract>
    <AcDxHandleData>183</AcDxHandleData>
    <Layer>0</Layer>
    <LinetypeScale>1</LinetypeScale>
    <PlotStyleName>ByLayer</PlotStyleName>
    <LineWeight>ByLayer</LineWeight>
    <Material>Material 3</Material>
    <Linetype>ByLayer</Linetype>
    <Color>ByLayer</Color>
    <AcDxObjectTypeName>3D Solid</AcDxObjectTypeName>
    <AcDxObjectTypeGlobalName>
      Autodesk.AutoCAD.DatabaseServices.Solid3d
    </AcDxObjectTypeGlobalName>
    <AcDxDwgSummaryDwgName>
      Visualization - Aerial.dwg
    </AcDxDwgSummaryDwgName>
    <AcDxDwgSummaryDwgLocation>
      c:\Program Files\Autodesk\AutoCAD 2009\Sample
    </AcDxDwgSummaryDwgLocation>
    <AcDxDwgSummaryDwgSize>472480</AcDxDwgSummaryDwgSize>
    <AcDxDwgSummaryDwgCreated>
      2007-01-03T00:44:20+01:00
    </AcDxDwgSummaryDwgCreated>
    <AcDxDwgSummaryDwgModified>
      2007-01-03T00:44:20+01:00
    </AcDxDwgSummaryDwgModified>
    <AcDxDwgSummaryDwgAccessed>
      2008-03-03T11:40:53.796875+01:00
    </AcDxDwgSummaryDwgAccessed>
    <AcDxDwgSummaryDwgTitle />
    <AcDxDwgSummaryDwgSubject />
    <AcDxDwgSummaryDwgAuthor />
    <AcDxDwgSummaryDwgKeywords />
    <AcDxDwgSummaryDwgComments />
    <AcDxDwgSummaryDwgHyperLinkBase />
    <AcDxDwgSummaryDwgLastSavedBy>
      thompsl
    </AcDxDwgSummaryDwgLastSavedBy>
    <AcDxDwgSummaryDwgRevisionNumber />
    <AcDxDwgSummaryDwgTotalEditingTime>
      1179
    </AcDxDwgSummaryDwgTotalEditingTime>
    <AcDxEntityHyperlink />
</My_Data_Extract>
<My_Data_Extract>
    <AcDxHandleData>191</AcDxHandleData>
    <Layer>0</Layer>
    <LinetypeScale>1</LinetypeScale>
    <PlotStyleName>ByLayer</PlotStyleName>
    <LineWeight>ByLayer</LineWeight>
    <Material>Material 3</Material>
    <Linetype>ByLayer</Linetype>
    <Color>ByLayer</Color>
    <AcDxObjectTypeName>3D Solid</AcDxObjectTypeName>
    <AcDxObjectTypeGlobalName>
      Autodesk.AutoCAD.DatabaseServices.Solid3d
    </AcDxObjectTypeGlobalName>
    <AcDxDwgSummaryDwgName>
      Visualization - Aerial.dwg
    </AcDxDwgSummaryDwgName>
    <AcDxDwgSummaryDwgLocation>
      c:\Program Files\Autodesk\AutoCAD 2009\Sample
    </AcDxDwgSummaryDwgLocation>
    <AcDxDwgSummaryDwgSize>472480</AcDxDwgSummaryDwgSize>
    <AcDxDwgSummaryDwgCreated>
      2007-01-03T00:44:20+01:00
    </AcDxDwgSummaryDwgCreated>
    <AcDxDwgSummaryDwgModified>
      2007-01-03T00:44:20+01:00
    </AcDxDwgSummaryDwgModified>
    <AcDxDwgSummaryDwgAccessed>
      2008-03-03T11:40:53.796875+01:00
    </AcDxDwgSummaryDwgAccessed>
    <AcDxDwgSummaryDwgTitle />
    <AcDxDwgSummaryDwgSubject />
    <AcDxDwgSummaryDwgAuthor />
    <AcDxDwgSummaryDwgKeywords />
    <AcDxDwgSummaryDwgComments />
    <AcDxDwgSummaryDwgHyperLinkBase />
    <AcDxDwgSummaryDwgLastSavedBy>
      thompsl
    </AcDxDwgSummaryDwgLastSavedBy>
    <AcDxDwgSummaryDwgRevisionNumber />
    <AcDxDwgSummaryDwgTotalEditingTime>
      1179
    </AcDxDwgSummaryDwgTotalEditingTime>
    <AcDxEntityHyperlink />
</My_Data_Extract>
<!-- Stuff deleted from the XML output -->
<!-- ... -->
</DocumentElement>

雪山飞狐_lzh 发表于 2009-6-5 14:05:00

本帖最后由 作者 于 2009-6-7 16:11:06 编辑

十二、图层过滤器
July 11, 2008
Adding and removing AutoCAD layer filters using .NET
This question came in by email last week:
The last two days I am struggling with adding a new LayerGroup to HostApplicationServices.WorkingDatabase.LayerFilters.Root. I simply cannot get it working. Is it possible to write a blog item with example on this?
An interesting question and it can indeed be quite tricky to understand how this works. For those of you who are ADN members, here's a DevNote covering the basics, which I've taken and extended for this post.
When working with LayerFilters, it's important to know that the Database property providing access to the list of current LayerFilters does so by value (i.e. you have to get a copy of the current set of LayerFilters, manipulate it, and then set it back on the Database object to have them picked up by the system).
The below C# code implements commands to list the existing layer filters (LLFS), create three new layer filters (CLFS), and delete a layer filter selected by the user (DLF):
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.LayerManager;
namespace LayerFilters
{
public class Commands
{
   
    static public void ListLayerFilters()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      // List the nested layer filters
      LayerFilterCollection lfc =
      db.LayerFilters.Root.NestedFilters;
      for (int i = 0; i < lfc.Count; ++i)
      {
      LayerFilter lf = lfc;
      ed.WriteMessage(
          "\n{0} - {1} (can{2} be deleted)",
          i + 1,
          lf.Name,
          (lf.AllowDelete ? "" : "not")
      );
      }
    }
   
    static public void CreateLayerFilters()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      try
      {
      // Get the existing layer filters
      // (we will add to them and set them back)
      LayerFilterTree lft =
          db.LayerFilters;
      LayerFilterCollection lfc =
          lft.Root.NestedFilters;
      // Create three new layer filters
      LayerFilter lf1 = new LayerFilter();
      lf1.Name = "Unlocked Layers";
      lf1.FilterExpression = "LOCKED==\"False\"";
      LayerFilter lf2 = new LayerFilter();
      lf2.Name = "White Layers";
      lf2.FilterExpression = "COLOR==\"7\"";
      LayerFilter lf3 = new LayerFilter();
      lf3.Name = "Visible Layers";
      lf3.FilterExpression =
          "OFF==\"False\" AND FROZEN==\"False\"";
      // Add them to the collection
      lfc.Add(lf1);
      lfc.Add(lf2);
      lfc.Add(lf3);
      // Set them back on the Database
      db.LayerFilters = lft;
      // List the layer filters, to see the new ones
      ListLayerFilters();
      }
      catch (Exception ex)
      {
      ed.WriteMessage(
          "\nException: {0}",
          ex.Message
      );
      }
    }
   
    static public void DeleteLayerFilter()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      ListLayerFilters();
      try
      {
      // Get the existing layer filters
      // (we will add to them and set them back)
      LayerFilterTree lft =
          db.LayerFilters;
      LayerFilterCollection lfc =
          lft.Root.NestedFilters;
      // Prompt for the index of the filter to delete
      PromptIntegerOptions pio =
          new PromptIntegerOptions(
          "\n\nEnter index of filter to delete"
          );
      pio.LowerLimit = 1;
      pio.UpperLimit = lfc.Count;
      PromptIntegerResult pir =
          ed.GetInteger(pio);
      // Get the selected filter
      LayerFilter lf = lfc;
      // If it's possible to delete it, do so
      if (!lf.AllowDelete)
      {
          ed.WriteMessage(
            "\nLayer filter cannot be deleted."
          );
      }
      else
      {
          lfc.Remove(lf);
          db.LayerFilters = lft;
          ListLayerFilters();
      }
      }
      catch(Exception ex)
      {
      ed.WriteMessage(
          "\nException: {0}",
          ex.Message
      );
      }
    }
}
}
Here's what happens when we run the various commands:
Command: LLFS
1 - All Used Layers (cannot be deleted)
2 - Unreconciled New Layers (cannot be deleted)
3 - Viewport Overrides (cannot be deleted)
Command: CLFS
1 - Unlocked Layers (can be deleted)
2 - White Layers (can be deleted)
3 - Visible Layers (can be deleted)
4 - All Used Layers (cannot be deleted)
5 - Unreconciled New Layers (cannot be deleted)
6 - Viewport Overrides (cannot be deleted)
Command: DLF
1 - Unlocked Layers (can be deleted)
2 - White Layers (can be deleted)
3 - Visible Layers (can be deleted)
4 - All Used Layers (cannot be deleted)
5 - Unreconciled New Layers (cannot be deleted)
6 - Viewport Overrides (cannot be deleted)
Enter index of filter to delete: 1
1 - White Layers (can be deleted)
2 - Visible Layers (can be deleted)
3 - All Used Layers (cannot be deleted)
4 - Unreconciled New Layers (cannot be deleted)
5 - Viewport Overrides (cannot be deleted)
You can see the three new filters (before you delete any with the DLF command) in the LAYER dialog. To see them work, create a bunch of layers, changing the colour of some and turning off/freezing/locking others:

雪山飞狐_lzh 发表于 2009-6-7 16:08:00

September 24, 2008
Creating a layer group inside AutoCAD using .NET
The following question came in as a comment on this previous post:
Your example above shows how to create a property type filter, how do you add layers to a group type filter (LayerGroup class)? I can create the group filter but can't figure out how to add layers to the group. The filterexpression method is available but doesn't seem to work (at least not like with the LayerFilter object)
To tackle this, let's start by looking at the documentation on the LayerGroup class in the .NET Reference (currently part of the ObjectARX Reference):
LayerGroup is derived from LayerFilter and serves as the access to layer group filters. It allows the client to specify and retrieve a set of layer IDs. The filter() method returns true if the object ID of the given layer is contained in the set of layer IDs for the LayerGroup.
Specifying the filter criteria is done solely by using LayerId. LayerGroup doesn't use a filter expression string, so FilterExpression and FilterExpressionTree return a null pointer.
The recommended way of identifying LayerGroup filters in a group of layer filters is to query the IsIdFilter property, which returns true for an LayerGroup.
So, at the root of the question, we need to create a LayerGroup and add the ObjectIds of the LayerTableRecords we wish to include in the group to its LayerIds property.
I started by taking the code in the post referred to above and extended it to contain a new CLG command to Create a Layer Group. This command lists the available layers for the user to select from, and creates a layer group containing the selected layers.
Here's the updated C# code - pay particular attention to the new CLG command, of course:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.LayerManager;
using System.Collections.Generic;
namespace LayerFilters
{
public class Commands
{
   
    static public void ListLayerFilters()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      // List the nested layer filters
      LayerFilterCollection lfc =
      db.LayerFilters.Root.NestedFilters;
      for (int i = 0; i < lfc.Count; ++i)
      {
      LayerFilter lf = lfc;
      ed.WriteMessage(
          "\n{0} - {1} (can{2} be deleted)",
          i + 1,
          lf.Name,
          (lf.AllowDelete ? "" : "not")
      );
      }
    }
   
    static public void CreateLayerFilters()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      try
      {
      // Get the existing layer filters
      // (we will add to them and set them back)
      LayerFilterTree lft =
          db.LayerFilters;
      LayerFilterCollection lfc =
          lft.Root.NestedFilters;
      // Create three new layer filters
      LayerFilter lf1 = new LayerFilter();
      lf1.Name = "Unlocked Layers";
      lf1.FilterExpression = "LOCKED==\"False\"";
      LayerFilter lf2 = new LayerFilter();
      lf2.Name = "White Layers";
      lf2.FilterExpression = "COLOR==\"7\"";
      LayerFilter lf3 = new LayerFilter();
      lf3.Name = "Visible Layers";
      lf3.FilterExpression =
          "OFF==\"False\" AND FROZEN==\"False\"";
      // Add them to the collection
      lfc.Add(lf1);
      lfc.Add(lf2);
      lfc.Add(lf3);
      // Set them back on the Database
      db.LayerFilters = lft;
      // List the layer filters, to see the new ones
      ListLayerFilters();
      }
      catch (Exception ex)
      {
      ed.WriteMessage(
          "\nException: {0}",
          ex.Message
      );
      }
    }
   
    static public void CreateLayerGroup()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      // A list of the layers' names & IDs contained
      // in the current database, sorted by layer name
      SortedList<string, ObjectId> ld =
      new SortedList<string, ObjectId>();
      // A list of the selected layers' IDs
      ObjectIdCollection lids =
      new ObjectIdCollection();
      // Start by populating the list of names/IDs
      // from the LayerTable
      Transaction tr =
      db.TransactionManager.StartTransaction();
      using (tr)
      {
      LayerTable lt =
          (LayerTable)tr.GetObject(
            db.LayerTableId,
            OpenMode.ForRead
          );
      foreach(ObjectId lid in lt)
      {
          LayerTableRecord ltr =
            (LayerTableRecord)tr.GetObject(
            lid,
            OpenMode.ForRead
          );
          ld.Add(ltr.Name, lid);
      }
      }
      // Display a numbered list of the available layers
      ed.WriteMessage("\nLayers available for group:");
      int i = 1;
      foreach (KeyValuePair<string,ObjectId> kv in ld)
      {
      ed.WriteMessage(
          "\n{0} - {1}",
          i++,
          kv.Key
      );
      }
      // We will ask the user to select from the list
      PromptIntegerOptions pio =
      new PromptIntegerOptions(
          "\nEnter number of layer to add: "
      );
      pio.LowerLimit = 1;
      pio.UpperLimit = ld.Count;
      pio.AllowNone = true;
      // And will do so in a loop, waiting for
      // Escape or Enter to terminate
      PromptIntegerResult pir;
      do
      {
      // Select one from the list
      pir = ed.GetInteger(pio);
      if (pir.Status == PromptStatus.OK)
      {
          // Get the layer's name
          string ln =
            ld.Keys;
          // And then its ID
          ObjectId lid;
          ld.TryGetValue(ln, out lid);
          // Add the layer'd ID to the list, is it's not
          // already on it
          if (lids.Contains(lid))
          {
            ed.WriteMessage(
            "\nLayer \"{0}\" has already been selected.",
            ln
            );
          }
          else
          {
            lids.Add(lid);
            ed.WriteMessage(
            "\nAdded \"{0}\" to selected layers.",
            ln
            );
          }
      }
      } while (pir.Status == PromptStatus.OK);
      // Now we've selected our layers, let's create the group
      try
      {
      if (lids.Count > 0)
      {
          // Get the existing layer filters
          // (we will add to them and set them back)
          LayerFilterTree lft =
            db.LayerFilters;
          LayerFilterCollection lfc =
            lft.Root.NestedFilters;
          // Create a new layer group
          LayerGroup lg = new LayerGroup();
          lg.Name = "My Layer Group";
          // Add our layers' IDs to the list
          foreach (ObjectId id in lids)
            lg.LayerIds.Add(id);
          // Add the group to the collection
          lfc.Add(lg);
          // Set them back on the Database
          db.LayerFilters = lft;
          ed.WriteMessage(
            "\n\"{0}\" group created containing {1} layers.\n",
            lg.Name,
            lids.Count
          );
          // List the layer filters, to see the new group
          ListLayerFilters();
      }
      }
      catch (Exception ex)
      {
      ed.WriteMessage(
          "\nException: {0}",
          ex.Message
      );
      }
    }
   
    static public void DeleteLayerFilter()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;
      ListLayerFilters();
      try
      {
      // Get the existing layer filters
      // (we will add to them and set them back)
      LayerFilterTree lft =
          db.LayerFilters;
      LayerFilterCollection lfc =
          lft.Root.NestedFilters;
      // Prompt for the index of the filter to delete
      PromptIntegerOptions pio =
          new PromptIntegerOptions(
          "\n\nEnter index of filter to delete"
          );
      pio.LowerLimit = 1;
      pio.UpperLimit = lfc.Count;
      PromptIntegerResult pir =
          ed.GetInteger(pio);
      // Get the selected filter
      LayerFilter lf = lfc;
      // If it's possible to delete it, do so
      if (!lf.AllowDelete)
      {
          ed.WriteMessage(
            "\nLayer filter cannot be deleted."
          );
      }
      else
      {
          lfc.Remove(lf);
          db.LayerFilters = lft;
          ListLayerFilters();
      }
      }
      catch(Exception ex)
      {
      ed.WriteMessage(
          "\nException: {0}",
          ex.Message
      );
      }
    }
}
}
Here's what happens when we run the code on a drawing containing a number of layers (all of which have the default properties for new layers):
Command: CLG
Layers available for group:
1 - 0
2 - Layer1
3 - Layer2
4 - Layer3
5 - Layer4
6 - Layer5
7 - Layer6
8 - Layer7
9 - Layer8
10 - Layer9
11 - Test1
12 - Test2
13 - Test3
14 - Test4
15 - Test5
Enter number of layer to add: 2
Added "Layer1" to selected layers.
Enter number of layer to add: 4
Added "Layer3" to selected layers.
Enter number of layer to add: 6
Added "Layer5" to selected layers.
Enter number of layer to add: 8
Added "Layer7" to selected layers.
Enter number of layer to add: 11
Added "Test1" to selected layers.
Enter number of layer to add: 15
Added "Test5" to selected layers.
Enter number of layer to add:
"My Layer Group" group created containing 6 layers.
1 - My Layer Group (can be deleted)
2 - All Used Layers (cannot be deleted)
3 - Unreconciled New Layers (cannot be deleted)
4 - Viewport Overrides (cannot be deleted)
I've hard-coded the name of the new layer group to be "My Layer Group". It's a trivial exercise to modify the code to ask the user to enter a name, each time.
Here is how our new group looks in AutoCAD's Layer dialog:

雪山飞狐_lzh 发表于 2009-6-7 16:24:00

十三、向.NetApi开放AutoCAD中的属性面板的功能
OPM & 例子


March 13, 2009
Exposing AutoCAD's Properties Palette functionality to .NET - Part 1
A huge thanks to Cyrille Fauvel, who manages DevTech's Media & Entertainment team but still finds the time to dip into the odd deeply-technical, AutoCAD-related issue. Cyrille provided the original article on this topic late last year, but it's taken me time to get around to editing and publishing it. A quick tip... if you're not interested in the technical details of how Cyrille has exposed the various Properties Palette interfaces to .NET, you can safely skip this post and join us again when we go ahead and make use of the implementation to add dynamic properties to core AutoCAD objects using C#. Most people won't want to know all the details included in this post, but are just interested in the end results: next time I'll be providing a pre-built ObjectARX module, along with full source-code, that can be loaded into AutoCAD 2007-2009 to enable the use of the Properties Palette from .NET.
AutoCAD's Properties Palette - once known as the Object Properties Manager (OPM) - is a very handy way to display properties inside your application, whether those properties are associated with individual objects or with the application itself. The Properties Palette uses COM to communicate with the object(s) in question, and has always required the use of C++ to expose particular interfaces that control the display of the properties in the palette, so its functionality has not been available to developers using managed .NET languages such as C# and VB.NET.
There is some portion of the Properties Palette functionality exposed via the Autodesk.AutoCAD.Windows.ToolPalette namespace, such as the IAcPiPropertyDisplay interface allowing objects and commands to customize the display of properties in the property inspector window, but this is far from complete. This post looks at exposing more of the standard Properties Palette functionality to .NET languages, and next time we'll look at some specific examples of using it from C#.
Our first step is to expose some "OPM" interfaces to .NET. There are two ways to do this: we can expose them from our .NET application sung standard C# or VB.NET code, or we can use a small ObjectARX module to implement them.
Well, we have to use ObjectARX to access some OPM functionality that is currently only exposed via C++, so we're going to go down that path. What we're going to do is expose the interface(s) we want from our ObjectARX module and marshal the various parameters to be useable from .NET. Simple! :-)
To get started, we're going to look at AutoCAD's IPropertyManager2 interface, which is contained in dynprops.h on the ObjectARX SDK:
//--------------------------
// IPropertyManager2 interface
// This is the main property manager class. Use this to add your
// property classes for a given type of IUnknown object.
// You can get this interface using
// CreateOPMIUnknownProtocol(ppUnk)->GetPropertyManager2().
//--------------------------
// {FABC1C70-1044-4aa0-BF8D-91FFF9052715}
DEFINE_GUID(IID_IPropertyManager2, 0xfabc1c70, 0x1044, 0x4aa0, 0xbf, 0x8d, 0x91, 0xff, 0xf9, 0x5, 0x27, 0x15);

interface DECLSPEC_UUID("FABC1C70-1044-4aa0-BF8D-91FFF9052715")
IPropertyManager2 : public IUnknown
{
    BEGIN_INTERFACE
    // *** IUnknown methods ****
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    // *** IPropertyManager2 methods ***
    STDMETHOD(AddProperty)(THIS_ IUnknown FAR* pDynPropObj) PURE;
    STDMETHOD(RemoveProperty)(THIS_ IUnknown FAR* pDynPropObj) PURE;
    STDMETHOD(GetDynamicProperty)(THIS_ /**/LONG index,
                                    /**/IUnknown ** pDynPropObj) PURE;
    STDMETHOD(GetDynamicPropertyByName)(THIS_ /**/BSTR propName,
                                          /**/IUnknown ** pDynPropObj) PURE;
    STDMETHOD(GetDynamicPropertyCountEx)(THIS_ /**/LONG* count) PURE;
    //For COM Wrappers to generate dynamic property typeinfo
    STDMETHOD(GetDynamicClassInfo)(THIS_ /**/IUnknown* pObj,
                                        /**/ITypeInfo** pptiDynamic,
                                        /**/DWORD* dwCookie) PURE;
};Now in our ObjectARX application we're going to expose the IPropertyManager2 interface to .NET.
At line 41 in the IPropertyManager2.h file in the provided project , we declare the interface using the same Global Unique Identifier (GUID):

Then we need to indicate that our interface is derived from the standard, root COM interface, IUnknown:

We make it visible to COM, of course:

Now for the interface itself. We declare that it's a "public interface class" along with its various members, specifying how to marshal all the types. We're not going to return values from our methods (which usually indicate success or failure): we'll leave it to the .NET code that implements the interfaces (which we'll see in the final post of the series) to throw exceptions instead.
Here's the full class declaration, edited for display on this blog:
namespace Autodesk
{
namespace AutoCAD
{
    namespace Windows
    {
      namespace OPM
      {
      [InteropServices::Guid(
          "FABC1C70-1044-4aa0-BF8D-91FFF9052715"
      )]
      [InteropServices::InterfaceTypeAttribute(
          InteropServices::ComInterfaceType::InterfaceIsIUnknown
      )]
      

      public interface class IPropertyManager2
      {
          void AddProperty(
            [InteropServices::In,
            InteropServices::MarshalAs(
                InteropServices::UnmanagedType::IUnknown
            )
            ] Object^ pDynPropObj
          );
          void RemoveProperty(
            [InteropServices::In,
            InteropServices::MarshalAs(
                InteropServices::UnmanagedType::IUnknown
            )
            ] Object^ pDynPropObj
          );
          void GetDynamicProperty(
             long index,
            [InteropServices::Out,
            InteropServices::MarshalAs(
                InteropServices::UnmanagedType::IUnknown
            )
            ] interior_ptr<Object^> value
          );
          void GetDynamicPropertyByName(
            [InteropServices::In,
            InteropServices::MarshalAs(
                InteropServices::UnmanagedType::BStr
            )
            ] System::String^ name,
            [InteropServices::Out,
            InteropServices::MarshalAs(
                InteropServices::UnmanagedType::IUnknown
            )
            ] interior_ptr<Object^> value
          );
          void GetDynamicPropertyCountEx(
             long* count
            );
          void GetDynamicClassInfo(
            [InteropServices::In,
            InteropServices::MarshalAs(
                InteropServices::UnmanagedType::IUnknown
            )
            ] Object^ pDynPropObj,
            [InteropServices::Out,
            InteropServices::MarshalAs(
                /*InteropServices::UnmanagedType::ITypeInfo*/
                InteropServices::UnmanagedType::IUnknown
            )
            ] interior_ptr<Object^> typeInfo,
             ulong* dwCookie
          );
      };
      }
    }
}
}
This one class is really at the core of our effort to expose the OPM to .NET. To let us access and use this class, we're going to expose a "property extension factory". We might have opted to P/Invoke an ObjectARX API for this, but it's ultimately cleaner to implement this from C++ and expose it via a managed wrapper.
So how do we know we need a property extension factory? Typically to access the IPropertyManager2 instance from ObjectARX we use the GET_OPMPROPERTY_MANAGER() macro. From dynprops.h, we know that this macro expands into GET_OPMEXTENSION_CREATE_PROTOCOL()->CreateOPMObjectProtocol(pAcRxClass)->GetPropertyManager() and the GET_OPMEXTENSION_CREATE_PROTOCOL() macro expands, in turn, into OPMPropertyExtensionFactory::cast(AcDbDatabase::desc()->queryX(OPMPropertyExtensionFactory::desc())). This is how we know we need to expose the OPMPropertyExtensionFactory class.
So let's take a look at the declaration in the ObjectARX SDK of OPMPropertyExtensionFactory, once again from dynprops.h:
//--------------------------
// OPMPropertyExtension interface
// This class is implemented by AutoCAD and available through
// GET_OPMEXTENSION_CREATE_PROTOCOL. You can add property classes
// by calling GET_OPMPROPERTY_MANAGER for a particular AcRxClass
// to get the property manager for that class.
// You can also enumerate the dynamic properties which have
// been added to that class as well as its base class(es) via
// GetPropertyCount and GetPropertyClassArray
//--------------------------
class OPMPropertyExtensionFactory: public AcRxObject
{
public:
    ACRX_DECLARE_MEMBERS(OPMPropertyExtensionFactory);
    virtual ~OPMPropertyExtensionFactory(){}

    //Retrieves the OPMPropertyExtension for the specified class, if the
    //extension has not been added before, it creates it. Note: the implementation
    //of this class manages the lifetime of OPMPropertyExtension, as such you don't
    //need to delete them.
    virtual OPMPropertyExtension* CreateOPMObjectProtocol(AcRxClass* pClass,
                                                            LONG lReserved = NULL) = 0;

...
}
To expose this class through .NET we're going to use the Autodesk::AutoCAD::Runtime::Wrapper attribute class to tell AutoCAD to manage this class as an internal class object wrapper. We then derive our wrapper class from RXObject (as the base class of the corresponding ObjectARX class is AcRxObject), and implement a constructor, a GetImpObj() method to provide access to the unmanaged object and our factory function itself, CreateOPMObjectProtocol(). There's no need for a destructor as this is handled by the wrapper.
Here's the complete class declaration from OPMPropertyExtensionFactory.h in the provided sample:
namespace Autodesk
{
namespace AutoCAD
{
    namespace Windows
    {
      namespace OPM
      {
      [Autodesk::AutoCAD::Runtime::Wrapper(
          "OPMPropertyExtensionFactory"
      )]
      public ref class AcMgdOPMPropertyExtensionFactory
          : public RXObject
      {
      public protected:
          AcMgdOPMPropertyExtensionFactory(
            System::IntPtr unmanagedPointer,
            bool bAutoDelete
          )
            : RXObject(unmanagedPointer, bAutoDelete) {}

      internal:
          //- Returns the unmanaged ARX Object
          inline OPMPropertyExtensionFactory* GetImpObj()
          {
            return(
            static_cast<OPMPropertyExtensionFactory *>(
                UnmanagedObject.ToPointer()
            )
            );
          }

      public:
          virtual AcMgdOPMPropertyExtension^ CreateOPMObjectProtocol(
            RXClass^ runtimeClass,
            long lReserved
          );
      } ;
      }
    }
}
}
There is one last class that needs to be exposed: OPMPropertyExtension, which is what is created by the OPMPropertyExtensionFactory (logically enough). We'll use the same technique as for the OPMPropertyExtensionFactory.
Here's the class declaration from OPMPropertyExtension.h:
namespace Autodesk
{
namespace AutoCAD
{
    namespace Windows
    {
      namespace OPM
      {
      [Autodesk::AutoCAD::Runtime::Wrapper(
          "OPMPropertyExtension"
      )]
      public ref class AcMgdOPMPropertyExtension : public RXObject
      {
      public protected:
          AcMgdOPMPropertyExtension(
            System::IntPtr unmanagedPointer,
            bool bAutoDelete
          )
            : RXObject (unmanagedPointer, bAutoDelete) {}

      internal:
          //- Returns the unmanaged ARX Object
          inline OPMPropertyExtension* GetImpObj()
          {
            return (
            static_cast<OPMPropertyExtension *>(
                UnmanagedObject.ToPointer()
            )
            );
          }

      public:
          virtual Object^ GetPropertyManager();
          virtual void SetPropertyManager(Object^ pPropManager);
      } ;
      }
    }
}
}
Those are all the declarations (yes, in C++ you need declare your classes separately from their definitions... yawn... :-) so let's go ahead and implement the wrapper classes.
First the OPMPropertyExtensionFactory class (from OPMPropertyExtensionFactory.cpp), with it's main method to create and return a new OPMPropertyExtension:
namespace Autodesk
{
namespace AutoCAD
{
    namespace Windows
    {
      namespace OPM
      {
      AcMgdOPMPropertyExtension^
          AcMgdOPMPropertyExtensionFactory::CreateOPMObjectProtocol(
            RXClass^ runtimeClass, long lReserved
          )
      {
          return (
            gcnew AcMgdOPMPropertyExtension(
            System::IntPtr(
                GetImpObj()->CreateOPMObjectProtocol(
                  static_cast<AcRxClass*>(
                  //runtimeClass->GetImpObj()
                  runtimeClass->UnmanagedObject.ToPointer()
                  ),
                  lReserved
                )
            ),
            false
            )
          );
      }
      }
    }
}
}Here's the implementation of the OPMPropertyExtension class (from OPMPropertyExtension.cpp), which gets us access to the OPMPropertyManager:
namespace Autodesk
{
namespace AutoCAD
{
    namespace Windows
    {
      namespace OPM
      {
      Object^ AcMgdOPMPropertyExtension::GetPropertyManager()
      {
          IUnknown *pUnk =
            GetImpObj()->GetPropertyManager();
          return (
            System::Runtime::InteropServices::Marshal::
            GetObjectForIUnknown(System::IntPtr(pUnk))
          );
      }

      void AcMgdOPMPropertyExtension::SetPropertyManager(
          Object^ pPropManager
      )
      {
          IPropertyManager *pPropMgr =
            reinterpret_cast<IPropertyManager *>(
            System::Runtime::InteropServices::Marshal::
            GetIUnknownForObject(pPropManager).ToPointer()
            );
          GetImpObj()->SetPropertyManager(pPropMgr);
      }
      }
    }
}
}To finish off our implementation, we're going to re-implement the two handy macros we have in unmanaged C++ (i.e. ObjectARX), to give us the same capabilities when in a managed environment.
Here's our implementation from xOPM.cpp:
namespace Autodesk
{
namespace AutoCAD
{
    namespace Windows
    {
      namespace OPM
      {
      AcMgdOPMPropertyExtensionFactory^
          xOPM::xGET_OPMEXTENSION_CREATE_PROTOCOL()
      {
          Dictionary^ classDict =
            SystemObjects::ClassDictionary;
          RXClass^ opmFactoryClass =
            (RXClass^)classDict->At("OPMPropertyExtensionFactory");
          RXClass^ dbClass =
            (RXClass^)classDict->At("AcDbDatabase");
          return(
            gcnew AcMgdOPMPropertyExtensionFactory(
            dbClass->QueryX (opmFactoryClass),
            false
            )
          );
      }

      Object^
          xOPM::xGET_OPMPROPERTY_MANAGER(RXClass^ pAcRxClass)
      {
          AcMgdOPMPropertyExtensionFactory^ opmFactory =
            xOPM::xGET_OPMEXTENSION_CREATE_PROTOCOL();
          return(
            opmFactory->
            CreateOPMObjectProtocol(pAcRxClass, 0)->
                GetPropertyManager());
      }
      }
    }
}
}
There's just one more interface that needs exposing to .NET, and that's IDynamicProperty2. We're going to expose this in much the same way as we did IPropertyManager2.
Here's the original declaration from dynprops.h:
//--------------------------
// IDynamicProperty2 interface
// Implement this class to create dynamic properties for the PropertyPalette
// it defines the base set of property attributes as well as
// the name/type/data tuples.
//--------------------------
// {9CAF41C2-CA86-4ffb-B05A-AC43C424D076}
DEFINE_GUID(IID_IDynamicProperty2, 0x9caf41c2, 0xca86, 0x4ffb, 0xb0, 0x5a, 0xac, 0x43, 0xc4, 0x24, 0xd0, 0x76);

interface DECLSPEC_UUID("9CAF41C2-CA86-4ffb-B05A-AC43C424D076")
IDynamicProperty2 : public IUnknown
{
    BEGIN_INTERFACE
    // *** IUnknown methods ****

    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    // *** IDynamicProperty2 methods ***
    //Unique property ID
    STDMETHOD(GetGUID)(THIS_ /**/GUID* propGUID) PURE;
    // Property display name
    STDMETHOD(GetDisplayName)(THIS_ /**/BSTR* bstrName) PURE;
    // Show/Hide property in the OPM, for this object instance
    STDMETHOD(IsPropertyEnabled)(THIS_ /**/IUnknown *pUnk,
                                    /**/BOOL* pbEnabled) PURE;
    // Is property showing but disabled
    STDMETHOD(IsPropertyReadOnly)(THIS_ /**/BOOL* pbReadonly) PURE;
    // Get the property description string
    STDMETHOD(GetDescription)(THIS_ /**/BSTR* bstrName) PURE;

    // *** Basic property value information ***
    // OPM will typically display these in an edit field

    // optional: meta data representing property type name, ex. ACAD_ANGLE
    STDMETHOD(GetCurrentValueName)(THIS_ /**/BSTR* pbstrName) PURE;
    // What is the property type, ex. VT_R8
    STDMETHOD(GetCurrentValueType)(THIS_ /**/VARTYPE* pVarType) PURE;
    // Get the property value, passes the specific object we need the property
    // value for.
    STDMETHOD(GetCurrentValueData)(THIS_ /*in*/IUnknown *pUnk,
                                    /**/VARIANT* pvarData) PURE;
    // Set the property value, passes the specific object we want to set the
    // property value for
    STDMETHOD(SetCurrentValueData)(THIS_ /**/IUnknown *pUnk,
                                        /**/const VARIANT varData) PURE;

    //*** Notifications ***
    //OPM passes its implementation of IDynamicPropertyNotify, you
    //cache it and call it to inform OPM your property has changed
    STDMETHOD(Connect)(THIS_ /**/IDynamicPropertyNotify2* pSink) PURE;
    STDMETHOD(Disconnect)(THIS_ ) PURE;
};And here's our own exposure of this interface to .NET (from IDynamicProperty2.h):
namespace Autodesk
{
namespace AutoCAD
{
    namespace Windows
    {
      namespace OPM
      {
      [InteropServices::Guid(
          "9CAF41C2-CA86-4ffb-B05A-AC43C424D076"
      )]
      [InteropServices::InterfaceTypeAttribute(
          InteropServices::ComInterfaceType::InterfaceIsIUnknown
      )]
      
      public interface class IDynamicProperty2
      {
          void GetGUID(
             System::Guid% propGUID
          );
          void GetDisplayName(
            [InteropServices::Out,
            InteropServices::MarshalAs(
            InteropServices::UnmanagedType::BStr
            )
            ] interior_ptr<System::String^> name);
          void IsPropertyEnabled(
            [InteropServices::In,
            InteropServices::MarshalAs(
            InteropServices::UnmanagedType::IUnknown
            )
            ] Object^ pUnk,
             System::Int32% bEnabled
          );
          void IsPropertyReadOnly(
             System::Int32% bReadonly
          );
          void GetDescription(
            [InteropServices::Out,
            InteropServices::MarshalAs(
            InteropServices::UnmanagedType::BStr
            )
            ] interior_ptr<System::String^> description
          );
          void GetCurrentValueName(
            [InteropServices::Out,
            InteropServices::MarshalAs(
            InteropServices::UnmanagedType::BStr
            )
            ] interior_ptr<System::String^> name
          );
          void GetCurrentValueType(
             ushort% pVarType
          );
          void GetCurrentValueData(
            [InteropServices::In,
            InteropServices::MarshalAs(
            InteropServices::UnmanagedType::IUnknown
            )
            ] Object^ pUnk,
            [InteropServices::In,
            InteropServices::Out,
            InteropServices::MarshalAs(
            InteropServices::UnmanagedType::Struct
            )
            ] interior_ptr<Object^> varData
          );
          void SetCurrentValueData(
            [InteropServices::In,
            InteropServices::MarshalAs(
            InteropServices::UnmanagedType::IUnknown
            )
            ] Object^ pUnk,
            [InteropServices::In,
            InteropServices::MarshalAs(
            InteropServices::UnmanagedType::Struct
            )
            ] Object^ varData
          );
          void Connect(
            [InteropServices::In,
            InteropServices::MarshalAs(
            /*IDynamicPropertyNotify2*/
            InteropServices::UnmanagedType::IUnknown
            )
            ] Object^ pSink
          );
          void Disconnect();
      };
      [InteropServices::Guid(
          "975112B5-5403-4197-AFB8-90C6CA73B9E1"
      )]
      [InteropServices::InterfaceTypeAttribute(
          InteropServices::ComInterfaceType::InterfaceIsIUnknown
      )]
      
      public interface class IDynamicPropertyNotify2
      {
          void OnChanged(
            [InteropServices::In,
            InteropServices::MarshalAs(
            InteropServices::UnmanagedType::IUnknown
            )
            ] Object^ pDynamicProperty
          );
          void GetCurrentSelectionSet(
            [InteropServices::In,
            InteropServices::Out,
            InteropServices::MarshalAs(
            InteropServices::UnmanagedType::Struct
            )
            ] interior_ptr<Object^> pSelection
          );
      };
      }
    }
}
}
OK, that's it for the hardcore technical details on exposing AutoCAD's Properties Palette to .NET. In the next post we'll start the real fun with some real examples of using these interfaces from C# (and you can trust me when I say that's going to be a whole lot simpler than this post, honestly! :-).

雪山飞狐_lzh 发表于 2009-6-7 16:27:00

March 16, 2009
Exposing AutoCAD's Properties Palette functionality to .NET - Part 2
In the last post we looked at the code behind an ObjectARX module exposing AutoCAD's Properties Palette for use from managed .NET languages. Thanks again to Cyrille Fauvel for providing this implementation. In this post we're going to move right onto using this implementation from C#.
First things first: if you didn't understand much of what was said in the previous post in this series, Don't Panic! (Yes, that's a quick reference toThe Hitchhiker's Guide to the Galaxy.) The actual implementation details aren't particularly important - you only really need to understand them if you want to expose additional interfaces to .NET in the same way (such as IFilterableProperty, the interface discussed in this previous post and something I expect to extend the application to handle, at some point).
Here is the project that contains both the ObjectARX module implementing exposing these interfaces and the sample we're showing today. You simply have to make sure the .NET module can find our asdkOPMNetExt.dll module (ideally both this and your .NET module should be located under AutoCAD's program folder).
Then you can simply implement code, as in the below sample, which loads and makes use of interfaces exposed by this assembly.
Here’s the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows.OPM;
using System;
using System.Reflection;
using System.Runtime.InteropServices;

namespace OPMNetSample
{
#region Our Custom Property
[
    Guid("F60AE3DA-0373-4d24-82D2-B2646517ABCB"),
    ProgId("OPMNetSample.CustomProperty.1"),

    // No class interface is generated for this class and
    // no interface is marked as the default.
    // Users are expected to expose functionality through
    // interfaces that will be explicitly exposed by the object
    // This means the object can only expose interfaces we define

    ClassInterface(ClassInterfaceType.None),
    // Set the default COM interface that will be used for
    // Automation. Languages like: C#, C++ and VB allow to
    //query for interface's we're interested in but Automation
    // only aware languages like javascript do not allow to
    // query interface(s) and create only the default one

    ComDefaultInterface(typeof(IDynamicProperty2)),
    ComVisible(true)
]
public class CustomProp : IDynamicProperty2
{
    private IDynamicPropertyNotify2 m_pSink = null;

    // Unique property ID

    public void GetGUID(out Guid propGUID)
    {
      propGUID =
      new Guid("F60AE3DA-0373-4d24-82D2-B2646517ABCB");
    }

    // Property display name

    public void GetDisplayName(out string szName)
    {
      szName = "My integer property";
    }

    // Show/Hide property in the OPM, for this object instance

    public void IsPropertyEnabled(object pUnk, out int bEnabled)
    {
      bEnabled = 1;
    }

    // Is property showing but disabled

    public void IsPropertyReadOnly(out int bReadonly)
    {
      bReadonly = 0;
    }

    // Get the property description string

    public void GetDescription(out string szName)
    {
      szName =
      "This property is an integer";
    }

    // OPM will typically display these in an edit field
    // optional: meta data representing property type name,
    // ex. ACAD_ANGLE

    public void GetCurrentValueName(out string szName)
    {
      throw new System.NotImplementedException();
    }

    // What is the property type, ex. VT_R8

    public void GetCurrentValueType(out ushort varType)
    {
      // The Property Inspector supports the following data
      // types for dynamic properties:
      // VT_I2, VT_I4, VT_R4, VT_R8,VT_BSTR, VT_BOOL
      // and VT_USERDEFINED.

      varType = 3; // VT_I4
    }

    // Get the property value, passes the specific object
    // we need the property value for.

    public void GetCurrentValueData(object pUnk, ref object pVarData)
    {
      // TODO: Get the value and return it to AutoCAD

      // Because we said the value type was a 32b int (VT_I4)
      pVarData = (int)4;
    }

    // Set the property value, passes the specific object we
    // want to set the property value for

    public void SetCurrentValueData(object pUnk, object varData)
    {
      // TODO: Save the value returned to you

      // Because we said the value type was a 32b int (VT_I4)
      int myVal = (int)varData;
    }

    // OPM passes its implementation of IDynamicPropertyNotify, you
    // cache it and call it to inform OPM your property has changed

    public void Connect(object pSink)
    {
      m_pSink = (IDynamicPropertyNotify2)pSink;
    }

    public void Disconnect() {
      m_pSink = null;
    }
}
#endregion

#region Application Entry Point
public class MyEntryPoint : IExtensionApplication
{
    protected internal CustomProp custProp = null;

    public void Initialize()
    {
      Assembly.LoadFrom("asdkOPMNetExt.dll");

      // Add the Dynamic Property

      Dictionary classDict = SystemObjects.ClassDictionary;
      RXClass lineDesc = (RXClass)classDict.At("AcDbLine");
      IPropertyManager2 pPropMan =
      (IPropertyManager2)xOPM.xGET_OPMPROPERTY_MANAGER(lineDesc);

      custProp = new CustomProp();
      pPropMan.AddProperty((object)custProp);
    }

    public void Terminate()
    {
      // Remove the Dynamic Property

      Dictionary classDict = SystemObjects.ClassDictionary;
      RXClass lineDesc = (RXClass)classDict.At("AcDbLine");
      IPropertyManager2 pPropMan =
      (IPropertyManager2)xOPM.xGET_OPMPROPERTY_MANAGER(lineDesc);

      pPropMan.RemoveProperty((object)custProp);
      custProp = null;
    }
}
#endregion
}
A few comments on what this code does…
It defines a class for our custom dynamic property (CustomProp), for which we need a unique GUID (and yes, by definition GUIDs are unique, but if you use the same one twice it ceases to be :-), and implement various callbacks to indicate the name, type, description and writeability of the property, as well as methods to get and set the property value. For this example our property is called “My integer property”, and – guess what? – it’s an integer, and has been hardcoded to have the value 4. We’re not actually storing the data being exposed via this property, but in a real-world application you would probably store it either as XData attached to the object, inside an XRecord in the object’s extension dictionary or in an external database of some kind.
In the rest of the code we’re defining functions that are called when the module is loaded and when AutoCAD terminates (see this previous post for more information on this mechanism). We use the Initialize() callback to load our mixed-mode module for which we presented the code in the last post (asdkOPMNetExt.dll) and then go ahead and instantiate our property and attach it to line objects.
When we build and load the sample, making sure the Properties Palette is visible (using the PROPS command or by double-clicking on the line) and selecting a line we’ve just drawn, we should see our dynamic property appear, as shown in this image.
If you don’t see the property appear, the application is probably having trouble loading asdkOPMNetExt.dll: as mentioned earlier I have placed both this and the sample application in the root folder of my AutoCAD installation. If you’re not sure whether the module is loaded properly, you can step through the Initialize() function in the debugger or add a simple command to your code which will obviously only work if your application has been loaded (the Assembly.LoadFrom() call will throw an exception if it doesn’t find the module, and if an exception is thrown from Initialize() the application will not be loaded, as described in this previous post).
For the sake of simplicity I’m going to leave this basic sample as having just one, hardwired property: hopefully it’s obvious how the application could be extended to handle more properties and to store these properties with their objects (if not, let me know by posting a comment and I’ll put together a more extensive example when I get the chance).

雪山飞狐_lzh 发表于 2009-8-18 11:30:00

Kean专题(14)—Dynamic_Blocks

一、访问动态块属性
March 04, 2009
Accessing the properties of a dynamic AutoCAD block using .NET
This is one of those funny scenarios... I was just thinking about what to do for my next post - whether to dive into some new features of AutoCAD 2010 (which I will do soon, I promise! :-) or whether to choose something from my ever-increasing to-do list, when I received two emails.
One was from our old friend Fernando Malard, suggesting a topic for a blog post, and the other was from Philippe Leefsma, a member of our DevTech team in Europe, in response to an ADN members question. It provided some code that could eventually form the basis for a response to Fernando's question. Coincidence? Maybe. Am I one to ignore serendipity at work (or to look a gift horse in the mouth)? No!
So, here's Fernando's question:
    Just would suggest a new topic for your Blog. An article explaining how to insert a dynamic block and modifying its dynamic properties like point, rotation, dimension. This is something I’m currently working on .NET and I think it would be a very interesting topic to evolve.
Philippe's code is a little different - it shows how to retrieve and display the "dynamic" properties of a dynamic block read in from an external file - but it was a great start for my first step towards Fernando's goal, which was simply to access the dynamic properties for a block selected by the user.
Here's the C# code I based on Philippe's:using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;

namespace DynamicBlocks
{
public class Commands
{
   
    static public void DynamicBlockProps()
    {
      Document doc =
      Application.DocumentManager.MdiActiveDocument;
      Database db = doc.Database;
      Editor ed = doc.Editor;

      PromptStringOptions pso =
      new PromptStringOptions(
          "\nEnter dynamic block name or enter to select: "
      );

      pso.AllowSpaces = true;
      PromptResult pr = ed.GetString(pso);

      if (pr.Status != PromptStatus.OK)
      return;

      Transaction tr =
      db.TransactionManager.StartTransaction();
      using (tr)
      {
      BlockReference br = null;

      // If a null string was entered allow entity selection

      if (pr.StringResult == "")
      {
          // Select a block reference

          PromptEntityOptions peo =
            new PromptEntityOptions(
            "\nSelect dynamic block reference: "
            );

          peo.SetRejectMessage("\nEntity is not a block.");
          peo.AddAllowedClass(typeof(BlockReference), false);

          PromptEntityResult per =
            ed.GetEntity(peo);

          if (per.Status != PromptStatus.OK)
            return;

          // Access the selected block reference

          br =
            tr.GetObject(
            per.ObjectId,
            OpenMode.ForRead
            ) as BlockReference;
      }
      else
      {
          // Otherwise we look up the block by name

          BlockTable bt =
            tr.GetObject(
            db.BlockTableId,
            OpenMode.ForRead) as BlockTable;

          if (!bt.Has(pr.StringResult))
          {
            ed.WriteMessage(
            "\nBlock \"" + pr.StringResult + "\" does not exist."
            );
            return;
          }

          // Create a new block reference referring to the block

          br =
            new BlockReference(
            new Point3d(),
            bt
            );
      }

      BlockTableRecord btr =
          (BlockTableRecord)tr.GetObject(
            br.DynamicBlockTableRecord,
            OpenMode.ForRead
          );

      // Call our function to display the block properties

      DisplayDynBlockProperties(ed, br, btr.Name);

      // Committing is cheaper than aborting

      tr.Commit();
      }
    }

    private static void DisplayDynBlockProperties(
      Editor ed, BlockReference br, string name
    )
    {
      // Only continue is we have a valid dynamic block

      if (br != null && br.IsDynamicBlock)
      {
      ed.WriteMessage(
          "\nDynamic properties for \"{0}\"\n",
          name
      );

      // Get the dynamic block's property collection

      DynamicBlockReferencePropertyCollection pc =
          br.DynamicBlockReferencePropertyCollection;

      // Loop through, getting the info for each property

      foreach (DynamicBlockReferenceProperty prop in pc)
      {
          // Start with the property name, type and description

          ed.WriteMessage(
            "\nProperty: \"{0}\" : {1}",
            prop.PropertyName,
            prop.UnitsType
          );

          if (prop.Description != "")
            ed.WriteMessage(
            "\nDescription: {0}",
            prop.Description
            );

          // Is it read-only?

          if (prop.ReadOnly)
            ed.WriteMessage(" (Read Only)");

          // Get the allowed values, if it's constrained

          bool first = true;

          foreach (object value in prop.GetAllowedValues())
          {
            ed.WriteMessage(
            (first ? "\nAllowed values: [" : ", ")
            );
            ed.WriteMessage("\"{0}\"", value);

            first = false;
          }
          if (!first)
            ed.WriteMessage("]");

          // And finally the current value

          ed.WriteMessage(
            "\nCurrent value: \"{0}\"\n",
            prop.Value
          );
      }
      }
    }
}
}
Here's what happens when we run the DBP command, selecting the "Hex Socket Bolt (Side) - Metric" block from the "Mechanical - Metric.dwg" file in the Samples\Dynamic Blocks folder of your AutoCAD installation.Command: DBP
Enter dynamic block name or enter to select:
Select dynamic block reference:

Dynamic properties for "Hex Socket Bolt (Side) - Metric"

Property: "d1" : Distance
Allowed values: ["3", "4", "5", "6", "8", "10", "12", "14", "16", "20", "24",
"27", "30", "36"]
Current value: "14"

Property: "Origin" : NoUnits (Read Only)
Current value: "(97.714,-5,0)"

Property: "b" : Distance
Allowed values: ["18", "20", "22", "24", "28", "32", "36", "40", "44", "52",
"57", "60", "65", "66", "72", "73", "84", "85"]
Current value: "40"

Property: "Origin" : NoUnits (Read Only)
Current value: "(100,-3.5,0)"

Property: "k" : Distance
Allowed values: ["3", "4", "5", "6", "8", "10", "12", "14", "16", "20", "24",
"27", "30", "36"]
Current value: "14"

Property: "Origin" : NoUnits (Read Only)
Current value: "(5.64204767561892E-15,-7,0)"

Property: "d2" : Distance
Allowed values: ["5.5", "7", "8.5", "10", "13", "16", "18", "21", "24", "30",
"36", "40", "45", "54"]
Current value: "21"

Property: "Origin" : NoUnits (Read Only)
Current value: "(3.5527136788005E-15,-8,0)"

Property: "Size" : NoUnits
Allowed values: ["M3", "M4", "M5", "M6", "M8", "M10", "M12", "M14"]
Current value: "M14"

Property: "Visibility" : NoUnits
Allowed values: ["M3", "M4", "M5", "M6", "M8", "M10", "M12", "M14"]
Current value: "M14"

Property: "Length (M3)" : Distance
Description: Set the bolt length
Allowed values: ["25", "30", "35", "40", "45", "50", "60"]
Current value: "100"

Property: "Origin" : NoUnits (Read Only)
Current value: "(0,0,0)"

Property: "Length (M4)" : Distance
Description: Set the bolt length
Allowed values: ["30", "35", "40", "45", "50", "60", "70", "80"]
Current value: "100"

Property: "Origin" : NoUnits (Read Only)
Current value: "(0,0,0)"

Property: "Length (M5)" : Distance
Description: Set the bolt length
Allowed values: ["30", "35", "40", "45", "50", "55", "60", "70", "80", "90",
"100"]
Current value: "100"

Property: "Origin" : NoUnits (Read Only)
Current value: "(0,0,0)"

Property: "Length (M6)" : Distance
Description: Set the bolt length
Allowed values: ["35", "40", "45", "50", "55", "60", "65", "70", "75", "80",
"90", "100", "110", "120"]
Current value: "100"

Property: "Origin" : NoUnits (Read Only)
Current value: "(2.25597318603832E-14,0,0)"

Property: "Length (M8)" : Distance
Description: Set the bolt length
Allowed values: ["40", "45", "50", "55", "60", "65", "70", "75", "80", "85",
"90", "100", "110", "120", "130", "140", "150", "160", "180", "200"]
Current value: "100"

Property: "Origin" : NoUnits (Read Only)
Current value: "(2.25597318603832E-14,0,0)"

Property: "Length (M10)" : Distance
Description: Set the bolt length
Allowed values: ["45", "50", "55", "60", "65", "70", "75", "80", "85", "90",
"100", "110", "120", "130", "140", "150", "160", "180", "200"]
Current value: "100"

Property: "Origin" : NoUnits (Read Only)
Current value: "(2.25597318603832E-14,0,0)"

Property: "Length (M12)" : Distance
Description: Set the bolt length
Allowed values: ["55", "60", "65", "70", "75", "80", "85", "90", "100",
"110", "120", "130", "140", "150", "160", "180", "200"]
Current value: "100"

Property: "Origin" : NoUnits (Read Only)
Current value: "(2.25597318603832E-14,0,0)"

Property: "Length (M14)" : Distance
Description: Set the bolt length
Allowed values: ["60", "65", "70", "75", "80", "90", "100", "110", "120",
"130", "140", "150", "160"]
Current value: "100"

Property: "Origin" : NoUnits (Read Only)
Current value: "(2.25597318603832E-14,0,0)"
Many of the properties are actually 0, but have ended up being printed as a very small number... it would be simple enough to check these against a tolerance rather than trusting them to be understood as being zero.
OK, that's a good enough start for today. In the next post we'll address the need to capture dynamic properties from an inserted dynamic block and copy them across to another, already-inserted dynamic block. A bit like a "property painter" for dynamic blocks. (If you're thinking that this doesn't sound quite like what Fernando originally asked for, then you'd be quite right. We exchanged a few emails, and I then opted for a "property painter" approach to address the problem.)
Thanks for the inspiration, Fernando and Philippe! :-)

页: 1 [2] 3
查看完整版本: Kean专题(9)—Object_Properties