From MLeader Block to Block Reference

From MLeader Block to Block Reference

Anonymous
Not applicable
4,786 Views
16 Replies
Message 1 of 17

From MLeader Block to Block Reference

Anonymous
Not applicable

Selection of objects to edit either attributes or text is going great for me until I get to MLeaders.  Our keynotes are set up as an MLStyle with a block.  Within the block is an attribute.  I can get to the BlockTableRecord of that block but as yet am unable to get to the BlockReference of the block to access the attributes for edit.  Appropriate research has been completed.  Now I call on the forum.  Any help to get me to the BlockReference?

 

Code below starts with the selection (perid) sorted by name.

 

'MLeader
                ElseIf pername = "MULTILEADER" Then
                    Using tr As Transaction = db.TransactionManager.StartTransaction
                        Dim mld As MLeader = tr.GetObject(perid, OpenMode.ForRead, False, True)
                        Dim btr As BlockTableRecord = TryCast(tr.GetObject(mld.BlockContentId, OpenMode.ForRead), BlockTableRecord)
                        Dim br As BlockReference = CType(tr.GetObject(btr, OpenMode.ForRead), BlockReference)  '<~~~~ISSUE HERE
                        If btr.HasAttributeDefinitions Then
                            Dim blkname As String = btr.Name
                            Dim z As Integer = 0
                            For Each attid As ObjectId In btr
                                Dim attdef As AttributeDefinition = TryCast(tr.GetObject(attid, OpenMode.ForRead), AttributeDefinition)
                                Dim AttRef As AttributeReference = CType(tr.GetObject(br.AttributeCollection(z), OpenMode.ForRead), AttributeReference)
                                taglist.Add(attdef.Tag)
                                promptlist.Add(attdef.Prompt)
                                Dim prmt As String = promptlist(z)
                                tbname = "textbox" & z
                                pretxt = AttRef.TextString
                                lb = New System.Windows.Forms.Label
                                With lb
                                    .Size = New System.Drawing.Size(100, 40)
                                    .Location = New System.Drawing.Point(10, z * 45 + 10)
                                    .Text = prmt
                                End With
                                tb = New System.Windows.Forms.TextBox
                                With tb
                                    .Name = "tb" & z
                                    .Size = New System.Drawing.Size(500, 20)
                                    .Location = New System.Drawing.Point(120, z * 45 + 10)
                                    .Text = pretxt
                                    .TabIndex = z
                                    .AcceptsTab = True
                                    .Enabled = True
                                    .SelectAll()
                                End With
                                With Me
                                    .Text = blkname
                                    .Panel1.Controls.Add(lb)
                                    .Panel1.Controls.Add(tb)
                                    .Panel1.AutoScroll = True
                                    .Panel1.Enabled = True
                                    .AutoSize = True
                                    .Update()
                                End With
                                z = z + 1

                            Next
                        Else

                        End If
                        tr.Commit()
                    End Using
                End If

 

 

 Appreciative accolades promised for any assistance

0 Likes
Accepted solutions (1)
4,787 Views
16 Replies
Replies (16)
Message 2 of 17

norman.yuan
Mentor
Mentor

Well, look at the problematic lines of code:

 

Dim btr As BlockTableRecord = TryCast(tr.GetObject(mld.BlockContentId, OpenMode.ForRead), BlockTableRecord)
Dim br As BlockReference = CType(tr.GetObject(btr, OpenMode.ForRead), BlockReference)  '<~~~~ISSUE HERE

There is no way one can cast a BlockTablerecord into a BlockReference! The second line does nothing but breaking the execution.

 

The block type of content in MLeader, as its name implies, is a reference to a block, and the block definition can be accessed by MLeader.BlockContentId, as you did with the first line of code shown above. However, the actual block reference shown in each MLeader as content is not directly accessible as individual BlockReference within an MLeader object.

 

