.NET
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Get specified attribute from block without loop.

12 REPLIES 12
SOLVED
Reply
Message 1 of 13
GrzesiekGP
1339 Views, 12 Replies

Get specified attribute from block without loop.

Hello!

I know how to get/set attribute within loop at block's AttributeCollection.

 

But I'm wonder if I can set value for specified attribute without loop. Or I always have to iterate on each attribute in block's AttributeCollection?

 

Thanks.

12 REPLIES 12
Message 2 of 13

Only if you store the ids somehow, after the first time you find them, or when you create them, but I can't think of very many scenarios where that would be beneficial.

Dave O.                                                                  Sig-Logos32.png
Message 3 of 13

Thanks for the reply.

 

I've written own functions to get/set specified attribute (of course with loop using):

 

        private static string GetAttribute(Transaction tr, BlockReference blkRef, string attribute)
        {
            AttributeCollection attCol = blkRef.AttributeCollection;

            foreach (ObjectId attId in attCol)
            {
                AttributeReference attRef = (AttributeReference)tr.GetObject(attId, OpenMode.ForWrite);

                if (attRef.Tag.ToUpper() == attribute.ToUpper())
                {
                    return attRef.TextString;
                }
            }

            return string.Empty;
        }

        private static void SetAttribute(Transaction tr, BlockReference blkRef, string attribute, string value)
        {
            AttributeCollection attCol = blkRef.AttributeCollection;

            foreach (ObjectId attId in attCol)
            {
                AttributeReference attRef = (AttributeReference)tr.GetObject(attId, OpenMode.ForWrite);

                if (attRef.Tag.ToUpper() == attribute.ToUpper())
                {
                    attRef.UpgradeOpen();
                    attRef.TextString = value;
                    attRef.DowngradeOpen();
                }
            }
        }

 

If someone see some bad code, please let me know 🙂

Message 4 of 13

I personally don't like passing transactions around, and would prefer to open a new transaction inside these functions, but that is my personal preference.

 

You should, however, be disposing the Attribute References that you open.

 

Otherwise, I have two functions just like these (In VB), and a couple more that overload them where I can pass in a Dictionary(of String, String) to the Set method, and set the values of multiple attributes in the same transaction, or I can get all the tags and values at once by returning the same kind of dictionary from the Get function.

Dave O.                                                                  Sig-Logos32.png
Message 5 of 13
khoa.ho
in reply to: GrzesiekGP

AttributeCollection is implemented from the interface ICollection, not from IDictionary. So there is no key-value pair to jump right away to the specified attribute. We expect to have blockReference.AttributeCollection[attributeTagName] = attributeReference, to get a specified attribute without using loop. But AttributeCollection is a .NET collection that is an enumerable list having to deal with iteration loop.

 

We can write code to create an attribute dictionary of a block, then use it to randomly find the attribute (from ObjectId) based on its unique tag:

 

public static Dictionary<string, ObjectId> GetAttributeDictionary(BlockReference blockRef)
{
 var dictionary = new Dictionary<string, ObjectId>();
 using (Transaction tr = blockRef.Database.TransactionManager.StartTransaction())
 {
  AttributeCollection collection = blockRef.AttributeCollection;
  foreach (ObjectId objectId in collection)
  {
   var attribute = (AttributeReference)tr.GetObject(objectId, OpenMode.ForRead);
   dictionary.Add(attribute.Tag, objectId);
  }
  tr.Commit();
 }
 return dictionary;
}

 

public static ObjectId GetAttributeFromTagName(BlockReference blockRef, string attributeTag)
{
 Dictionary<string, ObjectId> dictionary = GetAttributeDictionary(blockRef);
 return dictionary[attributeTag];
}

 

-Khoa

Message 6 of 13
chiefbraincloud
in reply to: khoa.ho

It is late, and I am a little bleary eyed, and a little tipsy, and I don't mean to be adversarial, or controversial, but that does not help to prevent the iteration of the attribute collection.  It might be the underlying code for storing the IDs as I had originally proposed, but as written it does not store the IDs, it just iterates the collection every time the wrapper function is called, so it saves you nothing.

 

I've got to get to bed, and trying to read C# code right now is making me dizzy.  I'll look at it again tommorrow and see if I feel differently about it.

