In the last post we introduced a static C# class containing extension methods for the ObjectId and Transaction classes. The new Transaction methods allow you to more easily “lock” objects, whether because they’re “system” objects you want to keep around in every drawing or because they’re objects that shouldn’t be purged at whim by users.
Under the hood, the implementation uses Xrecords stored in the Named Objects Dictionary that contain hard-pointer references to the various locked objects. This stops the PURGE command from removing them, but also allows us to check via Database.Purge() – or our new ObjectId.IsErasable() shortcut – to see whether they can safely be erased by our application.
In today’s post we’re going to see some code that exercises these methods and shows how the PURGE command reacts once objects have been locked. In fact, here’s what the PURGE command shows after we’ve used LC command, below, to lock parts of the drawing, collection by collection.
(This was created from a sequence of images: there isn’t a magic way for our code to lock parts of the drawing while the PURGE command is active, in case people start wondering. :-)
Here’s the C# code for our commands. The assumption is that it’s placed in the same source project as the code from the last post (the name of the files shouldn’t matter).
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using LockingObjects;
namespace ObjectLockingCommands
{
public class Commands
{
const string lockKey = "TtifObjectLock";
// Command to print the erasability of the objects in the drawing
[CommandMethod("CO")]
public void CheckObjects()
{
var doc = Application.DocumentManager.MdiActiveDocument;
if (doc == null) return;
var db = doc.Database;
var ed = doc.Editor;
using (var tr = db.TransactionManager.StartTransaction())
{
var ids = tr.GetAllObjectsToLock(db);
foreach (ObjectId id in ids)
{
ed.WriteMessage(
"\n{0}: {1} ({2})", id, id.ObjectClass.Name, id.IsErasable()
);
}
tr.Commit();
}
}
// Lock all the objects currently in the drawing using a master key
[CommandMethod("LO")]
public void LockObjects()
{
var doc = Application.DocumentManager.MdiActiveDocument;
if (doc == null) return;
var db = doc.Database;
var ed = doc.Editor;
using (var tr = db.TransactionManager.StartTransaction())
{
tr.LockObjects(tr.GetAllObjectsToLock(db), lockKey);
// Commit the transaction
tr.Commit();
}
}
// Unlock the objects locked by our master key
[CommandMethod("UO")]
public void UnlockAllObjects()
{
var doc = Application.DocumentManager.MdiActiveDocument;
if (doc == null) return;
var db = doc.Database;
using (var tr = db.TransactionManager.StartTransaction())
{
tr.RemoveObjectLock(db, lockKey);
tr.Commit();
}
}
// Lock the specified collection in the drawing
[CommandMethod("LC")]
public void LockCollection()
{
var doc = Application.DocumentManager.MdiActiveDocument;
if (doc == null) return;
var db = doc.Database;
var ed = doc.Editor;
// Names must be in the same order as the ObjectLocking.Contents enum
var collectionNames = new string[] {
"Named Objects Dictionary",
"Block Table",
"DimStyle Table",
"Layer Table",
"Linetype Table",
"RegApp Table",
"TextStyle Table",
"Ucs Table",
"Viewport Table",
"View Table"
};
for (int i = 0; i < collectionNames.Length; i++)
{
ed.WriteMessage("\n{0} - {1}", i + 1, collectionNames[i]);
}
var pio = new PromptIntegerOptions("\nEnter collection number");
pio.AllowNone = false;
pio.AllowZero = false;
pio.AllowNegative = false;
pio.LowerLimit = 1;
pio.UpperLimit = collectionNames.Length;
var pir = ed.GetInteger(pio);
if (pir.Status != PromptStatus.OK)
return;
using (var tr = db.TransactionManager.StartTransaction())
{
// Use the position in the list to determine the bit to set
// (this is a bit lazy: it would be better to use more code to
// correlate the chosen option with its enum value)
var ids =
tr.GetObjectsToLock(
db, (ObjectLocking.Contents)(System.Math.Pow(2, pir.Value - 1))
);
string key = null;
bool exists = false;
do
{
var psr = ed.GetString("\nEnter lock name");
if (psr.Status != PromptStatus.OK)
return;
key = psr.StringResult;
exists = tr.HasObjectLock(db, key);
if (exists)
ed.WriteMessage("\nLock object already exists.");
}
while (exists);
tr.LockObjects(ids, key);
// Commit the transaction
tr.Commit();
}
}
// Unlock the objects locked by a specific key
[CommandMethod("UC")]
public void UnlockCollection()
{
var doc = Application.DocumentManager.MdiActiveDocument;
if (doc == null) return;
var db = doc.Database;
var ed = doc.Editor;
using (var tr = db.TransactionManager.StartTransaction())
{
var names = tr.GetObjectLocks(db);
if (names == null || names.Length == 0)
{
ed.WriteMessage("\nNo lock objects in drawing.");
}
else
{
for (int i = 0; i < names.Length; i++)
{
ed.WriteMessage("\n{0} - {1}", i+1, names[i]);
}
var pio = new PromptIntegerOptions("\nEnter lock object number");
pio.AllowNone = false;
pio.AllowZero = false;
pio.AllowNegative = false;
pio.LowerLimit = 1;
pio.UpperLimit = names.Length;
var pir = ed.GetInteger(pio);
if (pir.Status == PromptStatus.OK)
{
tr.RemoveObjectLock(db, names[pir.Value - 1]);
}
}
// Commit the transaction
tr.Commit();
}
}
}
}
Here are the various commands and what they do:
- CO (CheckObjects)
- Command to print the erasability of the objects in the drawing
- LO (LockObjects)
- Lock all the objects currently in the drawing using a master key
- UO (UnlockAllObjects)
- Unlock the objects locked by our master key
- LC (LockCollection)
- Lock the specified collection in the drawing
- UC (UnlockCollection)
- Unlock the objects locked by a specific key
The CO command is a bit like the PURGE command, in that it gives you a sense of which objects are erasable and which are not.
When I test the functioning of the locking mechanism, I typically start by running CO, then use one of the locking commands – whether LO to lock everything currently in the drawing or LC to lock a specific collection – and then run CO or PURGE to make sure the specified objects are locked.
If you scan through the results of CO on the command-line after running LO, you’ll actually see there’s a single object that’s erasable. That’s our Xrecord: it contains hard-pointers to all the other objects in the drawing, but not to itself (of course).
You can use LC to lock a number of different collections, one after the other – each getting a unique key name – to show that the locking mechanism is quite flexible and you can have locks applied at different times for different categories of object.
I hope you’ve found this mini-series of posts useful. The principle can certainly be applied in a number of situations – I’d be curious to hear how it gets used, over time. Please do post a comment if you find an interesting use for it.