找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 1182|回复: 1

[分享] Using .NET reflection with AutoCAD to change object properties

[复制链接]

已领礼包: 859个

财富等级: 财运亨通

发表于 2014-5-8 15:07:54 来自手机 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

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

×
本帖最后由 csharp 于 2014-5-9 07:10 编辑

http://through-the-interface.typepad.com/through_the_interface/2007/02/using_net_refle_1.html
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:

// Function to change an individual entity's property
private int ChangeSingleProperty(
  Entity ent,
  System.Type objType,
  string propName,
  object newValue)
{
  int changedCount = 0;

  // Are we dealing with an entity we care about?
  if (objType.IsInstanceOfType(ent))
  {
    // Check the existing value
    object res =
      objType.InvokeMember(
        propName,
        BindingFlags.GetProperty,
        null,
        ent,
        new object[0]
      );

    // If it is not the same then change it
    if (!res.Equals(newValue))
    {
      // Entity is only open for read
      ent.UpgradeOpen();
      object[] args = new object[1];
      args[0] = newValue;
      res =
        objType.InvokeMember(
          propName,
          BindingFlags.SetProperty,
          null,
          ent,
          args
        );
      changedCount++;
      ent.DowngradeOpen();
    }
  }
  return changedCount;
}

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:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using System.Reflection;

namespace PropertyChanger
{
  public class PropertyChangerCmds
  {
    [CommandMethod("CHPS",
      CommandFlags.Modal |
      CommandFlags.Redraw |
      CommandFlags.UsePickSet)
    ]
    public void ChangePropertyOnSelectedEntities()
    {
      Document doc =
        Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;

      try
      {
        PromptSelectionResult psr =
          ed.GetSelection();

        if (psr.Status == PromptStatus.OK)
        {
          System.Type objType;
          string propName;
          object newPropValue;
          bool recurse;

          if (SelectClassPropertyAndValue(
                out objType,
                out propName,
                out newPropValue,
                out recurse
              )
            )
          {
            int count =
              ChangePropertyOfEntitiesOfType(
                psr.Value.GetObjectIds(),
                objType,
                propName,
                newPropValue,
                recurse);
            // Update the display, and print the count
            ed.Regen();
            ed.WriteMessage(
              "\nChanged " +
              count + " object" +
              (count == 1 ? "" : "s") +
              " of type " +
              objType.Name +
              " to have a " +
              propName + " of " +
              newPropValue + "."
            );
          }
        }
      }
      catch (System.Exception ex)
      {
        ed.WriteMessage(
          "Exception: " + ex
        );
      }
    }

    [CommandMethod("CHPM")]
    public void ChangePropertyOnModelSpaceContents()
    {
      ChangePropertyOnSpaceContents(
        BlockTableRecord.ModelSpace
      );
    }

    [CommandMethod("CHPP")]
    public void ChangePropertyOnPaperSpaceContents()
    {
      ChangePropertyOnSpaceContents(
        BlockTableRecord.PaperSpace
      );
    }

    private void ChangePropertyOnSpaceContents(
      string spaceName
    )
    {
      Document doc =
        Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;

      try
      {
        System.Type objType;
        string propName;
        object newPropValue;
        bool recurse;

        if (SelectClassPropertyAndValue(
              out objType,
              out propName,
              out newPropValue,
              out recurse
            )
          )
        {
          ObjectId spaceId;
          Transaction tr =
            doc.TransactionManager.StartTransaction();
          using (tr)
          {
            BlockTable bt =
              (BlockTable)tr.GetObject(
                doc.Database.BlockTableId,
                OpenMode.ForRead
              );
            spaceId = bt[spaceName];

            // Not needed, but quicker than aborting
            tr.Commit();
          }
          // Call our recursive function to set the new
          // value in our nested objects
          int count =
            ChangePropertyOfEntitiesOfType(
              spaceId,
              objType,
              propName,
              newPropValue,
              recurse);
          // Update the display, and print the count
          ed.Regen();
          ed.WriteMessage(
            "\nChanged " +
            count + " object" +
            (count == 1 ? "" : "s") +
            " of type " +
            objType.Name +
            " to have a " +
            propName + " of " +
            newPropValue + "."
          );
        }
      }
      catch (System.Exception ex)
      {
        ed.WriteMessage(
          "Exception: " + ex
        );
      }
    }

