Hi,
is it possible to store a list of objectids in a global list. This list should be available each time I call a command.
Right I do not want to take care about a the mdi environment, I am just looking for a solution for a single document.
I have written a short class to clarify my requiement.
Imports Autodesk.AutoCAD.Runtime Imports Autodesk.AutoCAD.DatabaseServices Imports Autodesk.AutoCAD.ApplicationServices Imports Autodesk.AutoCAD.EditorInput Public Class Class1 Implements IExtensionApplication Private _lObjIds As List(Of ObjectId) Public Sub Initialize() Implements Autodesk.AutoCAD.Runtime.IExtensionApplication.Initialize _lObjIds = New List(Of ObjectId) End Sub Public Sub Terminate() Implements Autodesk.AutoCAD.Runtime.IExtensionApplication.Terminate End Sub <CommandMethod("gvSelectObject")> _ Public Sub gvSelectObject() Dim doc As Document = Application.DocumentManager.MdiActiveDocument Dim ed As Editor = doc.Editor Dim db As Database = doc.Database ' Select Object Dim pso As New PromptSelectionOptions pso.SingleOnly = True pso.MessageForAdding = "Select Object" Dim psr As PromptSelectionResult = ed.GetSelection(pso, Nothing) If psr.Status <> PromptStatus.OK Then ed.WriteMessage(vbCrLf & "Nothing selected") Exit Sub End If ' Check, if global list exists If _lObjIds Is Nothing Then ed.WriteMessage(vbCrLf & "Global List is not available") Exit Sub Else _lObjIds.Add(psr.Value.GetObjectIds(0)) ed.WriteMessage(vbCrLf & "Global List has {0} IDs", _lObjIds.Count) End If End Sub End Class
Do I have to use any flag for my method?
Would be nice if anyone could provide me a solution.
Regards
Michael
Solved! Go to Solution.
Solved by _gile. Go to Solution.
OK, sorry for wasting your time. I found the solution by myself.
The thraed from yesterday from jmaeding took me on the right way.
He linked to a post from Kean (http://through-the-interface.typepad.com/through_the_interface/2006/10/perdocument_dat_1.html)
I just needed to declare my variable an my method as shared.
Imports Autodesk.AutoCAD.Runtime Imports Autodesk.AutoCAD.DatabaseServices Imports Autodesk.AutoCAD.ApplicationServices Imports Autodesk.AutoCAD.EditorInput Public Class Class1 Implements IExtensionApplication Private Shared _lObjIds As List(Of ObjectId) Public Sub Initialize() Implements Autodesk.AutoCAD.Runtime.IExtensionApplication.Initialize _lObjIds = New List(Of ObjectId) End Sub Public Sub Terminate() Implements Autodesk.AutoCAD.Runtime.IExtensionApplication.Terminate End Sub <CommandMethod("gvSelectObject")> _ Public Shared Sub gvSelectObject() Dim doc As Document = Application.DocumentManager.MdiActiveDocument Dim ed As Editor = doc.Editor Dim db As Database = doc.Database ' Select Object Dim pso As New PromptSelectionOptions pso.SingleOnly = True pso.MessageForAdding = "Select Object" Dim psr As PromptSelectionResult = ed.GetSelection(pso, Nothing) If psr.Status <> PromptStatus.OK Then ed.WriteMessage(vbCrLf & "Nothing selected") Exit Sub End If ' Check, if global list exists If _lObjIds Is Nothing Then ed.WriteMessage(vbCrLf & "Global List is not available") Exit Sub Else _lObjIds.Add(psr.Value.GetObjectIds(0)) ed.WriteMessage(vbCrLf & "Global List has {0} IDs", _lObjIds.Count) End If End Sub End Class
Hi,
It looks like you didn't understand the per document vs per session behavior.
If you declare your collection as static (Shared) member of your class, this collection will be available in each document of the session.
If the field isn't declared static (as an instance member of the class), it will be available for this 'document instance' each time the command is called.
Try the 2 commands in the following code in a document, then in another one, the back in the first one...
public class Class1 { private static ObjectIdCollection ids = new ObjectIdCollection(); [CommandMethod("PerSession")] public static void PerSessionList() { Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; PromptEntityResult result = ed.GetEntity("\nSelect object: "); if (result.Status != PromptStatus.OK) return; ids.Add(result.ObjectId); ed.WriteMessage("\nSession List has {0} IDs", ids.Count); } } public class Class2 { private ObjectIdCollection ids = new ObjectIdCollection(); [CommandMethod("PerDocument")] public void PerDocumentList() { Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; PromptEntityResult result = ed.GetEntity("\nSelect object: "); if (result.Status != PromptStatus.OK) return; ids.Add(result.ObjectId); ed.WriteMessage("\nDocument List has {0} IDs", ids.Count); } }
The upper test can be done with a single command:
public class Class1 {
// static (Shared) field private static ObjectIdCollection globalIds = new ObjectIdCollection();
// instance field (related to each document calling the command) private ObjectIdCollection localIds = new ObjectIdCollection(); [CommandMethod("gvSelectObject")] public void gvSelectObject() { Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; PromptEntityResult result = ed.GetEntity("\nSelect object: "); if (result.Status == PromptStatus.OK) { globalIds.Add(result.ObjectId); localIds.Add(result.ObjectId); } ed.WriteMessage( "\nGlobal List has {0} IDs\nLocal LIST has {1} IDs", globalIds.Count, localIds.Count); } }
Hi Gilles,
Nice clear explanation of the session versus document storage but for what purpose is a session storage of ObjectIds?
As far as i know ObjectIds are private to documents and valid up to the closure of the document or am i wrong?
SENL1362 a écrit :
Hi Gilles,
Nice clear explanation of the session versus document storage but for what purpose is a session storage of ObjectIds?
As far as i know ObjectIds are private to documents and valid up to the closure of the document or am i wrong?
By my side I do not see any purpose to store ObjectIdS at session level, even ObjectIdS are unic in a session whatever the document they own.
I just tried to explain to MS7000 he was going the wrong route using a static/Shared field to store ObjectIds and how he can use a "document level global variable" with an 'document instance' field.
When we create a command method in a class, we have to think as if each first calling of the command in a document creates a new instance of this class which all non-static members are instance members of this 'document instance'.
Maybe using explicit constructors is more self explanatory:
public class TestClass { // static fields (session scope) declaration private static DocumentCollection docs; private static int sesCnt; // instance fields (document scope) declaration private Document doc; private Database db; private Editor ed; private static int docCnt; // static constructor (static fields initialization) // run once in the session at first command calling static TestClass() { docs = Application.DocumentManager; sesCnt = 0; docs.MdiActiveDocument.Editor.WriteMessage( "\nSession field initialization\nSession Counter = {0}", sesCnt); } // instance constructor (instance fields initialization) // run once in each document at first command calling public TestClass() { doc = docs.MdiActiveDocument; db = doc.Database; ed = doc.Editor; docCnt = 0; ed.WriteMessage(
"\nDocument fields initialization\nDocument Counter = {0}", docCnt); }
// using of static and instance fields [CommandMethod("TESTCMD")] public void TestCommand() { ed.WriteMessage( "\n{0}\nSession Counter = {1}\nDocument Couter = {2}", db.Filename, ++sesCnt, ++docCnt); } }
OK, I understand the difference between perDocument and perSession.
But I have another problem with the context.
- The id collection should exist in document focus.
-The id collection should be initialized while the assembly is loaded.
- The ObjectModified reactor should monitor the changes.
- With the method 'SelLine' a selected line should be added to the id collection.
In my code below the id collection for the reactor and for the method is different. Why?
How could I achieve the mentioned requirements?
Imports Autodesk.AutoCAD.Runtime Imports Autodesk.AutoCAD.DatabaseServices Imports Autodesk.AutoCAD.ApplicationServices Imports Autodesk.AutoCAD.EditorInput Public Class iExApp Implements IExtensionApplication Public Sub Initialize() Implements Autodesk.AutoCAD.Runtime.IExtensionApplication.Initialize Dim x As New Class1 End Sub Public Sub Terminate() Implements Autodesk.AutoCAD.Runtime.IExtensionApplication.Terminate End Sub End Class Public Class Class1 Private ids As New ObjectIdCollection() Public Sub New() AddHandler Application.DocumentManager.MdiActiveDocument.Database.ObjectModified, AddressOf OnObjectModified End Sub Private Sub OnObjectModified(sender As Object, e As Autodesk.AutoCAD.DatabaseServices.ObjectEventArgs) Dim ed As Editor = Application.DocumentManager.MdiActiveDocument.Editor ed.WriteMessage(vbLf & "Modify - Document List has {0} IDs", ids.Count) End Sub <CommandMethod("SelLine")> _ Public Sub SelLine() Dim ed As Editor = Application.DocumentManager.MdiActiveDocument.Editor ' Select Line Dim pso As New PromptSelectionOptions pso.SingleOnly = True pso.MessageForAdding = "Select Line" Dim psr As PromptSelectionResult = ed.GetSelection(pso, New SelectionFilter(New TypedValue() {New TypedValue(0, "LINE")})) If psr.Status <> PromptStatus.OK Then ed.WriteMessage(vbCrLf & "No line selected") Exit Sub End If ' Add line to collection ids.Add(psr.Value.GetObjectIds(0)) ed.WriteMessage(vbLf & "Document List has {0} IDs", ids.Count) End Sub End Class
Sorry, I do not understand your purpose.
As your SelLine command method is the only way to modify the ObjectId collection, it seems to me you do not need to create a new instance of Class1 at initialization (i.e. before the SelLine command is called), a new one will implicitly be created at the first call of the command (in each document).
The following code works for me (note the constructor can be used to initialize the fields).
public class Class1 { private ObjectIdCollection ids; private Document doc; private Editor ed; public Class1() { ids = new ObjectIdCollection(); doc = Application.DocumentManager.MdiActiveDocument; doc.Database.ObjectModified += onObjectModified; ed = doc.Editor; ed.WriteMessage("\nNew instance of Class1"); } void onObjectModified(object sender, ObjectEventArgs e) { ed.WriteMessage("\nModify - Document List has {0} IDs", ids.Count); } [CommandMethod("SelLine", CommandFlags.Modal)] public void SelLine() { PromptEntityOptions peo = new PromptEntityOptions("\nSelect Line: "); peo.SetRejectMessage("Only Line."); peo.AddAllowedClass(typeof(Line), true); PromptEntityResult per = ed.GetEntity(peo); if (per.Status != PromptStatus.OK) { ed.WriteMessage("\nNo line selected"); return; } ids.Add(per.ObjectId); ed.WriteMessage("\nDocument List has {0} IDs", ids.Count); } }
My last code sample is very reduced to the minimum. I tried to reduce it as good as possible.
l In reality I store my data (it is more than a objectidcollection) in a dictionary.
The stored data should be available just after the assembly has loaded for some reactors (e.g. ObjectModified). But the same date should also be available for some commands. The reactors and the commands should have access to the same data.
@Gilles: In your example the Class1 instance is not initialized until the command is called, and the reactor is not working.
MS7000 a écrit :@Gilles: In your example the Class1 instance is not initialized until the command is called, and the reactor is not working.
I said in my reply, due to the little I knew about your purpose, that it seems to me the initialization was unusefull.
MS7000 a écrit :
My last code sample is very reduced to the minimum. I tried to reduce it as good as possible.
l In reality I store my data (it is more than a objectidcollection) in a dictionary.
The stored data should be available just after the assembly has loaded for some reactors (e.g. ObjectModified). But the same date should also be available for some commands. The reactors and the commands should have access to the same data.
If your objectIdS are stored in a Dictionary/Xrecord, why do you need to store them again in an instance member ?
You'd directly read/write the Xrecord when needed.
@_gile wrote:
If your objectIdS are stored in a Dictionary/Xrecord, why do you need to store them again in an instance member ?
You'd directly read/write the Xrecord when needed.
I am not sure if I understand you right. I think it is not possible to read the dictionary directly. I think this would be a performance killer, as my reactors react e.g. for any object modification. If I would imagine that I e.g. move 1.000 entities the reactor would fire about 4.000 times for table and object modification.
I think I need to have my data in memory. I would like to read the data during initialization and write it during drawing save.
MS7000 a écrit :
I am not sure if I understand you right. I think it is not possible to read the dictionary directly. I think this would be a performance killer, as my reactors react e.g. for any object modification. If I would imagine that I e.g. move 1.000 entities the reactor would fire about 4.000 times for table and object modification.I think I need to have my data in memory. I would like to read the data during initialization and write it during drawing save.
IMO, the issue is more about handling events firing so often as ObjectModified than about reading a Dictionary...
Anyway, this works for me.
Initialization only calls the "initialize" command and handles the DocumentCreated event to do the same for each newly created document.
The "initialize" command does nothing. It's only called to implicitly create an instance of the Commands class for the active document.
The Commands class constructor does all the trick : initialization of instance members, ObjectModified event handling.
using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Runtime; using System; using AcAp = Autodesk.AutoCAD.ApplicationServices.Application; namespace DataStorage { public class Initilize : IExtensionApplication { DocumentCollection docs = AcAp.DocumentManager; public void Initialize() { docs.DocumentCreated += onDocumentCreated; AcAp.Idle += onIdle; }
public void Terminate() { }
void onIdle(object sender, EventArgs e) {
AcAp.Idle -= onIdle; docs.MdiActiveDocument.SendStringToExecute("initialize ", false, false, true); } void onDocumentCreated(object sender, DocumentCollectionEventArgs e) { AcAp.Idle += onIdle; } } public class Commands { ObjectIdCollection ids; Document doc; Database db; Editor ed; public Commands() { ids = new ObjectIdCollection(); doc = AcAp.DocumentManager.MdiActiveDocument; db = doc.Database; ed = doc.Editor; db.ObjectModified += onObjectModified; } [CommandMethod("initialize")] public void Intialize() { } [CommandMethod("SelLine")] public void SelLine() { PromptEntityOptions peo = new PromptEntityOptions("\nSelect Line: "); peo.SetRejectMessage("Only a Line."); peo.AddAllowedClass(typeof(Line), true); PromptEntityResult per = ed.GetEntity(peo); if (per.Status == PromptStatus.OK) ids.Add(per.ObjectId); ed.WriteMessage("\nDocument List has {0} IDs", ids.Count); } void onObjectModified(object sender, ObjectEventArgs e) { ed.WriteMessage("\nModify - Document List has {0} IDs", ids.Count); } } }