I have a lot of block’s (electrical symbols) in my toolpalettes. When I insert one into my drawing I want to show a custom attribute editor which contains comboboxes or other kind of controls to simply and quickly add value’s to their attributes. Maybe some attribute values can be modified or added programmatically but that’s for later.
What I first need to know is if it’s possible to show a form when I insert a block from my toolpalette without creating custom toolpalettes.
I know how to disable the advanced attribute editor with ATTREQ set to 0.
Which trigger can I use to start my own program?
Do I need to have another program running to detect changes in the blocktable or something like that?
Any help would be appreciated.
Solved! Go to Solution.
Solved by norman.yuan. Go to Solution.
You can use Database.ObjectAppended event as the trigger to catch the newly inserted block, and test if the block is the target block for custom attribute data input UI. If yes, show your custom UI (of course you need to make sure "ATTREQ" is set to "0" to avoid built-in attribute UI shows up, as you are already aware of. Here I assume you only want to show your custom UI for particular block or blocks for the specific set or sets of attributes. Most likely, the block name would be the identifier to decide whether custom Attribute UI is used or not. Handling ObjectAppended event would give you chance to know specific block has just been added into database, regardless which command does it, be it from command "INSERT" directly, or from toolpallete...
Following code shows how to do it:
using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Runtime; using System; using System.Collections.Generic; using CadApp = Autodesk.AutoCAD.ApplicationServices.Application; [assembly: ExtensionApplication(typeof(CustomAttEditForm.CustomAttributeEditor))] namespace CustomAttEditForm { public class CustomAttributeEditor : IExtensionApplication { public void Initialize() { var doc = CadApp.DocumentManager.MdiActiveDocument; try { HookEvenHandlers(); doc.Editor.WriteMessage("\nCustom attribute editor initialized.\n"); } catch(System.Exception ex) { doc.Editor.WriteMessage($"Initializing error\n{ex.Message}\n"); } } public void Terminate() { } private static void HookEvenHandlers() { var docs = CadApp.DocumentManager; docs.DocumentActivated += (o, e) => { e.Document.Database.ObjectAppended += Database_ObjectAppended; }; foreach (Document d in docs) { d.Database.ObjectAppended += Database_ObjectAppended; } } private static void Database_ObjectAppended(object sender, ObjectEventArgs e) { if (IsTargetBlock(e.DBObject)) { EditBlockAttribute(e.DBObject as BlockReference); } } private static bool IsTargetBlock(DBObject dbObject) { var blk = dbObject as BlockReference; if (blk!=null) { var attReq = Convert.ToBoolean(CadApp.GetSystemVariable("ATTREQ")); if (blk.Name.ToUpper() == "TestBlock".ToUpper() && !attReq) { return true; } } return false; } private static void EditBlockAttribute(BlockReference blk) { CadApp.ShowAlertDialog( "You are about to show custom attribute editing form.\n\n" + $"Block to be edited:\t {blk.Name}"); // Show attribute editing form that matches block's name (thus the // block's attributes) to collect attribute values var attNameList = GetAttributeNamesFromBlock(blk); var dic = GetUserInput(attNameList); if (dic!=null) { using (var tran = blk.Database.TransactionManager.StartTransaction()) { foreach (ObjectId id in blk.AttributeCollection) { var att = (AttributeReference)tran.GetObject(id, OpenMode.ForWrite); if (dic.ContainsKey(att.Tag.ToUpper())) { att.TextString = dic[att.Tag.ToUpper()]; } } tran.Commit(); } } } private static List<string> GetAttributeNamesFromBlock(BlockReference blk) { var lst = new List<string>(); using (var tran = blk.Database.TransactionManager.StartTransaction()) { foreach (ObjectId id in blk.AttributeCollection) { var att = (AttributeReference)tran.GetObject(id, OpenMode.ForRead); lst.Add(att.Tag); } tran.Commit(); } return lst; } private static Dictionary<string, string> GetUserInput(List<string> attNames) { Dictionary<string, string> dic = null; using (var dlg = new AttributeEditForm(attNames)) { var res = CadApp.ShowModalDialog(dlg); if (res== System.Windows.Forms.DialogResult.OK) { dic = new Dictionary<string, string>(); // fill the Dictionary object with user inputs } } return dic; } } }
In the code, I simply hard-coded the block name ("TestBlock") as the target block. I also omitted the form's detail. Pay attention to IsTargetBlock() method, which decides whether custom UI is needed or not.
See attached Video showing how the code works"
Norman Yuan
I'll suggest something a little bit different. Rather than inserting the blocks directly and handling that via events or other methods, you would create custom commands to handle the insertion. The commands can be placed on a palette just like a block, but you can also put them into the CUIx system so they can be called from the menu, Ribbon, or toolbars.
You could create one command per block but that may less-than-efficient for both development and the user. In most cases there would be several blocks that can be inserted from the same command via prompts or other input. You get to control which blocks are selected/inserted, and there's still the option of manually inserting them without any interruption.
Thank you very much for your extensive explanation Norman. That is exactly what I was looking for but I have some trouble to get it working. I know a little bit of writing code in VB.NET, I started programming 6 month's ago.
What I did is converting your code to VB.NET but I got some errors which I can not solve at this moment. So I tried your C# code. In C I have only one error message in Visual Studio. It's the red part in the code below.
I pasted your code in a class and I created a winform with the name AttributeEditForm. What am I missing here?
Thank you in advance
private static Dictionary<string, string> GetUserInput(List<string> attNames) { Dictionary<string, string> dic = null; using (var dlg = new AttributeEditForm(attNames)) { var res = CadApp.ShowModalDialog(dlg); if (res== System.Windows.Forms.DialogResult.OK) { dic = new Dictionary<string, string>(); // fill the Dictionary object with user inputs } } return dic; }
My code is only meant to show the entire process, so I omitted some details, especially the form. It is up to you to build a form suitable to edit a set of attributes from a block reference. Obviously, the form need some inputs to initialize. That is why the form in my sample code (AttributeEditForm) has an constructor that takes a List<string> parameter as input. The code for the form should be like this:
public partial class AttributeEditForm()
{
// This is the default constructor, automatically created by Visual Studio
public AttributeEditForm()
{
InitializeComponents()
}
// This is the constructor used in my code sample
public AttributeEditForm(List<string> attribueNames) : this()
{
// Vwry likely, you use the attribute names passed in to
// set labels for each textbox/dropdown for
// the attribute value field
}
... ...
... ...
}
Norman Yuan
Thanks again, I really appreciate your help.
I have de code working till the messagebox pops up. This is in C#. I know I have to add my own form with the textboxes and/or comboboxes to fill the attribute value's in the block's.
But, as I mentioned before, I wanted to make this code in VB. Here's were I am stuck now. The method of coding for some parts is totally different in C#, see code below.
Private Shared Sub HookEvenHandlers() Dim docs = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager docs.DocumentActivated += Function(o, e) e.Document.Database.ObjectAppended += AddressOf Database_ObjectAppended End Function For Each d As Document In docs d.Database.ObjectAppended += AddressOf Database_ObjectAppended Next End Sub
I searched the internet and tried different things but somehow it's not working in VB. I'm now at the point of considering to switch to C# unless someone knows how to fix this?
Here's the link to the Autodesk documentation.
Handle DocumentCollection Events (.NET)
In VB.NET, you would use statement "AddHandler..." to attach event handler. This this case, the code would look like:
Private Shared Sub HookEventHandlers()
Dim docs As DocumentCollection = ....
'' My original code was wrongly used DocumentActivated event. It should be DocumentCreated event
AddHandler docs.DocumentCreated, AddressOf DocumentManager_DocumentCreated
For Each doc As Document in docs
AddEventHandler doc.Database.ObjectAppended, AddressOf Database_ObjectAppended
Next
End Sub
Private Shared Sub DocumentManager_DocumentCreated(sender As Object, e As DocumentCollectionEventArgs)
AddEventHandler e.Document.Database, AddressOf Database_ObjectAppended
End Sub
Private Shared Sub Database_ObjectAppended(sender As Object, e As ObjectEventArgs)
If (IsTargetBlock(e.DBObject) Then
EditBlockAttribute(DirectCast(e.DBObject, BlockReference))
End If
End Sub
... ...
Norman Yuan
Thank you for your help Norman, it's working fine now. I have one little problem to fix but hopefully I can fix that by myself.
@ Dgorsman, thanks for your input. I thought about your solution, but for me it's best to have the same custom attribute editor for every block in my toolpalettes. That's because I have a lot of blocks on my toolpalettes and I add new ones from time to time. When I'm using a custom attribute editor I think I don't need to add block names to custom command's and/or modify their code.
Can't find what you're looking for? Ask the community or share your knowledge.