    private bool SelectClassPropertyAndValue(
      out System.Type objType,
      out string propName,
      out object newPropValue,
      out bool recurse)
    {
      Document doc =
        Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;

      objType = null;
      propName = "";
      newPropValue = null;
      recurse = true;

      // Let's first get the class to query for
      PromptResult ps =
        ed.GetString(
          "\nEnter type of objects to look for: "
        );

      if (ps.Status == PromptStatus.OK)
      {
        string typeName = ps.StringResult;

        // Use reflection to get the type from the string
        objType =
          System.Type.GetType(
            typeName,
            false,        // Do not throw an exception
            true          // Case-insensitive search
          );

        // If we didn't find it, try prefixing with
        // "Autodesk.AutoCAD.DatabaseServices."

        if (objType == null)
        {
          objType =
            System.Type.GetType(
              "Autodesk.AutoCAD.DatabaseServices." +
              typeName + ", acdbmgd",
              false,      // Do not throw an exception
              true        // Case-insensitive search
            );
        }

        if (objType == null)
        {
          ed.WriteMessage(
            "\nType " + typeName + " not found."
          );
        }
        else
        {
          // If we have a valid type then let's
          // first list its writable properties
          ListProperties(objType);

          // Prompt for a property
          ps = ed.GetString(
            "\nEnter property to modify: "
          );

          if (ps.Status == PromptStatus.OK)
          {
            propName = ps.StringResult;

            // Make sure the property exists...
            System.Reflection.PropertyInfo propInfo =
              objType.GetProperty(propName);
            if (propInfo == null)
            {
              ed.WriteMessage(
                "\nProperty " +
                propName +
                " for type " +
                typeName +
                " not found."
              );
            }
            else
            {
              if (!propInfo.CanWrite)
              {
                ed.WriteMessage(
                  "\nProperty " +
                  propName +
                  " of type " +
                  typeName +
                  " is not writable."
                );
              }
              else
              {
                // If the property is writable...
                // ask for the new value
                System.Type propType = propInfo.PropertyType;
                string prompt =
                      "\nEnter new value of " +
                      propName +
                      " property for all objects of type " +
                      typeName +
                      ": ";

                // Only certain property types are currently
                // supported: Int32, Double, String, Boolean
                switch (propType.ToString())
                {
                  case "System.Int32":
                    PromptIntegerResult pir =
                      ed.GetInteger(prompt);
                    if (pir.Status == PromptStatus.OK)
                      newPropValue = pir.Value;
                    break;
                  case "System.Double":
                    PromptDoubleResult pdr =
                      ed.GetDouble(prompt);
                    if (pdr.Status == PromptStatus.OK)
                      newPropValue = pdr.Value;
                    break;
                  case "System.String":
                    PromptResult psr =
                      ed.GetString(prompt);
                    if (psr.Status == PromptStatus.OK)
                      newPropValue = psr.StringResult;
                    break;
                  case "System.Boolean":
                    PromptKeywordOptions pko =
                      new PromptKeywordOptions(
                      prompt);
                    pko.Keywords.Add("True");
                    pko.Keywords.Add("False");
                    PromptResult pkr =
                      ed.GetKeywords(pko);
                    if (pkr.Status == PromptStatus.OK)
                    {
                      if (pkr.StringResult == "True")
                        newPropValue = true;
                      else
                        newPropValue = false;
                    }
                    break;
                  default:
                    ed.WriteMessage(
                      "\nProperties of type " +
                      propType.ToString() +
                      " are not currently supported."
                    );
                    break;
                }
                if (newPropValue != null)
                {
                  PromptKeywordOptions pko =
                    new PromptKeywordOptions(
                      "\nChange properties in nested blocks: "
                    );
                  pko.AllowNone = true;
                  pko.Keywords.Add("Yes");
                  pko.Keywords.Add("No");
                  pko.Keywords.Default = "Yes";
                  PromptResult pkr =
                    ed.GetKeywords(pko);
                  if (pkr.Status == PromptStatus.None |
                      pkr.Status == PromptStatus.OK)
                  {
                    if (pkr.Status == PromptStatus.None |
                        pkr.StringResult == "Yes")
                      recurse = true;
                    else
                      recurse = false;

                    return true;
                  }
                }
              }
            }
          }
        }
      }
      return false;
    }

