October 2014

Sun Mon Tue Wed Thu Fri Sat
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  










« Using .NET reflection with AutoCAD to change object properties - Part 1 | Main | AutoCAD 2008 – 64-bit application migration »

February 07, 2007

Using .NET reflection with AutoCAD to change object properties - Part 2

In the last post we looked at some code that combined user-input from the AutoCAD command-line with .NET reflection to determine an object type, a property belonging to that type, and the value to which we want to change that property on instances of that type (phew!). Here's where we look at hooking that up with some code to work recursively through the drawing database and make the changes to the actual objects.

Firstly, let's look at some C# code to check the type of an entity, and then - as appropriate - to query the property's value and to set it to a new value, if it doesn't match what it needs to be set to:


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

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00d83452464869e200d835187d5469e2

Listed below are links to weblogs that reference Using .NET reflection with AutoCAD to change object properties - Part 2:

blog comments powered by Disqus

Feed/Share

10 Random Posts