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! 🙂

11 responses to “Using .NET reflection with AutoCAD to change object properties - Part 2”

  1. Hi Kean, I couldn't understand the real benefits in using refletion for to change the objects properties. To Use the objects interfaces was much better and easier, Couldn't be?
    I've been appreciating your blog.
    Tks in advanced!

  2. Hi Fábio,

    Yes, using object interfaces is just fine (and much easier) for the majority of programming tasks. Occasionally you need to provide some interface to the user that does not depend on compiled-in behaviour (and therefore can also be extended by other applications).

    Think about the Property Palette, which uses COM to do the same thing as we're showing here: it doesn't have behaviour hardcoded for the various object types it supports, it queries their properties at runtime.

    Regards,

    Kean

  3. Really enjoy your stuff ...
    MPOLYGONS? maybe you could spend some time on this lil'beast?

  4. Hi Kean
    I translated this example on VB.NET
    Of course my programming level in VB.NET
    is very poor, I started to learned it
    a few days only
    I can't to fix 3 errors in this code snipped
    What I did worng here?

    Regards,

    Oleg

    Imports Autodesk.AutoCAD.ApplicationServices
    Imports Autodesk.AutoCAD.DatabaseServices
    Imports Autodesk.AutoCAD.EditorInput
    Imports Autodesk.AutoCAD.Runtime
    Imports System.Reflection
    Namespace PropertyChanger

    Public Class PropertyChangerCmds

    <commandmethod("chps", commandflags.modal="" or="" commandflags.redraw="" or="" commandflags.usepickset)=""> _
    Public Sub ChangePropertyOnSelectedEntities()
    Dim doc As Document = Application.DocumentManager.MdiActiveDocument
    Dim ed As Editor = doc.Editor
    Try
    Dim psr As PromptSelectionResult = ed.GetSelection
    If psr.Status = PromptStatus.OK Then
    Dim objType As System.Type
    Dim propName As String
    Dim newPropValue As Object
    Dim recurse As Boolean
    If SelectClassPropertyAndValue(objType, propName, newPropValue, recurse) Then
    Dim count As Integer = ChangePropertyOfEntitiesOfType(psr.Value.GetObjectIds, objType, propName, newPropValue, recurse)
    ed.Regen()
    ed.WriteMessage("" & Microsoft.VisualBasic.Chr(10) & "Changed " + count + " object" + (Microsoft.VisualBasic.IIf(count = 1, "", "s")) + " of type " + objType.Name + " to have a " + propName + " of " + newPropValue + ".")
    End If
    End If
    Catch ex As System.Exception
    ed.WriteMessage("Exception: " & ex.StackTrace)
    End Try
    End Sub

    <commandmethod("chpm")> _
    Public Sub ChangePropertyOnModelSpaceContents()
    ChangePropertyOnSpaceContents(BlockTableRecord.ModelSpace)
    End Sub

    <commandmethod("chpp")> _
    Public Sub ChangePropertyOnPaperSpaceContents()
    ChangePropertyOnSpaceContents(BlockTableRecord.PaperSpace)
    End Sub

    Private Sub ChangePropertyOnSpaceContents(ByVal spaceName As String)
    Dim doc As Document = Application.DocumentManager.MdiActiveDocument
    Dim ed As Editor = doc.Editor
    Try
    Dim objType As System.Type
    Dim propName As String
    Dim newPropValue As Object
    Dim recurse As Boolean
    If SelectClassPropertyAndValue(objType, propName, newPropValue, recurse) Then
    Dim spaceId As ObjectId
    Using tr As Transaction = doc.TransactionManager.StartTransaction

    Try
    Dim bt As BlockTable = CType(tr.GetObject(doc.Database.BlockTableId, OpenMode.ForRead), BlockTable)
    spaceId = bt(spaceName)
    tr.Commit()
    Finally
    ' could not find variable declaration
    ' TODO : Dispose object
    End Try
    End Using
    Dim count As Integer = ChangePropertyOfEntitiesOfType(spaceId, objType, propName, newPropValue, recurse)
    ed.Regen()
    ed.WriteMessage("" & Microsoft.VisualBasic.Chr(10) & "Changed " + count + " object" + (Microsoft.VisualBasic.IIf(count = 1, "", "s")) + " of type " + objType.Name + " to have a " + propName + " of " + newPropValue + ".")
    End If
    Catch ex As System.Exception
    ed.WriteMessage("Exception: " & ex.StackTrace)
    End Try

    End Sub

    Private Function SelectClassPropertyAndValue(ByRef objType As System.Type, ByRef propName As String, ByRef newPropValue As Object, ByRef recurse As Boolean) As Boolean
    Dim doc As Document = Application.DocumentManager.MdiActiveDocument
    Dim ed As Editor = doc.Editor
    objType = Nothing
    propName = ""
    newPropValue = Nothing
    recurse = True
    Dim ps As PromptResult = ed.GetString("" & Microsoft.VisualBasic.Chr(10) & "Enter type of objects to look for: ")
    If ps.Status = PromptStatus.OK Then
    Dim typeName As String = ps.StringResult
    objType = System.Type.GetType(typeName, False, True)
    If objType Is Nothing Then
    objType = System.Type.GetType("Autodesk.AutoCAD.DatabaseServices." + typeName + ", acdbmgd", False, True)
    End If
    If objType Is Nothing Then
    ed.WriteMessage("" & Microsoft.VisualBasic.Chr(10) & "Type " + typeName + " not found.")
    Else
    ListProperties(objType)
    ps = ed.GetString("" & Microsoft.VisualBasic.Chr(10) & "Enter property to modify: ")
    If ps.Status = PromptStatus.OK Then
    propName = ps.StringResult
    Dim propInfo As System.Reflection.PropertyInfo = objType.GetProperty(propName)
    If propInfo Is Nothing Then
    ed.WriteMessage("" & Microsoft.VisualBasic.Chr(10) & "Property " + propName + " for type " + typeName + " not found.")
    Else
    If Not propInfo.CanWrite Then
    ed.WriteMessage("" & Microsoft.VisualBasic.Chr(10) & "Property " + propName + " of type " + typeName + " is not writable.")
    Else
    Dim propType As System.Type = propInfo.PropertyType
    Dim prompt As String = "" & Microsoft.VisualBasic.Chr(10) & "Enter new value of " + propName + " property for all objects of type " + typeName + ": "
    Select Case propType.ToString
    Case "System.Int32"
    Dim pir As PromptIntegerResult = ed.GetInteger(prompt)
    If pir.Status = PromptStatus.OK Then
    newPropValue = pir.Value
    End If
    ' break
    Case "System.Double"
    Dim pdr As PromptDoubleResult = ed.GetDouble(prompt)
    If pdr.Status = PromptStatus.OK Then
    newPropValue = pdr.Value
    End If
    ' break
    Case "System.String"
    Dim psr As PromptResult = ed.GetString(prompt)
    If psr.Status = PromptStatus.OK Then
    newPropValue = psr.StringResult
    End If
    ' break
    Case "System.Boolean"
    Dim pko As PromptKeywordOptions = New PromptKeywordOptions(prompt)
    pko.Keywords.Add("True")
    pko.Keywords.Add("False")
    Dim pkr As PromptResult = ed.GetKeywords(pko)
    If pkr.Status = PromptStatus.OK Then
    If pkr.StringResult = "True" Then
    newPropValue = True
    Else
    newPropValue = False
    End If
    End If
    ' break
    Case Else
    ed.WriteMessage("" & Microsoft.VisualBasic.Chr(10) & "Properties of type " + propType.ToString + " are not currently supported.")
    ' break
    End Select
    If Not (newPropValue Is Nothing) Then
    Dim pko As PromptKeywordOptions = New PromptKeywordOptions("" & Microsoft.VisualBasic.Chr(10) & "Change properties in nested blocks: ")
    pko.AllowNone = True
    pko.Keywords.Add("Yes")
    pko.Keywords.Add("No")
    pko.Keywords.Default = "Yes"
    Dim pkr As PromptResult = ed.GetKeywords(pko)
    If pkr.Status = PromptStatus.None Or pkr.Status = PromptStatus.OK Then
    If pkr.Status = PromptStatus.None Or pkr.StringResult = "Yes" Then
    recurse = True
    Else
    recurse = False
    End If
    Return True
    End If
    End If
    End If
    End If
    End If
    End If
    End If
    Return False
    End Function

    Private Sub ListProperties(ByVal objType As System.Type)
    Dim doc As Document = Application.DocumentManager.MdiActiveDocument
    Dim ed As Editor = doc.Editor
    ed.WriteMessage("" & Microsoft.VisualBasic.Chr(10) & "Writable properties for " + objType.Name + ": ")
    Dim propInfos As PropertyInfo() = objType.GetProperties
    For Each propInfo As PropertyInfo In propInfos
    If propInfo.CanWrite Then
    ed.WriteMessage("" & Microsoft.VisualBasic.Chr(10) & " " & propInfo.Name & " : " & propInfo.PropertyType)
    End If
    Next
    ed.WriteMessage("" & Microsoft.VisualBasic.Chr(10) & "")
    End Sub

    Private Function ChangePropertyOfEntitiesOfType(ByVal btrId As ObjectId, ByVal objType As System.Type, ByVal propName As String, ByVal newValue As Object, ByVal recurse As Boolean) As Integer
    Dim btrContents As ObjectIdCollection = New ObjectIdCollection
    Dim doc As Document = Application.DocumentManager.MdiActiveDocument
    Using tr As Transaction = doc.TransactionManager.StartTransaction

    Try
    Dim btr As BlockTableRecord = CType(tr.GetObject(btrId, OpenMode.ForRead), BlockTableRecord)
    For Each entId As ObjectId In btr
    btrContents.Add(entId)
    Next
    tr.Commit()
    Finally
    ' could not find variable declaration
    ' TODO : Dispose object
    End Try
    End Using
    Dim ids(btrContents.Count) As ObjectId
    btrContents.CopyTo(ids, 0)
    Return ChangePropertyOfEntitiesOfType(ids, objType, propName, newValue, recurse)
    End Function

    Private Function ChangePropertyOfEntitiesOfType(ByVal objIds As ObjectId(), ByVal objType As System.Type, ByVal propName As String, ByVal newValue As Object, ByVal recurse As Boolean) As Integer
    Dim changedCount As Integer = 0
    Dim doc As Document = Application.DocumentManager.MdiActiveDocument
    Using tr As Transaction = doc.TransactionManager.StartTransaction

    Try
    For Each entId As ObjectId In objIds
    Dim ent As Entity = CType(ConversionHelpers.AsWorkaround(tr.GetObject(entId, OpenMode.ForRead), GetType(Entity)), Entity)
    If Not (ent Is Nothing) Then
    changedCount += ChangeSingleProperty(ent, objType, propName, newValue)
    End If
    If recurse Then
    Dim br As BlockReference = CType(ConversionHelpers.AsWorkaround(ent, GetType(BlockReference)), BlockReference)
    If Not (br Is Nothing) Then
    changedCount += ChangePropertyOfEntitiesOfType(br.BlockTableRecord, objType, propName, newValue, recurse)
    End If
    End If
    Next
    tr.Commit()
    Finally
    ' could not find variable declaration
    ' TODO : Dispose object
    End Try
    End Using
    Return changedCount
    End Function

    Private Function ChangeSingleProperty(ByVal ent As Entity, ByVal objType As System.Type, ByVal propName As String, ByVal newValue As Object) As Integer
    Dim changedCount As Integer = 0
    If objType.IsInstanceOfType(ent) Then
    Dim res As Object = objType.InvokeMember(propName, BindingFlags.GetProperty, Nothing, ent, New Object(0) {})
    If Not res.Equals(newValue) Then
    ent.UpgradeOpen()
    Dim args(1) As Object
    args(0) = newValue
    res = objType.InvokeMember(propName, BindingFlags.SetProperty, Nothing, ent, args)
    System.Math.Min(System.Threading.Interlocked.Increment(changedCount), changedCount - 1)
    ent.DowngradeOpen()
    End If
    End If
    Return changedCount
    End Function
    End Class
    End Namespace

  5. Hi Oleg,

    There seem to be a few issues with the automatic conversion...

    C# supports "out" parameters, while VB has ByRef (which are "in/out"). So you need to disable the error or initialise the variables before passing them into SelectClassPropertyAndValue(). For example:

    objType = ed.GetType()
    propName = ""
    newPropValue = Nothing
    recurse = False

    This is ugly, but IMO better than disabling the wraning. Perhaps someone has a better suggestion.

    Next we have to add a ToString() to a type in a WriteMessage call - VB doesn't seem to do that implicitly:

    propInfo.PropertyType.ToString()

    And finally we have a couple of strange casts that need fixing:

    Dim ent As Entity = CType(tr.GetObject(entId, OpenMode.ForRead), Entity)
    Dim br As BlockReference = CType(ent, BlockReference)

  6. Dear Kean

    Thanks a lot for the spending your time
    That works like a charm and fixed
    a problems
    I'll work further this way

    Best regards,

    Oleg

  7. How to I use reflection to call "CopyObjects" method?

  8. The good news is that I just found this among my spam comments. The bad news is that I don't have a quick answer for you: calling anything like this through Reflection is quite a big deal (depending on whether you want to due this complete using "late binding"). Have you looked into using "dynamic" objects for this, instead?

    Kean

  9. Hi Kean,

    Thanks for sharing this code.
    I was wondering how can I change the custom properties of my dynamic block?
    BindingFlags.GetProperty does not get those properties.

    Thanks,

    1. Please post your support questions to the AutoCAD .NET forum.

      Many thanks,

      Kean

Leave a Reply

Your email address will not be published. Required fields are marked *