You might be able to use BlockTableRecord.GetBlockReferenceIds() to find all the references used in all the MLeaders of the same Mleader style, but I guess it is much tedius work/code to do, and I did not bother to try it, because MLeader class provides methods to access the Block Content (e.g. the attributes in the block): GetBlockAttribute()/SetBlockAttribute(). BTW, if you look at MLeader's COM API in VBA's Object Browser, you can find the similar methods.

 

Here is the sample code to read/set the attribute in MLeader's content of block type:

 

 

using System;
using System.Collections.Generic;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;

[assembly: CommandClass(typeof(MLeaderInfo.MyCadCommands))]

namespace MLeaderInfo
{
    public class MyCadCommands
    {
        [CommandMethod("GetMleader")]
        public static void RunMyCadCommand()
        {
            Document dwg = CadApp.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;

            ObjectId mlId = PickMleader(ed);
            if (!mlId.IsNull)
            {
                using (var tran=dwg.TransactionManager.StartTransaction())
                {
                    MLeader leader = (MLeader)
                        tran.GetObject(mlId, OpenMode.ForWrite);

                    //Collect attribute definition information
                    BlockTableRecord br = (BlockTableRecord)
                        tran.GetObject(leader.BlockContentId, OpenMode.ForRead);

                    List<Tuple<ObjectId, string>> lst = 
                        new List<Tuple<ObjectId, string>>();

                    foreach (ObjectId id in br)
                    {
                        AttributeDefinition adef = 
                            tran.GetObject(id, OpenMode.ForRead) 
                            as AttributeDefinition;

                        if (adef!=null)
                        {
                            ed.WriteMessage(
                                "\nAttribute tag in MLeader: {0}; " + 
                                "Attribute defualt value: {1}", 
                                adef.Tag, adef.TextString);

                            lst.Add(new Tuple<ObjectId, string>(id, adef.Tag));
                        }  
                    }

                    //Read/set attribute in the MLeader
                    int i=1;
                    foreach (var tp in lst)
                    {
                        AttributeReference aref = leader.GetBlockAttribute(tp.Item1);

                        //read attribute value
                        string val = aref.TextString;
                        ed.WriteMessage("\nAttribute value in MLeader: {0}", val);

                        //Set attribute value
                        aref.TextString = val + "00" + i;
                        leader.SetBlockAttribute(tp.Item1, aref);

                        i++;
                    }

                    tran.Commit();
                }
            }
        }

        private static ObjectId PickMleader(Editor ed)
        {
            PromptEntityOptions opt = new PromptEntityOptions(
                "\nSelect an MLeader:");
            opt.SetRejectMessage("\nInvalid: not an MLeader!");
            opt.AddAllowedClass(typeof(MLeader), true);
            PromptEntityResult res = ed.GetEntity(opt);
            if (res.Status==PromptStatus.OK)
            {
                return res.ObjectId;
            }
            else
            {
                return ObjectId.Null;
            }
        }
    }
}

 HTH

 

Norman Yuan

Drive CAD With Code

EESignature

Message 3 of 17

Anonymous
Not applicable

On it.   Thanks Norman.  Will let you know how I make out.

0 Likes
Message 4 of 17

Anonymous
Not applicable

Just an update.  I'm no where closer to getting this and still have the same issue as before.  I am unable to set the specific attribute reference text string.  Still working.

0 Likes
Message 5 of 17

norman.yuan
Mentor
Mentor

Attached is the drawing that I used to run the code I posted in previous reply. Try run my code against this drawing to see the effect of the code execution: the attributes iin the MLeader's block content are updated as expected.

 

Hope this help a bit more.

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes
Message 6 of 17

Anonymous
Not applicable

Just an update...I haven't forgotten this thread, it has just been a frantic couple of weeks.  I'm back in now.

0 Likes
Message 7 of 17

Anonymous
Not applicable

So..on my terms.  Correct me where I'm wrong.

 

