Author Topic: How do I get the prompt for an attributerefrence?  (Read 3128 times)

0 Members and 1 Guest are viewing this topic.

mkweaver

  • Bull Frog
  • Posts: 352
How do I get the prompt for an attributerefrence?
« on: June 21, 2009, 01:27:54 AM »
It appears that Prompt is not a member of the AttributeReference class, but is a member of the AttributeDefinition class.  That said, and accepted, grudgingly, I thought I would sub-class attributereference class and add a prompt property.

It appears that I will have to get the block definition and from there the attribute definition to get the prompt.

Is there not an easier way?

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: How do I get the prompt for an attributerefrence?
« Reply #1 on: June 21, 2009, 04:17:53 AM »
It appears that I will have to get the block definition and from there the attribute definition to get the prompt.

yes

Is there not an easier way?

No

Unless you consider COM  easier :|

Autodesk.AutoCAD.Interop.Common.AcadAttributeClass
public virtual extern string get_PromptString();

// kdub
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

mkweaver

  • Bull Frog
  • Posts: 352
Re: How do I get the prompt for an attributerefrence?
« Reply #2 on: June 22, 2009, 10:46:32 PM »
Here is what I ended up with - suggestions are welcome:
Code: [Select]
  Public Function GetAttributePrompt(ByRef Att As AttributeReference) As String
    Dim TransMan As Autodesk.AutoCAD.DatabaseServices.TransactionManager
    Dim Trans As Transaction
    Dim db As Autodesk.AutoCAD.DatabaseServices.Database = Att.Database
    TransMan = db.TransactionManager
    Trans = TransMan.StartTransaction
    Dim objInsert As BlockReference = Trans.GetObject(Att.OwnerId, OpenMode.ForRead)
    Dim blocks As Autodesk.AutoCAD.DatabaseServices.BlockTable = Trans.GetObject(db.BlockTableId, OpenMode.ForRead)
    Dim BlockDef As BlockTableRecord = Trans.GetObject(blocks.Item(objInsert.Name), OpenMode.ForRead)
    Dim Dict As New System.Collections.Generic.Dictionary(Of String, String)
    For Each objID In BlockDef
      Dim obj As New Object
      obj = Trans.GetObject(objID, OpenMode.ForRead)
      If obj.objectname = "AttributeDefinition" Then
        Dim AttFound As AttributeDefinition = obj
        If AttFound.Tag = Att.Tag Then
          GetAttributePrompt = AttFound.Prompt
          Trans.Dispose()
          TransMan.Dispose()
          db.Dispose()
          Exit Function
        End If
      End If
    Next
  End Function

Bryco

  • Water Moccasin
  • Posts: 1883
Re: How do I get the prompt for an attributerefrence?
« Reply #3 on: June 24, 2009, 10:54:41 AM »
I always heard that a string search ( obj.objectname = "AttributeDefinition") is slower than using a typeof comparison, so I use
if (obj is AttributeDefinition). I also have no idea if this is correct. Anybody?

Mike Leslie

  • Guest
Re: How do I get the prompt for an attributerefrence?
« Reply #4 on: July 24, 2013, 09:54:31 AM »
The code you wrote works OK when every attribute tag in a block is different.  This is the recommended practice and in recent versions of AutoCAD they actively discourage you from duplicating tags by coloring them red in EATTEDIT.  Your code breaks down, however, when a block has multiple attributes using the same tag.  One of the older title blocks at this company was set up that way, so I wrote a version that handles this better, by using the position of the attribute in the order of the list of attributes in the block (after matching by tag).  This better matches how ATTEDIT and EATTEDIT appear to work.

Code: [Select]
Public Shared Function GetAttributePrompt(ByRef Att As AttributeReference, ByVal DB As Database, ByVal Trans As Transaction) As String

        ValidateDBandTrans(DB, Trans, OpenMode.ForRead)
        If IsNothing(Att) Then Throw New ArgumentNullException("Att")

        'find out index position of attrib in block reference
        Dim BlkRef As BlockReference = DirectCast(Trans.GetObject(Att.OwnerId, OpenMode.ForRead), BlockReference)
        Dim attindex As Integer = 0
        For Each objID As ObjectId In BlkRef.AttributeCollection
            Dim AttRef As AttributeReference = DirectCast(objID.GetObject(OpenMode.ForRead), AttributeReference)
            If AttRef.Tag = Att.Tag Then
                If objID = Att.ObjectId Then Exit For
                attindex += 1
            End If
        Next
        'attindex should now be which attribute of this tag (#0, #1, etc)

        Dim Blocks As BlockTable = DirectCast(Trans.GetObject(DB.BlockTableId, OpenMode.ForRead), BlockTable)
        Dim BlockDef As BlockTableRecord = DirectCast(Trans.GetObject(Blocks.Item(BlkRef.Name), OpenMode.ForRead), BlockTableRecord)
        Dim ctr As Integer = 0
        For Each objID As ObjectId In BlockDef
            If objID.ObjectClass.Name = "AcDbAttributeDefinition" Then
                Dim AttDef As AttributeDefinition = DirectCast(objID.GetObject(OpenMode.ForRead), AttributeDefinition)
                If AttDef.Tag = Att.Tag Then
                    'maybe it's the right one, maybe not, check the count
                    If ctr = attindex Then
                        Return AttDef.Prompt
                    End If
                    ctr += 1
                End If

            End If
        Next

        Return ""
        'Throw New ApplicationException("Cannot find prompt for this attribute reference.")
    End Function

    Private Shared Sub ValidateDBandTrans(ByVal DB As Database, ByVal Trans As Transaction)
        If IsNothing(DB) Then Throw New ArgumentNullException("DB")
        If IsNothing(Trans) Then Throw New ArgumentNullException("Trans")
        If DB.IsDisposed Then Throw New ArgumentException("Database has been disposed.")
        If Trans.IsDisposed Then Throw New ArgumentException("Transaction has been disposed.")
    End Sub


