I wrote an article on this topic a few years ago, where 2 approaches were described: using singleton form instance, or multiple form instances. If the data used in the UI is document specific, using singleton form would require UI refreshing, which in turn may require lengthy data re-collecting/re-loading. In that article, the multiple-form approach relies on non-static CommandMethod call to instantiate the CommandClass per document, so that the UI can be member of the CommandClass and be created somehow automatically.
However, to make AutoCAD plug-in code cleaner, more modular, one may not want to put document specific data models and/or UI components directly in a CommandClass. Also, per-document CommandClass is only instantiated when a non-static CommandMethod is called. So, what if you want the per-document data available before a non-static CommandMethod is called against each document?
In this article, I demonstrate how to take advantage of PerDocumentClassAttribute class, introduced by AutoCAD 2015, to show modeless form easily with document specific data.
Firstly, Kean Wamsley had posted 2 articles about "PerDocumentClassAttribute" here and here. One may want to read them first before following me further here.
Here is a PerDocumentClass that holds some data for each document opened in AutoCAD and holds an UI (a modeless form) to allow user to view/edit the per-document data:
using System; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.Runtime; using CadApp = Autodesk.AutoCAD.ApplicationServices.Application; [assembly: PerDocumentClass(typeof(PerDocModelessForm.MyDocData))] namespace PerDocModelessForm { public class MyDocData : IDisposable { private const string USERDATA_KEY = "My_PER_DOC_DATA"; public string UserName { private set; get; } public string DrawingName { private set; get; } public string DwgNote1 { internal set; get; } public string DwgNote2 { internal set; get; } public MyDocDataView DataView { private set; get; } public bool WasShown { internal set; get; } private IntPtr _dwgPointer = IntPtr.Zero; private bool _saved = false; private Document _dwg = null; public MyDocData(Document dwg) { _dwg = dwg; DwgNote1 = ""; DwgNote2 = ""; UserName = CadApp.GetSystemVariable("LOGINNAME").ToString(); DrawingName = dwg.Name; DataView = new MyDocDataView(this); _dwgPointer = dwg.UnmanagedObject; _saved = false; dwg.UserData.Add(USERDATA_KEY, this); // Update DrawingName property if file is saved to a new file name dwg.Database.SaveComplete += (o, e) => { if (e.FileName.ToUpper()!=DrawingName.ToUpper()) { DrawingName = e.FileName; } }; // Show the view when document is activated, if // the view was shown when the document was activate prevuoisly CadApp.DocumentManager.DocumentActivated += (o, e) => { if (e.Document.UnmanagedObject == _dwgPointer) { if (WasShown) { DataView.Visible = true; } } }; // Hide the data view if the document is deactivated CadApp.DocumentManager.DocumentToBeDeactivated += (o, e) => { if (e.Document.UnmanagedObject == _dwgPointer) { if (DataView.Visible) { WasShown = true; DataView.Visible = false; } } }; // Save myDocData to somewhere CadApp.DocumentManager.DocumentToBeDestroyed += (o, e) => { var doc = e.Document; if (doc.UnmanagedObject == _dwg.UnmanagedObject) { var data = doc.UserData[USERDATA_KEY] as MyDocData; SaveDwgNotes(data); } }; } public static MyDocData Create(Document dwg) { return new MyDocData(dwg); } public static void ShowDocData(Document dwg) { var data = dwg.UserData[USERDATA_KEY] as MyDocData; CadApp.ShowModelessDialog(CadApp.MainWindow.Handle, data.DataView, true); } public void Dispose() { if (DataView!=null) { DataView.Dispose(); } } #region private methods private void SaveDwgNotes(MyDocData data) { if (!data._saved) { System.Windows.Forms.MessageBox.Show( "Saving drawing note data to somewhere...", "Per Document Data in \"" + System.IO.Path.GetFileName(_dwg.Name) + "\"...", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information); //To DO: save drawing note data to somewhere data._saved = true; } } #endregion } }
Here is the command that allows user to view/edit data for each document:
using Autodesk.AutoCAD.Runtime; using CadApp = Autodesk.AutoCAD.ApplicationServices.Application; [assembly: CommandClass(typeof(PerDocModelessForm.MyCommands))] namespace PerDocModelessForm { public class MyCommands { [CommandMethod("ShowData")] public static void RunCommandA() { var dwg = CadApp.DocumentManager.MdiActiveDocument; MyDocData.ShowDocData(dwg); } } }
This video clip shows the code in action
Some Thought
PerDocumentClass greatly simplifies the process of creating and cleaning "per-document" data in AutoCAD - a multiple document application: the class is instantiated to each existing open document when the .NET assembly, where the PerDocumentClass is defined, is loaded and to each newly opened document.
Using PerDocumentClass would make data segregation in the principle of "separate-concerns" much easier. My code here shows how multiple modeless forms are used for documents in AutoCAD with each only being associated to specific document.
In this example of using modeless form, I use Windows Form. Using WPF window would be the same. Using PaletteSet for each document is doable in the same way, but might not be desirable, because PaletteSet is designed to run at application level, as an UI (pallete) container, especially if a GUID is used to instantiate a PaletteSet, and AutoCAD remembers a PaletteSet based on its GUID per-application.
Download source code here.