In your code above you are creating a list of attribute definition ids - attribute tags.  Then using this list, looking at each pair, to pull the attribute reference.

 

In porting this over to vb.net I get an error at 'For Each tp as var in lst' where 'var' isn't defined.

 

Part of the problem I'm having is that I need the list of tags, prompts (from attribute definition) & textstring (from attribute reference) before moving on to populate a form which the user then can use to edit the text string with changes made back to the block on OK_Click.

0 Likes
Message 8 of 17

norman.yuan
Mentor
Mentor

Of course you cannot use "var" in VB.NET.

 

Since variable "lst" is a List(of Tuple(of ObjectId, String)), so, the code should be

 

For Each tp As Tuple(of ObjectId, String) In lst

    ....

Next

 

Data type "Tuple" is just a convenient data type that hold a pair of data together. You can alway define a class that holds 2 public members instead.

Norman Yuan

Drive CAD With Code

EESignature

0 Likes
Message 9 of 17

Anonymous
Not applicable

Do I need the attribute definition id and the tag to get to the attribute reference or can I just use the objectid?  Below does what I need to in populating the form with labels (taken from promptlist) & textboxes but does not give me the textstring of the attribute.  You've gotten me this far and I thank you for it.  I'm just not clicking on the attribute reference.

 

 

 Using tr As Transaction = db.TransactionManager.StartTransaction
                        Dim Mldr As MLeader = DirectCast(tr.GetObject(perid, OpenMode.ForWrite), MLeader)
                        Dim btr As BlockTableRecord = DirectCast(tr.GetObject(Mldr.BlockContentId, OpenMode.ForRead), BlockTableRecord)
                        Dim blkname As String = btr.Name.ToString
                        Dim idlist As New List(Of Tuple(Of ObjectId, String))()
                        If btr.HasAttributeDefinitions Then
                            Dim z As Integer = 0
                            For Each ObjId As ObjectId In btr
                                obj = tr.GetObject(ObjId, OpenMode.ForRead)
                                If TypeOf obj Is AttributeDefinition Then
                                    Dim AttId As ObjectId = obj.ObjectId
                                    Dim AttDef As AttributeDefinition = TryCast(tr.GetObject(ObjId, OpenMode.ForRead), AttributeDefinition)
                                    If AttDef IsNot Nothing Then
                                        taglist.Add(AttDef.Tag)
                                        promptlist.Add(AttDef.Prompt)
                                    End If
                                    Dim AttRef As AttributeReference = Mldr.GetBlockAttribute(AttId)
                                    pretxt = AttRef.TextString
                                    MsgBox(pretext)

 

0 Likes
Message 10 of 17

Anonymous
Not applicable

Having just completed an actual billable project I find myself now staring at this problem again.  I have made some progress.  I now get an error that something is not open for write.

 

By the time we get to here I have already had the user select an entity with editable attributes, created a form with the attribute prompts as the labels and the attribute text as the textbox text.  They have now changed the text that they want to change and have hit OK under which this portion of the code happens.  As I am pulling the attribute text from the attribute reference and can push that back out in many ways (like to the command line or a msgbox) I can confidently say I am pulling the text string from the attribute reference.  However, this code below fails with a Not Open For Write message and the attributes do not change in the multileader block.

 

                'MLeader
            ElseIf pername = "MULTILEADER" Then
                Dim txtlist As New List(Of String)()
                Dim childc As System.Windows.Forms.Control
                For Each childc In Me.Panel1.Controls
                    If TypeOf childc Is System.Windows.Forms.TextBox Then
                        txtlist.Add(childc.Text.ToString)
                    End If
                Next
                Dim txtarray As String() = txtlist.ToArray()
                Dim tagarray As String() = taglist.ToArray()
                Dim prmtarray As String() = promptlist.ToArray()
                Using tr As Transaction = AcDb.TransactionManager.StartTransaction()
                    Dim btr As BlockTableRecord = DirectCast(tr.GetObject(Mldr.BlockContentId, OpenMode.ForWrite), BlockTableRecord)
                    For z As Integer = 0 To txtarray.Length - 1
                        For Each id As ObjectId In btr
                            Dim adef As AttributeDefinition = TryCast(tr.GetObject(id, OpenMode.ForWrite), AttributeDefinition)
                            If adef IsNot Nothing Then
                                MsgBox("step 1")
                                Dim aref As AttributeReference = Mldr.GetBlockAttribute(adef.Id)
                                aref.TextString = txtlist(z)
                                Mldr.SetBlockAttribute(adef.Id, aref)
                            End If
                        Next
                    Next
                    tr.Commit()
                    AcEd.Regen()
                End Using