I tested this on our title block and it seems to work right.   An interesting situation comes up if the user uses "Edit Block In-Place" and deletes an attribute.  The prompt strings shown in the ATTEDIT or EATTEDIT shift position.  For example:

Code: [Select]
Originally Tag, Prompt, and Text
DOOR_NOTE    Door #1    1
DOOR_NOTE    Door #2    2
DOOR_NOTE    Door #3    3
 
Delete Attribute 2 using "Edit Block In-Place", attribute still exists in BlockReference, ATTSYNC was not run
DOOR_NOTE    Door #1    1
DOOR_NOTE    Door #3    2
DOOR_NOTE                     3


ATTEDIT shows the tag on screen in place of the Prompt, EATTEDIT shows a blank.  I made my code return a blank and to shift the prompts up the list like the AutoCAD commands do, but it goes to show that one should be careful about making assumptions that you have the correct PromptString.  This returns what is right in AutoCAD's eyes, but is conceptually the wrong one for the user.


For anyone that is interested, I tested this using:

Code: [Select]

    <CommandMethod("GetAttrib")> _
    Public Sub TestGetAttributePrompt()

        Try
            Dim DB As Database = HostApplicationServices.WorkingDatabase
            Dim ed As Autodesk.AutoCAD.EditorInput.Editor = Application.DocumentManager.GetDocument(DB).Editor

            Using Trans As Transaction = DB.TransactionManager.StartTransaction()

                Dim blkrefs As List(Of BlockReference) = AcadTransactionFunctions.GetBlockReferencesLike("AWL Title", True, DB, Trans, OpenMode.ForRead)
                If blkrefs.Count > 0 Then
                    Dim attribs As AttributeCollection = blkrefs(0).AttributeCollection
                    Dim prompt As String
                    For Each objID As ObjectId In attribs
                        Dim attrib As AttributeReference = DirectCast(objID.GetObject(OpenMode.ForRead), AttributeReference)

                        ed.WriteMessage("Tag: " & attrib.Tag & vbLf)
                        prompt = AcadTransactionFunctions.GetAttributePrompt(attrib, DB, Trans)
                        ed.WriteMessage("Prompt: " & prompt & vbLf)
                        ed.WriteMessage("Text: " & attrib.TextString & vbLf)

                    Next
                End If
                Trans.Commit()
            End Using

        Catch ex As System.Exception
            Application.ShowAlertDialog("Error: " & ex.Message)
        End Try
    End Sub


« Last Edit: July 24, 2013, 10:03:02 AM by Mike Leslie »

kaefer

  • Guest
Re: How do I get the prompt for an attributerefrence?
« Reply #5 on: July 24, 2013, 05:16:30 PM »
The code you wrote works OK when every attribute tag in a block is different.  This is the recommended practice and in recent versions of AutoCAD they actively discourage you from duplicating tags by coloring them red in EATTEDIT.  Your code breaks down, however, when a block has multiple attributes using the same tag.

I'd recommend a Dictionary<string,int> to keep count of duplicate tags. Something like this, returning a sequence of tuples string*AttributeReference, which could then be used as unique keys for yet another Dictionary<string,AttributeReference>.

Code - F#: [Select]
  1.     let deDup (atts : AttributeCollection) =
  2.         let d = new System.Collections.Generic.Dictionary<string, int>()
  3.         atts      
  4.         |> Seq.cast<ObjectId>
  5.         |> Seq.map (fun oid ->
  6.             oid.GetObject OpenMode.ForRead :?> AttributeReference )
  7.         |> Seq.map (fun att ->
  8.             if d.ContainsKey att.Tag then
  9.                 d.[att.Tag] <- d.[att.Tag] + 1
  10.                 att.Tag + "(" + string d.[att.Tag] + ")", att
  11.             else
  12.                 d.Add(att.Tag, 0)
  13.                 att.Tag, att )

Jeff H

  • Needs a day job
  • Posts: 6151
Re: How do I get the prompt for an attributerefrence?
« Reply #6 on: July 24, 2013, 06:27:31 PM »
I have been testing using a ExpandoObject to dynamically create properties that represent the Tags, but cast it to a IDictionary<string, object> for adding them, checking for duplicates, grouping where keys match, but other than doing a little tiny work workaround for tags that contain a "." in the name it has been working nicely and implements it INotifyPropertyChanged so makes binding very easy for creating UI to edit them.