public static class DocumentExtensionMethods
{
// Locks the active document, starts a transaction, and
// calls a delegate that takes a Database as its only
// parameter. This design allows the called delegate to
// have no dependence on AcMgd.dll (the AutoCAD application
// layer), which permits the delegate to be used in RealDwg
// applications, outboard databases opened via ReadDwgFile(),
// and in the AutoCAD core console in addition to being used
// on databases open in the AutoCAD editor.
//
// This extension method also demonstrates a more compelling
// and useful purpose to and reason for maintaining a clear
// separation between implementation and UI (command methods
// are actually a just a non-graphical type of UI).
public static void ExecuteInTransaction( this DocumentCollection docs,
Action<Database> action )
{
Document doc = docs.MdiActiveDocument;
using( doc.LockDocument() )
using( Transaction tr = doc.TransactionManager.StartTransaction() )
{
action( doc.Database );
tr.Commit();
}
}
// This is like the above, but returns the result that
// is returned by the passed delegate, where T is the
// type of the result:
public static T ExecuteInTransaction<T>( this DocumentCollection docs,
Func<Database, T> func )
{
Document doc = docs.MdiActiveDocument;
using( doc.LockDocument() )
using( Transaction tr = doc.TransactionManager.StartTransaction() )
{
T result = func( doc.Database );
tr.Commit();
return result;
}
}
}
// Example usage of the above extension methods:
public static class MyCommands
{
// This uses a lambda expression:
[CommandMethod("MYCOMMAND")]
public static void MyCommandMethod()
{
Application.DocumentManager.ExecuteInTransaction(
delegate( Database db )
{
// Erase all circles in the current space, using
// helper APIs posted elsewhere on this site, cause
// I'm too damn lazy to write out the 'longhand' :p)
var circles = db.CurrentSpace().GetObjects<Circle>();
foreach( Circle circle in circles.UpgradeOpen() )
{
circle.Erase();
}
}
);
}
// This version uses a separate method which could be
// in another class, or in another assembly that has no
// dependence on AcMgd.dll (the AutoCAD application layer),
// and is included here for the purpose of demonstrating
// how to properly separate implementation from UI:
[CommandMethod("MYCOMMAND2")]
public static void MyCommand2Method()
{
Application.DocumentManager.ExecuteInTransaction( EraseAllCircles );
}
// This method is included in this class for illustration only,
// but could also be in another assembly that has no dependence
// on AcMgd.dll (the AutoCAD application layer). That allows the
// method to be used on outboard/external databases from within
// AutoCAD, or from a RealDwg application, or the core console in
// AutoCAD 2013. For that reason the method takes a Database as
// its parameter, rather than a Document:
static void EraseAllCircles( Database db )
{
var circles = db.CurrentSpace().GetObjects<Circle>();
foreach( Circle circle in circles.UpgradeOpen() )
{
circle.Erase();
}
}
// This example uses a called method that returns the sum of
// the area of all closed polylines in the modelspace of the
// active document, but that same method could also be used
// on any database in AutoCAD, a RealDwg host application, or
// the 2013 core console. The aspect of the 'separation' that
// isn't shown here is that methods like the following should
// reside in a separate assembly that has no dependence
// on AcMgd.dll, following the same pattern of separation used
// by AutoCAD's own managed API (where AcDbMgd.dll has no
// dependence on AcMgd.dll).
[CommandMethod("SUMCURVEAREA")]
public static void SumCurveArea()
{
var docs = Application.DocumentManager;
double area = docs.ExecuteInTransaction( SumCurveArea );
docs.MdiActiveDocument.Editor.WriteMessage(
"Area of all closed polylines: {0} sq units", area );
}
// This method would be in another assembly that has no
// dependence on AcMgd.dll (the AutoCAD application layer):
public static double SumCurveArea( Database db )
{
return db.CurrentSpace().GetObjects<Polyline>()
.Where( pline => pline.Closed )
.Sum( pline => pline.Area );
}
}