Recently, some code on web came into our view:
// Change all the entities of the specified type in a Database
// to the specified linetype
private void ChangeEntityLinetype(
Database db, System.Type t, string ltname
)
{
using (
var tr = db.TransactionManager.StartTransaction()
)
{
// Check whether the specified linetype is already in the
// specified Database
var lt =
(LinetypeTable)tr.GetObject(
db.LinetypeTableId, OpenMode.ForRead
);
if (!lt.Has(ltname))
{
// If not, load it from acad.lin
string ltpath =
HostApplicationServices.Current.FindFile(
"acad.lin", db, FindFileHint.Default
);
db.LoadLineTypeFile(ltname, ltpath);
}
// Now go through and look for entities of the specified
// class, changing each of their linetypes
var bt =
(BlockTable)tr.GetObject(
db.BlockTableId, OpenMode.ForRead
);
var ms =
(BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace],
OpenMode.ForRead
);
// Loop through the modelspace
foreach (var id in ms)
{
// Get each entity
var ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
if (ent != null)
{
// Check its type against the one specified
var et = ent.GetType();
if (et == t || et.IsSubclassOf(t))
{
// In the case of a match, change the linetype
ent.UpgradeOpen();
ent.Linetype = ltname;
}
}
}
tr.Commit();
}
}
Its purpose as commented was to find all entities of a certain type from an AutoCAD database and change their line types to the one as specified. The method should be functional in the case as demonstrated there and should apply to the particular environment and settings of the AutoCAD there. However, if we use it for broader situations, it may just cause issues here or there.
Firstly, the System.Type argument can represent pretty anything in the .NET world rather than only restricted to AutoCAD Entity type as indicated by the implementation. It will not only cause confusions to callers but also ignore the benefits of the wonderful .NET compilers. Supposing a WPF Ellipse type is provided to the method, the Visual Studio .NET compiler won’t complaint anything about it, but problems would occur at run-time or no errors would be reported against the wrong usage at all.
Secondly, something was taken for granted. That is, it arbitrarily thinks that the line type file that callers want to use must be the acad.line in the AutoCAD search paths. It is not true. Even for those line type files provided by AutoCAD natively, there are two, the acad.lin as took for granted and the acadiso.lin for metric units. In fact, AutoCAD line types can come from any valid .lin files, and they had better be defined in template files instead of being loaded on the fly.
Thirdly, some code is made unnecessarily complicated. For example, why is it necessary to open the Entity for Read at the first place and update it to Write right after? If the entity is of concern, the writing operation should be done directly and efficiently.
Fourthly, the code is not concise enough. Except for many unnecessary comments, some code is redundant.
A better version?
Surely is there.
public static void ChangeEntityLinetype<T>(Database db, string targetName) where T : Entity
{
using (Transaction tr = db.TransactionManager.StartTransaction())
{
LinetypeTable lt = (LinetypeTable)tr.GetObject(db.LinetypeTableId, OpenMode.ForRead);
if (!lt.Has(targetName)) throw new System.Exception(string.Format("The linetype {0} is not available.", targetName));
BlockTableRecord ms = (BlockTableRecord)tr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForRead);
foreach (ObjectId id in ms)
{
T ent = tr.GetObject(id, OpenMode.ForWrite) as T;
if (ent != null) ent.Linetype = targetName;
}
tr.Commit();
}
}
As seen, the method is made generic. By doing so, the .NET compiler will do valid checking for any calling code at compiling time, and we don’t have to cast the DBObject to Entity which is not of our real concern or do any unnecessary comparisons other than simply checking the T variable is null or not. The opened object is for writing directly. If the line type is not available in the database, the code simply throws out an exception. The code is much more concise, only around ten lines.
Here is a test command for the generic method.
[CommandMethod("TestChangeEntityLinetype1")]
public static void TestChangeEntityLinetype1_Method()
{
MgdAcDocument curDoc = MgdAcApplication.DocumentManager.MdiActiveDocument;
try
{
ChangeEntityLinetype<Circle>(curDoc.Database, "DOT");
}
catch (System.Exception ex)
{
curDoc.Editor.WriteMessage(ex.Message);
}
}
In case the line type is available in the database, all circles will be changed to that line type; otherwise a friendly message will be printed out onto the command line window so that users know what to do.
“The linetype DASHED is not available.”
The generic method can be turned into an Extension Method to make use easier and clearer. Here it is along with a test command.
public static class ExtensionMethods
{
public static void ChangeEntityLinetype2<T>(this Database db, string targetName) where T : Entity
{
using (Transaction tr = db.TransactionManager.StartTransaction())
{
LinetypeTable lt = (LinetypeTable)tr.GetObject(db.LinetypeTableId, OpenMode.ForRead);
if (!lt.Has(targetName)) throw new System.Exception(string.Format("The linetype {0} is not available.", targetName));
BlockTableRecord ms = (BlockTableRecord)tr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForRead);
foreach (ObjectId id in ms)
{
T ent = tr.GetObject(id, OpenMode.ForWrite) as T;
if (ent != null) ent.Linetype = targetName;
}
tr.Commit();
}
}
[CommandMethod("TestChangeEntityLinetype2")]
public static void TestChangeEntityLinetype2_Method()
{
MgdAcDocument curDoc = MgdAcApplication.DocumentManager.MdiActiveDocument;
try
{
curDoc.Database.ChangeEntityLinetype2<Circle>("DOT");
}
catch (System.Exception ex)
{
curDoc.Editor.WriteMessage(ex.Message);
}
}
}
Enjoy it!
The leading edge AutoCAD .NET Addin Wizard (AcadNetAddinWizard) provides various project wizards, item wizards, coders and widgets to help program AutoCAD .NET addins.
Recent Comments