Since your own code causes the changes, surely you can right the code to reverse the changes back. But the code to reversing things back could be very tedious to write.
All AutoCAD users know the "UNDO" command (or simply press "Ctrl+Z") is very useful to reverse changes made by previous commands back.
When doing .NET programming, the Transaction class does provides a mechanism of undoing changes back by rolling back or aborting the working transaction. One of my previous post touched this topic here. However, if the process of changing is quite complicated and some changes must be first applied to the drawing database before it can be presented to user to decide whether the changes should stay or to be rolled back, then using Transaction as the mechanism of undoing in code might be quite difficult to implement. So, why not utilize AutoCAD's built-in "UNDO" command to make the coding easier?
I recently worked on a project for a drafting tool, which starts a custom command and then leads user to create a serious entities and the end of the process. Since the user is guided via command line prompt, based on previous steps, the entities created have to be added into drawing database in order to give user clear visual feedback on what have been done in the process. At the end, I thought it would be better to allow user to decide he/she wants to cancelled the command has done so far. This this particular case, writing code to reverse back the changes are quite difficult, if not impossible. Well, I can let the command end and just tell user that if he/she still can execute UNDO command to reverse back the changes done by the custom command. However, it would be much better the custom command itself can undo the changes based on user interaction. Thus, I wrote some code and tried with satisfactory result, which is presented in this post.
Here what I did is to use AutoCAD built-in UNDO command to set an UNDO mark and to undo the changes back to the UNDO mark. However, the trick is how the call UNDO command to set UNDO mark before the main process in the custom command and how to call UNDO command to roll changes back when necessary.
The code presented here is pretty self-descriptive. So, no more explanation is needed. Here is the code:
First, this is a class to manipulate drawing database:
1 using System.Windows.Forms;
2 using Autodesk.AutoCAD.ApplicationServices;
3 using Autodesk.AutoCAD.DatabaseServices;
4 using Autodesk.AutoCAD.EditorInput;
5 using Autodesk.AutoCAD.Geometry;
6
7 namespace UndoableCommand
8 {
9 public class DoWorkTool
10 {
11 private Document _dwg;
12
13 public DoWorkTool(Document dwg)
14 {
15 _dwg = dwg;
16 }
17
18 public bool CreateSomething()
19 {
20 int count = AddEntities();
21
22 if (count > 0)
23 {
24 string msg = string.Format("{0} circle{1} created." +
25 "\n\nDo you want to keep {2}?",
26 count, count > 1 ? "s" : "", count > 1 ? "them" : "it");
27
28 return MessageBox.Show(msg, "Do work tool question",
29 MessageBoxButtons.YesNo,
30 MessageBoxIcon.Question) == DialogResult.Yes;
31 }
32 else
33 {
34 return true;
35 }
36 }
37
38 //This method creates circles in a loop
39 private int AddEntities()
40 {
41 int count = 0;
42
43 using (Transaction tran =
44 _dwg.TransactionManager.StartTransaction())
45 {
46 BlockTableRecord model = (BlockTableRecord)tran.GetObject(
47 SymbolUtilityServices.GetBlockModelSpaceId(_dwg.Database),
48 OpenMode.ForWrite);
49
50 while (true)
51 {
52 Point3d centrePt;
53 double radius;
54 if (!CircleCetreRadius(out centrePt, out radius)) break;
55
56 CreateCircle(centrePt, radius, model, tran);
57 tran.TransactionManager.QueueForGraphicsFlush();
58 _dwg.Editor.UpdateScreen();
59
60 count++;
61 }
62
63 tran.Commit();
64 }
65
66 return count;
67 }
68
69 private bool CircleCetreRadius(out Point3d centre, out double rad)
70 {
71 centre=new Point3d();
72 rad=double.MinValue;
73
74 PromptPointOptions pOpt = new PromptPointOptions(
75 "\nPick circle centre:");
76 PromptPointResult pRes = _dwg.Editor.GetPoint(pOpt);
77 if (pRes.Status != PromptStatus.OK) return false;
78 centre = pRes.Value;
79
80 PromptDoubleOptions dOpt = new PromptDoubleOptions(
81 "\nEnter circle radius");
82 dOpt.AllowNegative = false;
83 dOpt.AllowNone = false;
84 dOpt.AllowZero = false;
85 PromptDoubleResult dRes = _dwg.Editor.GetDouble(dOpt);
86 if (dRes.Status != PromptStatus.OK) return false;
87 rad = dRes.Value;
88
89 return true;
90 }
91
92 private void CreateCircle(
93 Point3d centrePt, double radius,
94 BlockTableRecord model, Transaction tran)
95 {
96 Circle c = new Circle();
97 c.Center = centrePt;
98 c.Radius = radius;
99 c.SetDatabaseDefaults(_dwg.Database);
100
101 model.AppendEntity(c);
102 tran.AddNewlyCreatedDBObject(c, true);
103 }
104 }
105 }
Then, here is the CommandClass that defines the "undoable" custom command MyCmd:
1 using Autodesk.AutoCAD.ApplicationServices;
2 using Autodesk.AutoCAD.EditorInput;
3 using Autodesk.AutoCAD.Runtime;
4
5 [assembly: CommandClass(typeof(UndoableCommand.MyCommands))]
6
7 namespace UndoableCommand
8 {
9 public class MyCommands
10 {
11 private static bool _validCall = false;
12
13 [CommandMethod("MyCmd")]
14 public static void RunMyCommand()
15 {
16 Document dwg = Application.DocumentManager.MdiActiveDocument;
17 Editor ed = dwg.Editor;
18 dwg.CommandEnded += dwg_CommandEnded;
19
20 //Set UNDO mark
21 dwg.SendStringToExecute("UNDO M ", true, false, false);
22 }
23
24 static void dwg_CommandEnded(object sender, CommandEventArgs e)
25 {
26 if (e.GlobalCommandName.ToUpper() == "MYCMD")
27 {
28 //Run my real command
29 Document dwg = Application.DocumentManager.MdiActiveDocument;
30 Editor ed = dwg.Editor;
31
32 ed.WriteMessage("\nUndo mark has been set.");
33
34 //Run the command that does real work with its result
35 //might be undone
36 _validCall = true;
37 dwg.SendStringToExecute("MyRealCmd ", true, false, false);
38 }
39 }
40
41 [CommandMethod("MyRealCmd")]
42 public static void DoWork()
43 {
44 Document dwg = Application.DocumentManager.MdiActiveDocument;
45 Editor ed = dwg.Editor;
46
47 //Prevent this command being used without UNDO mark is set
48 if (!_validCall)
49 {
50 ed.WriteMessage(
51 "\ncommand \"MyRealCmd\" cannot be executed directly. " +
52 "Use command \"MyCmd\" instead.");
53 return;
54 }
55
56 //Do real work here and with return indicating
57 //whether UNDO is required
58 DoWorkTool work = new DoWorkTool(dwg);
59 bool keep = work.CreateSomething();
60
61 //Remove the commandEnded handler
62 dwg.CommandEnded -= dwg_CommandEnded;
63 _validCall = false;
64
65 if (!keep)
66 {
67 //Undo if it is needed
68 dwg.SendStringToExecute("UNDO B ", true, false, false);
69 }
70 }
71 }
72 }
Go to this video clip to see the undoable command in action.