0 Likes
Message 11 of 17

SENL1362
Advisor
Advisor
I'am not too familiar with VB.NET but my guess would be the AREF. You're changing it's TextString. Try AREF.UPGRADEOPEN() before you change it's text value.
0 Likes
Message 12 of 17

BKSpurgeon
Collaborator
Collaborator

I'm not expert, but this really is a very simple issue. I also wanted to edit some attributes yesterday, and did so by selection succesfully. I can offer some advice on how you can do it if you are prepared to do a little bit of reading.

 

Secondly, the cast seems a little problematic. Thirdly everything is set to read only - but since you are making changes shouldn't it be .forWRite?

 

Unfortunately, I'm in a rush today and cannot afford to give you the tailored specific answer that you are looking for, but what you need can be found quite satisfactorily here - this was how i successfully solved this exact problem yesterday (in C#). He can say it a thousand times better than I can.

 

Kean's blog particular blog post on block attributes:  http://through-the-interface.typepad.com/through_the_interface/2006/09/getting_autocad.html

 

 

gluck

 

 

 

 

 

 

 

Here's how I (successfully tackled the problem):

 

 

 

 

 

 

 

 

0 Likes
Message 13 of 17

Anonymous
Not applicable

Thank you both for the replies.

 

Keane's post referenced in the link has nothing to do with this situation as he doesn't address blocks with attributes as part of an MLeader.  I have no issues at all with stand alone blocks, dimensions, text, etc., I can manipulate them in any way I want.  I am having an issue with the attributes in blocks that are part of an MLeader.

0 Likes
Message 14 of 17

norman.yuan
Mentor
Mentor
Accepted solution

In the portion of code you showed, there is no need to open the BlockTableRecord and AttributeDefinition "ForWrite", because you only need to use the AttributeDefinition's Id to find corresponding AttributeReference in the MLeader.

 

However, you did not show the code how the MLeader is opened ("ForWrite" or "ForRead"), and did not indicate which line of code that caused "Not open for write" error. Have you stepped into the code line by line in debugging? If you had, you would have known which line of code causing the error and would have solved the problem by now. But if I have to guess, it is likely that this line causes the error:

 

Mldr.SetBlockAttribute(adef.Id, aref)

 

because the Mldr (MLeader entity) is not opened "ForWrite".

 

BTW, based on your partial code shown here, it is good to open an entity "ForRead" at beginning and examine if the entity is the target one. If it is, then call Entity.UpgradeOpen() to make it write-able only it is necessary.

Norman Yuan

Drive CAD With Code

EESignature

0 Likes
Message 15 of 17

Anonymous
Not applicable

You're dead on Norman and just resolved my issues and I heartily thank you.

 

Although I had the Mldr as open forwrite in a different portion of the code I had not maintained that to this portion.

0 Likes
Message 16 of 17

Anonymous
Not applicable
Hello,

is there a way to apply a colorindex to a specific Attributereference in given MLeader?
As far as I can see Mleader.SetBlockAttribute only takes care of the Textstring... or Am I missing something?
BR,
Daniel
0 Likes
Message 17 of 17

Anonymous
Not applicable

You should be able to change the color through the attribute record.

0 Likes