Dave O.                                                                  Sig-Logos32.png
Message 7 of 13
_gile
in reply to: chiefbraincloud

Hi,

 

chiefbraincloud,

I too don't like passing transactions around but I won't open a new while a TopTransaction is running.

Disposing the Attribute References that you open is unnecessary because it's opened with the transaction which will dispose it for you.

 

By my side, I use some extension methods with Dictionary<string, string>.

 

Here's an extract:

    public static class AttributeExtensions
    {
        public static IEnumerable<AttributeReference> GetAttributes(this AttributeCollection attribs)
        {
            foreach (ObjectId id in attribs)
            {
                yield return (AttributeReference)id.GetObject(OpenMode.ForRead, false, false);
            }
        }

        public static Dictionary<string, string> GetAttributesValues(this BlockReference br)
        {
            return br.AttributeCollection
                .GetAttributes()
                .ToDictionary(att => att.Tag, att => att.TextString);
        }

        public static void SetAttributesValues(this BlockReference br, Dictionary<string, string> atts)
        {
            foreach (AttributeReference attRef in br.AttributeCollection.GetAttributes())
            {
                if (atts.ContainsKey(attRef.Tag))
                {
                    attRef.UpgradeOpen();
                    attRef.TextString = atts[attRef.Tag];
                }
            }
        }
    }

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 8 of 13
GrzesiekGP
in reply to: GrzesiekGP

Excelent idea with dictionary use 🙂

 

Thanks mate!

Message 9 of 13
khoa.ho
in reply to: chiefbraincloud

Definitely we call the method GetAttributeDictionary() only one time, store in a variable, and use it many times based on specified attribute tag names. The second method (GetAttributeFromTagName) just shows how to find an attribute in the dictionary.

 

ObjectId is more generic to present an attribute reference. Then we can call GetObject() method to get or update attribute values. AttributeCollection is also a collection of ObjectIds.

 

-Khoa

Message 10 of 13
chiefbraincloud
in reply to: _gile

gile

 

I also try not to have nested transactions, and I should have also mentioned I don't like passing around open objects, either.  I always pass ObjectIds and open them in the function that is doing the work.

 

As I mentioned above, I also have two methods that use String, String dictionaries to get or set attributes.  I love dictionaries, but it is not feasable for me to create a String, ObjectId dictionary and leave it memory resident to use later.  I just don't think that is a good idea at all.  My code deals with far too many blocks and attributes to load up the memory like that.

Dave O.                                                                  Sig-Logos32.png
Message 11 of 13


@chiefbraincloud wrote:

 

You should, however, be disposing the Attribute References that you open.

 

 

Sorry, but that's not the case.

 

There's absolutely no need to dispose any DBObject that was opened from a transaction. If you've been reading the adndevblog, you may have misunderstood some recent advice offered there regarding disposing AutoCAD objects.

 

Message 12 of 13


@chiefbraincloud wrote:

 

I also try not to have nested transactions, and I should have also mentioned I don't like passing around open objects, either.  I always pass ObjectIds and open them in the function that is doing the work.

 

 

Hate to disagree.

 

I see no legitimate reason not to pass open DBObjects as parameters, especially if you follow consistent design patterns where APIs that take open DBObjects as parameters typically assume there is an active transaction that is managed at the entry point (e.g., a command method, event handler, etc).

 

The other problem with your idea that passing ObjectIds is preferable, is that it complicates code-reusability because APIs that take DBObjects can also accept DBObjects that are not database-resident, have no valid ObjectId, and can't be opened via GetObject().

 

Additionally, some events pass arguments that include open DBObjects that are not transaction-resident and can't be safely opened with a transaction from the handler of the event, further complicating reusability of APIs that require ObjectIds.

 

So.... If writing good sound reusable code is important, then writing APIs that take DBObjects is far better than ones that take ObjectIds, for many reasons aside from it being far more efficient..

Message 13 of 13
Hallex
in reply to: GrzesiekGP

Hello!
I know how to get/set attribute within loop at block's AttributeCollection.

 

But I'm wonder if I can set value for specified attribute without loop. Or I always have to iterate on each attribute in block's AttributeCollection?

 

Thanks.

 

Have you played with DATAEXTRACTION command ?

 

~'J'~

_____________________________________
C6309D9E0751D165D0934D0621DFF27919

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Autodesk Design & Make Report

”Boost