TheSwamp
Code Red => .NET => Topic started by: jmaeding on January 12, 2007, 01:18:10 PM
-
I am in the process of building my library of subroutines for .net programming with acad.
I have one that retrieves a table record, like a layer or linetype, based on the name or objectID.
I have only done it for the layer name so far, and want to mutate this so it looks up any kind of table object based on the name:
Public Function getLayerObjNet(ByVal name As String) As LayerTableRecord
Dim db As Database = HostApplicationServices.WorkingDatabase()
Dim tm As Autodesk.AutoCAD.DatabaseServices.TransactionManager = db.TransactionManager
Dim layTblRec As LayerTableRecord = Nothing
Using myT As Transaction = tm.StartTransaction()
Try
Dim layTbl As LayerTable = CType(myT.GetObject(db.LayerTableId, OpenMode.ForRead, False), LayerTable)
If layTbl.Has(name) Then
layTblRec = CType(myT.GetObject(layTbl.Item(name), OpenMode.ForRead), LayerTableRecord)
End If
myT.Commit()
Catch
layTblRec = Nothing
Finally
myT.Dispose()
End Try
End Using
Return layTblRec
End Function
I am wondering these things:
1) To make this work for ObjectID input, I think I can just make another function to set up an overload:
Public Function getLayerObjNet(ByVal oID as ObjectID) As LayerTableRecord
...
Is that a good way to do this? (may be the only way...)
2) Is my error checking ok, any comments welcome.
3) If I want to add a param to make this work for all tables, it would be like this:
Public Function getAnyObjNet(ByVal name As String, ByVal tableType As String) As LayerTableRecord
Now, of course, the return type cannot be LayerTableRecord, it has to switch to the type specified in the tableType param.
Is there an easy way to do this? Can I somehow tell it to look up the object type from a string name? Maybe that is what CType can do, I thought that only casts one object type to another though.
Once I know how to look up an object type with a string, I can make much more generic code, that adapts to the object types I am feeding in.
It would be great if there was something like:
GetObjectType("LayerTableRecord") because I would use a variable for the string of course. I have a feeling that is super easy...
thanks for any help.
-
Take a look at the SymbolTable and SymbolTableRecord types for starters. All of the symbol tables (i.e. BlockTable, LayerTable) inherit from SymbolTable, so you can treat them all generically as SymbolTable objects. The same relationship exists between SymbolTableRecord and, for instance, BlockTableRecord.
-
One more thing, is it a bad idea to get an object with such a routine, and not do anything to "close" it?
It seems like if I get a reference to an object, I must release that at some point.
Maybe that is only if I open it for write. I get confused between lisp/VB/ and .net...
-
wow, things get deep fast. I have read a bunch on inheritance, but not used it much in this way.
Can I use those object types generically, like:
Public Function getLayerObjNet(ByVal name As String) As SymbolTableRecord ?
if I can, is this a form of late binding?
Also, do I need to cast that at some point to the desired type so I can use the props and methods of, say, a layer object?
To me, this casting business is the strangest thing to me compared to VB6. Its funny how it does not get a lot of press in tutorials.
I hate to seem so ignorant, I don't forget though once I understand it. thanks a bunch
-
Hi James.
I'm horrible with VB .. it give me a mental block.
I hope you have good fortune with it.
1) Personally, Yes I'd just overload the Method.
2) see initial comment.
3) For now, I'm write purpose built routines.
-
I don't know much about how garbage collection works in .NET, so I can't really answer your first question.
This is a form of late binding, and will have some runtime cost, though I doubt that cost will be significant.
You will need to cast the object to its specific type if you want to access any methods or properties that are not part of the base class interface.
The nice part is you probably can reduce what might otherwise have been a group of functions into a single one (I say probably because I haven't actually tried this in .NET just C++).
-
cool, I'll post what I come up with.
This is all for that modeless dialog tool I wrote about in another post so I'll post the whole project when done.
-
So this is what I have so far for the "generic" table functions:
Public Function getAnyObjNet(ByVal name As String, ByVal tableType As String) As SymbolTableRecord
Dim db As Database = HostApplicationServices.WorkingDatabase()
Dim tm As Autodesk.AutoCAD.DatabaseServices.TransactionManager = db.TransactionManager
Dim symTblRec As SymbolTableRecord = Nothing
Using myT As Transaction = tm.StartTransaction()
Try
Dim symTbl As SymbolTable = CType(myT.GetObject(db.LayerTableId, OpenMode.ForRead, False), tableType) '<---- not correct
'cannot use tableType as variable for object type, must use explicit name like LayerTable, need ideas here
If symTbl.Has(name) Then
symTblRec = CType(myT.GetObject(symTbl.Item(name), OpenMode.ForRead), SymbolTableRecord) '<---- works but
'I would prefer to send back correct cast of object even though function says "As SymbolTableRecord.."
End If
myT.Commit()
Catch
symTblRec = Nothing
Finally
myT.Dispose()
End Try
End Using
Return symTblRec
End Function
So while I can stay generic in most places, I must specify what kind of table I want to look up at some point.
I am also wondering if I can return a specific object type like LayerTableRecord, even though my function says "as SymbolTableRecord".
thanks
-
Having taken a closer look at it, I'm not sure this can be done in .NET the way I would do it in C++.
I would use template programming to do something like this in C++ :
AcDbSymbolTablePointer<TableType> symbolTable(pDb, AcDb::kForRead);
where TableType is a template parameter to the function that defines the type of symbol table I am interested in opening.
The paradigm in .NET seems to be something like:
Database db = HostApplicationServices.WorkingDatabase;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
LayerTable table = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead);
}
I can't find anything in AutoCAD's managed API that offers the same genericness as the C++ version.
Sorry for the bum steer.
-
ah, a challenge. Lets see how this develops. I'll post on the adesk NG's too..
-
I think Tony T. might have an example of this. I will see if I can find it, or if I just imaged it out of thin air.
-
Okay I think this is what I'm talking about < Link > (http://www.theswamp.org/index.php?topic=12123.msg150999#msg150999)
Here is something else from the same topic by Ton T. < Link > (http://www.theswamp.org/index.php?topic=12123.msg151231#msg151231)
-
Okay I think this is what I'm talking about < Link > (http://www.theswamp.org/index.php?topic=12123.msg150999#msg150999)
Here is something else from the same topic by Ton T. < Link > (http://www.theswamp.org/index.php?topic=12123.msg151231#msg151231)
Very nice. Thanks.
-
OT:
Just a question James, isnt
Using myT As Transaction = tm.StartTransaction()
And
myT.Dispose()
redundant? myT should be disposed at the end of the using block correct?
edit
I don’t think it hurts anything, I just wondered if the using statement in vb acted the same as in c#
-
Would passing the database along with the layer name be worth while?
Public Function getLayerTableRecordByName(dwgDB as Database, ByVal name As String) As LayerTableRecord
-
http://discussion.autodesk.com/thread.jspa?threadID=533848
-
Hello Nathan,
Is that a translation of Tony's
public static ObjectId GetTableRecordId( ObjectId TableId, string Name )
From the Link posted here by Tim ?
You can post code here directly if you like .. :-)
-
Hello Nathan,
Is that a translation of Tony's
public static ObjectId GetTableRecordId( ObjectId TableId, string Name )
From the Link posted here by Tim ?
You can post code here directly if you like .. :-)
Hi Kerry, Yes it is.
Public Function GetTableRecordId(ByVal objTableId As ObjectId, ByVal strName As String) As ObjectId
GetTableRecordId = ObjectId.Null
Dim objDB As Database = objTableId.Database
Dim objTrans As Transaction = objDB.TransactionManager.StartTransaction
Dim objTable As SymbolTable = CType(objTrans.GetObject(objTableId, OpenMode.ForRead, False), SymbolTable)
If objTable.Has(strName) Then
Dim objID As ObjectId
objID = objTable(strName)
If objID.IsErased Then
For Each objID In objTable
If Not objID.IsErased Then
Dim objSTR As SymbolTableRecord = CType(objTrans.GetObject(objID, OpenMode.ForRead, False), SymbolTableRecord)
If String.Compare(objSTR.Name, strName, True) = 0 Then
GetTableRecordId = objID
Exit For
End If
End If
Next
Else
GetTableRecordId = objID
End If
End If
objTrans.Commit()
objTrans.Dispose()
End Function
-
so that would require it to be done in three steps right?
1) get the table ID
2) get the record objectID
3) cast it to the desired specific record type
I am wondering if its such a good idea to split things up so much. I think that requires three transactions.
These patterns are so new to me, but I bet the arx crowd knows exactly how they like to organize subroutines like this.
That would sure be useful to hear a commentary on how experinced .net or arx'ers like to do things.
One thing I noticed is the code posted uses an ObjectId.Null as the default should the function not find what it was looking for.
Is that any better than using GetTableRecordId = nothing? Why not return nothing rather than a null ObjectID?
With subroutines, you are always testing for valid returns. I tend to test for nothing for object returns, and special values for number and string functions. What do you guys typically do?
-
so that would require it to be done in three steps right?
1) get the table ID
2) get the record objectID
3) cast it to the desired specific record type
I am wondering if its such a good idea to split things up so much. I think that requires three transactions.
These patterns are so new to me, but I bet the arx crowd knows exactly how they like to organize subroutines like this.
That would sure be useful to hear a commentary on how experinced .net or arx'ers like to do things.
One thing I noticed is the code posted uses an ObjectId.Null as the default should the function not find what it was looking for.
Is that any better than using GetTableRecordId = nothing? Why not return nothing rather than a null ObjectID?
With subroutines, you are always testing for valid returns. I tend to test for nothing for object returns, and special values for number and string functions. What do you guys typically do?
Here is a sample I posted at http://discussion.autodesk.com/thread.jspa?threadID=529533
Private Sub Sample()
AddRegAppTable("YourApplicationName")
End Sub
Private Sub AddRegAppTable(ByVal strAppName As String)
Dim objDB As Database = HostApplicationServices.WorkingDatabase
Dim objTrans As Transaction = objDB.TransactionManager.StartTransaction
Dim objRAT As RegAppTable = CType(objTrans.GetObject(objDB.RegAppTableId, OpenMode.ForWrite, False), RegAppTable)
Dim objRATR As RegAppTableRecord
Dim objRATRID As ObjectId = GetTableRecordId(objDB.RegAppTableId, strAppName)
If objRATRID = ObjectId.Null Then
objRATR = New RegAppTableRecord
objRATR.Name = strAppName
objRAT.Add(objRATR)
objTrans.AddNewlyCreatedDBObject(objRATR, True)
End If
objTrans.Commit()
objTrans.Dispose()
End Sub
The example might answer your questions if not I will explain.
Regards - Nathan
-
I get the idea of avoiding passing objects and using id's instead, but the code posted is a sub. I am wondering about functions that return things.
-
I get the idea of avoiding passing objects and using id's instead, but the code posted is a sub. I am wondering about functions that return things.
Hi James,
I understand you are trying to simplify things as much as possible. I think returning an ObjectID is the simplest option and would seem to be good paractice given all the AutoCAD methods use this approach. It is probably a bad idea to open an object with a transaction within a function and the return that object. Although if you still wanted to go down that route maybe passing the transaction to the function by reference might be the right approach. I am not overly experienced so take my advice with a grain of salt and I would be happy for others to comment.
I will comment my example to perhaps explain what I think better.
Private Sub Sample()
AddRegAppTable("YourApplicationName")
End Sub
'Example adds a Record to the Registered Application Table if it does not exist
Private Sub AddRegAppTable(ByVal strAppName As String)
Dim objDB As Database = HostApplicationServices.WorkingDatabase
Dim objTrans As Transaction = objDB.TransactionManager.StartTransaction
'Get Registered Application table using existing Transaction
Dim objRAT As RegAppTable = CType(objTrans.GetObject(objDB.RegAppTableId, OpenMode.ForWrite, False), RegAppTable)
Dim objRATR As RegAppTableRecord
'Get ObjectID of Record using posted GetTableRecordId fuction note how simple it is to get ObjectID of desired table using existing Datbase
Dim objRATRID As ObjectId = GetTableRecordId(objDB.RegAppTableId, strAppName)
'Test if Record existed note I don't know if there are any benefits using ObjectId.Null over Nothing but since there is a null value of the return type it makes sense to me to use it
If objRATRID = ObjectId.Null Then
'Record did not exist so create it
objRATR = New RegAppTableRecord
objRATR.Name = strAppName
objRAT.Add(objRATR)
objTrans.AddNewlyCreatedDBObject(objRATR, True)
Else
'Record exists although we are not using it get it for the example using existing transaction
objRATR = CType(objTrans.GetObject(objRATRID, OpenMode.ForWrite, False), RegAppTableRecord)
End If
objTrans.Commit()
objTrans.Dispose()
End Sub
I hope that helps.
Regards - Nathan
-
Interesting, I do like the flow of your code, that does not seem overly chopped up.
I am very interested in your comment on dealing with an object as opposed to an objectID. Somehow, that nags at me like it will come back to bite me (my method, not yours).
I am learning fast how sensitive acad is to an error in a dll. I was crashing acad on stuff like converting color objects...
Thanks for the reply, we'll get to bullfrog status yet!
-
by the way, got this code from ADN...answers my question on a generic lookup function:
Public Function getAnyObjNet(ByVal name As String, ByVal tableTypeId As ObjectId) As SymbolTableRecord
Dim db As Database = HostApplicationServices.WorkingDatabase()
Dim tm As Autodesk.AutoCAD.DatabaseServices.TransactionManager = db.TransactionManager
Dim symTblRec As SymbolTableRecord = Nothing
Using myT As Transaction = tm.StartTransaction()
Try
Dim symTbl As SymbolTable = myT.GetObject(tableTypeId, OpenMode.ForRead, False)
If symTbl.Has(name) Then
symTblRec = myT.GetObject(symTbl.Item(name), OpenMode.ForRead)
End If
myT.Commit()
Catch
symTblRec = Nothing
Finally
myT.Dispose()
End Try
End Using
Return symTblRec
End Function
'Example usage:
'Define command 'test'
<CommandMethod("test")> _
Public Sub Asdkcmd1()
' Type your code here
Dim db As Database = HostApplicationServices.WorkingDatabase
Dim str As SymbolTableRecord = getAnyObjNet("*Model_Space", db.BlockTableId)
Dim tm As Autodesk.AutoCAD.DatabaseServices.TransactionManager = db.TransactionManager
Using myT As Transaction = tm.StartTransaction()
If str.Name = "*Model_Space" Then
Dim btr As BlockTableRecord = CType(str, BlockTableRecord)
End If
End Using
End Sub
now that wasn't so hard was it? You just Use an * to tell it the string is meant as an object type.
-
I guess that does not do what I was thinking, it still needs to grab the table outside and cast after.
It does answer a question though.
Notice that an object was returned by the getAnyObjNet function.
Then later, a transaction was opened, and the object cast to the desired type.
It seems it is ok to pass objects around, and do any changes within a transaction.
Note that the code does not commit or dispose of the transaction though, I think it needs to commit. The Using method will dispose for us, but I think a commit is needed still.
-
I guess that does not do what I was thinking, it still needs to grab the table outside and cast after.
It does answer a question though.
Notice that an object was returned by the getAnyObjNet function.
Then later, a transaction was opened, and the object cast to the desired type.
It seems it is ok to pass objects around, and do any changes within a transaction.
Note that the code does not commit or dispose of the transaction though, I think it needs to commit. The Using method will dispose for us, but I think a commit is needed still.
The thing I don't like about the code is that the function is determining the OpenMode for the object. So the function is not as generic. Although you could pass the OpenMode as another parameter to the function.
Regards - Nathan
-
oh, good point, I'll change that