    private void ListProperties(System.Type objType)
    {
      Document doc =
        Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;

      ed.WriteMessage(
        "\nWritable properties for " +
        objType.Name +
        ": "
      );

      PropertyInfo[] propInfos =
        objType.GetProperties();
      foreach (PropertyInfo propInfo in propInfos)
      {
        if (propInfo.CanWrite)
        {
          ed.WriteMessage(
            "\n  " +
            propInfo.Name +
            " : " +
            propInfo.PropertyType
          );
        }
      }
      ed.WriteMessage("\n");
    }

    // Version of the function that takes a container ID
    private int ChangePropertyOfEntitiesOfType(
      ObjectId btrId,
      System.Type objType,
      string propName,
      object newValue,
      bool recurse
    )
    {
      // We simply open the container, extract the IDs
      // and pass them to another version of the function...
      // If efficiency is an issue, then this could be
      // streamlined (i.e. duplicated, rather than factored)

      ObjectIdCollection btrContents =
        new ObjectIdCollection();

      Document doc =
        Application.DocumentManager.MdiActiveDocument;

      Transaction tr =
        doc.TransactionManager.StartTransaction();
      using (tr)
      {
        BlockTableRecord btr =
          (BlockTableRecord)tr.GetObject(
            btrId,
            OpenMode.ForRead
          );
        foreach (ObjectId entId in btr)
        {
          btrContents.Add(entId);
        }
        tr.Commit();
      }
      ObjectId[] ids = new ObjectId[btrContents.Count];
      btrContents.CopyTo(ids, 0);

      // Call the other version of this function
      return ChangePropertyOfEntitiesOfType(
        ids,
        objType,
        propName,
        newValue,
        recurse
      );
    }

    // Version of the function that takes a list of ents
    private int ChangePropertyOfEntitiesOfType(
      ObjectId[] objIds,
      System.Type objType,
      string propName,
      object newValue,
      bool recurse
    )
    {
      int changedCount = 0;

      Document doc =
        Application.DocumentManager.MdiActiveDocument;

      Transaction tr =
        doc.TransactionManager.StartTransaction();
      using (tr)
      {
        foreach (ObjectId entId in objIds)
        {
          Entity ent =
            tr.GetObject(entId, OpenMode.ForRead)
            as Entity;

          // Change each entity, one by one
          if (ent != null)
          {
            changedCount +=
              ChangeSingleProperty(
                ent,
                objType,
                propName,
                newValue
              );
          }

          // If we are to recurse and it's a blockref...
          if (recurse)
          {
            BlockReference br = ent as BlockReference;
            if (br != null)
            {
              // ...then recurse
              changedCount +=
                ChangePropertyOfEntitiesOfType(
                  br.BlockTableRecord,
                  objType,
                  propName,
                  newValue,
                  recurse
                );
            }
          }
        }
        tr.Commit();
      }
      return changedCount;
    }

    // Function to change an individual entity's property
    private int ChangeSingleProperty(
      Entity ent,
      System.Type objType,
      string propName,
      object newValue)
    {
      int changedCount = 0;

      // Are we dealing with an entity we care about?
      if (objType.IsInstanceOfType(ent))
      {
        // Check the existing value
        object res =
          objType.InvokeMember(
            propName,
            BindingFlags.GetProperty,
            null,
            ent,
            new object[0]
          );

        // If it is not the same then change it
        if (!res.Equals(newValue))
        {
          // Entity is only open for read
          ent.UpgradeOpen();
          object[] args = new object[1];
          args[0] = newValue;
          res =
            objType.InvokeMember(
              propName,
              BindingFlags.SetProperty,
              null,
              ent,
              args
            );
          changedCount++;
          ent.DowngradeOpen();
        }
      }
      return changedCount;
    }
  }
}

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! :-)


论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

已领礼包: 859个

财富等级: 财运亨通

 楼主| 发表于 2014-5-9 17:16:27 | 显示全部楼层
尝试下改造成 SetPropertyValue 函数
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

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

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|辽公网安备|晓东CAD家园 ( 辽ICP备15016793号 )

GMT+8, 2024-11-17 22:45 , Processed in 0.188813 second(s), 30 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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