With AutoCAD .NET API, one can use either Windows Form or WPF Window as dialog box. This discussion is limited to Windows Form, because it is still used far more often than WPF window.
System.Windows.Forms.Form class has 2 methods to show a form: ShowDialog() and Show(). The former shows a form as modal form and the latter shows a form as modeless form. Depending on which method is used to show a form, the form's closing behaviour is different (either by calling Form.Close(), or by clicking "x" button on the form), which I have seen is misunderstood quite often.
The difference is:
- For modeless form, Form.Close() (or clicking the "x" button on the form. I'll not repeat this hereafter) also calls the Dispose() method;
- For modal form, Form.Close() only hide the form, which is basically equivalent to setting the form's Visible property to False. The modal form can also be "closed" by calling Form.Hide(), by setting Form.DialogResult. That is, the modal form cannot be really closed unless Form.Dispose() is called explicitly.
In AutoCAD .NET API, one should use ShowModalDialog()/ShowModelessDialog() instead of Form.ShowDialog/Form.Show() to display modal/modeless dialog boxes. These 2 methods have the same behaviours in terms of how form/dialog box is closed.
When using modeless form in AutoCAD, one thing to be careful is how the form is opened and closed/hidden. The fact while the form is open AutoCAD's active document can be changed by user interaction makes things a bit complicated, if the information showing on the form is document specific. Here I am going to show some code dealing with modeless form in 2 different ways: creating a singleton form in entire AutoCAD session; or creating multiple forms (per document) and showing them accordingly.
Here is the Visual Studio 2012 solution, which includes 3 projects:
Here is the Visual Studio 2012 solution, which includes 3 projects:
Project "DwgDataView" contains a Windows UserControl and Windows Form that can be used in AutoCAD addins:
This project also has a data model class and a AutoCAD utility class to get and hold drawing specific information.
The code follows.
Class DrawingData:
Class DrawingDataUtil:
Code of UserControl DrawingDataView:
Code of Form frmDrawing:
In the project SigletonModelessDialog, the modeless form will not be disposed (by calling Form.Dispose() or Form.Close()) once it is instantiated. User's interaction with the form can hide the form.
In this scenario, when the modeless form deals with per document data, we need to make sure the modeless form is tied to correct document, usually the active document, which can change when user works with AutoCAD while the modeless form is visible.
Here is code of project SingletonModelessDialog:
This project also has a data model class and a AutoCAD utility class to get and hold drawing specific information.
The code follows.
Class DrawingData:
1 namespace DwgDataView
2 {
3 public class DrawingData
4 {
5 public string CurrentDocName { set; get; }
6 public string CurrentLayer { set; get; }
7 public double CurrentDimScale { set; get; }
8 }
9 }
Class DrawingDataUtil:
1 using System;
2 using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application;
3
4 namespace DwgDataView
5 {
6 public class DrawingDataUtil
7 {
8 public static DrawingData GetCurrentDrawingData()
9 {
10 return new DrawingData
11 {
12 CurrentDocName=AcadApp.DocumentManager.MdiActiveDocument.Name,
13 CurrentLayer=AcadApp.GetSystemVariable("CLAYER").ToString(),
14 CurrentDimScale=Convert.ToDouble(AcadApp.GetSystemVariable("DIMSCALE"))
15 };
16 }
17 }
18 }
Code of UserControl DrawingDataView:
1 using System.Windows.Forms;
2
3 namespace DwgDataView
4 {
5 public partial class DrawingDataView : UserControl
6 {
7 public DrawingDataView()
8 {
9 InitializeComponent();
10 }
11
12 public void SetDataView(DrawingData data)
13 {
14 txtDrawingName.Text = data.CurrentDocName;
15 txtLayer.Text = data.CurrentLayer;
16 txtDimScale.Text = data.CurrentDimScale.ToString();
17 }
18
19 public void ClearDataView()
20 {
21 txtDimScale.Text = "";
22 txtDrawingName.Text = "";
23 txtLayer.Text = "";
24 }
25 }
26 }
Code of Form frmDrawing:
1 using System;
2 using System.Windows.Forms;
3
4 namespace DwgDataView
5 {
6 public partial class frmDrawing : Form
7 {
8 //This property is only used for showing multiple modeless forms
9 public bool ShowMe { set; get; }
10
11 //This property is used to make the modeless form close differently:
12 //hidden as closed, or disposed as closed
13 public bool HiddenAsClosed { set; get; }
14
15 public frmDrawing()
16 {
17 InitializeComponent();
18
19 ShowMe = false;
20 HiddenAsClosed = true;
21 }
22
23 public void SetDataView(DrawingData data)
24 {
25 ctlDataView.SetDataView(data);
26 }
27
28 public void ClearDataView()
29 {
30 ctlDataView.ClearDataView();
31 }
32
33 private void btnClose_Click(object sender, EventArgs e)
34 {
35 if (HiddenAsClosed)
36 {
37 ShowMe = false;
38 this.Hide();
39 }
40 else
41 {
42 this.Close();
43 }
44 }
45
46 private void frmDrawing_FormClosing(object sender, FormClosingEventArgs e)
47 {
48 if (HiddenAsClosed)
49 {
50 e.Cancel = true;
51 ShowMe = false;
52 this.Hide();
53 }
54 }
55 }
56 }
In the project SigletonModelessDialog, the modeless form will not be disposed (by calling Form.Dispose() or Form.Close()) once it is instantiated. User's interaction with the form can hide the form.
In this scenario, when the modeless form deals with per document data, we need to make sure the modeless form is tied to correct document, usually the active document, which can change when user works with AutoCAD while the modeless form is visible.
Here is code of project SingletonModelessDialog:
1 using Autodesk.AutoCAD.ApplicationServices;
2 using Autodesk.AutoCAD.EditorInput;
3 using Autodesk.AutoCAD.Runtime;
4 using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application;
5
6 using DwgDataView;
7
8 [assembly: CommandClass(typeof(SingletonModelessDialog.MyCommands))]
9 [assembly: ExtensionApplication(typeof(SingletonModelessDialog.MyCommands))]
10
11 namespace SingletonModelessDialog
12 {
13 public class MyCommands : IExtensionApplication
14 {
15 private static frmDrawing _frm = null;
16
17 public void Initialize()
18 {
19 DocumentCollection dwgCol = AcadApp.DocumentManager;
20 dwgCol.DocumentBecameCurrent += dwgCol_DocumentBecameCurrent;
21 Document dwg = AcadApp.DocumentManager.MdiActiveDocument;
22 Editor ed = dwg.Editor;
23
24 try
25 {
26 ed.WriteMessage("\nInitializing {0}...", this.GetType().Name);
27
28 ed.WriteMessage("comleted\n");
29 }
30 catch (System.Exception ex)
31 {
32 ed.WriteMessage("failed:\n{0}", ex.ToString());
33 }
34 }
35
36 public void Terminate()
37 {
38 if (_frm != null)
39 {
40 if (!_frm.IsDisposed) _frm.Dispose();
41 }
42 }
43
44 private void dwgCol_DocumentBecameCurrent(
45 object sender, DocumentCollectionEventArgs e)
46 {
47 if (_frm == null) return;
48
49 if (_frm.Visible)
50 {
51 //Reload drawing data to the form, when the form turns from
52 //invisible to visible. To make the code more efficient,
53 //we also check if the drawing information on the form points
54 //to the same drawing that became current, and only refresh
55 //information on the form when drawing is different
56 DrawingData data =
57 DwgDataView.DrawingDataUtil.GetCurrentDrawingData();
58 _frm.SetDataView(data);
59 }
60 }
61
62 [CommandMethod("DwgDlg", CommandFlags.Session)]
63 public static void RunMyCommand()
64 {
65 if (_frm == null)
66 {
67 _frm = new frmDrawing();
68 }
69
70 DrawingData data =
71 DwgDataView.DrawingDataUtil.GetCurrentDrawingData();
72 _frm.SetDataView(data);
73 AcadApp.ShowModelessDialog(_frm);
74 }
75 }
76 }
As the code shows. using singleton modeless dialog box, the form/dialog box is only instantiated once, and it stays in memory until AutoCAD session ends. When user closes the form, it becomes invisible, instead of being disposed.
See this video clip showing how the singleton modeless dialog behaves.
Then in the project MultiModelessDialog, The intention is to create a modeless form for each document when needed. The modeless form is also never disposed once created, only being set to visible/invisible accordingly.
Here is the code for project MultiModelessDialog:
Pay attention to the use of the non-static CommandMethod in this project, which ensures that the class MultiModelessDialog.MyCommand is instantiated for each document when the CommandMethod is called, thus one modeless form of frmDrawing type can be instantiated. For more information on "static" or "non-static" CommandMethod, see one of my old article here.
See this video clip showing how the multiple modeless dialogs behave.
In both cases of singleton form and multiple forms projects here the form or forms will never be disposed once opened. It simply changes its visibility as needed.
In the singleton case, the form simply be destroyed when AutoCAD session is closed. We can choose to add some code in IExtenstionApplication.Terminate() implementation to dispose the modeless form properly, if there is some complicated resources being used by the form and should be released nicely.
In the case of multiple modeless forms, the modeless form corresponding to each document is disposed when the document is to be closed.
Well, I could have chosen to actually dispose/close the modeless form when user clicks "Close" button or clicks the "x" on the form, instead of hiding the form. This may actually save some memory usage, if the form size is big (has a lots of controls, showing a lots of data, showing some images...). Then, this may results in some performance penalty: each time the form is to show, it has to be re-established from scratch, even user just simply switch the active document. Modern AutoCAD capable computer usually comes with huge amount of memory, and keeping a few moderately sized forms in memory with a few extra MB would not be a much of issue, in my opinion.
See this video clip showing how the singleton modeless dialog behaves.
Then in the project MultiModelessDialog, The intention is to create a modeless form for each document when needed. The modeless form is also never disposed once created, only being set to visible/invisible accordingly.
Here is the code for project MultiModelessDialog:
1 using System.Collections.Generic;
2 using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application;
3 using Autodesk.AutoCAD.ApplicationServices;
4 using Autodesk.AutoCAD.EditorInput;
5 using Autodesk.AutoCAD.Runtime;
6 using DwgDataView;
7
8 [assembly: CommandClass(typeof(MultiModelessDialog.MyCommands))]
9 [assembly: ExtensionApplication(typeof(MultiModelessDialog.MyCommands))]
10
11 namespace MultiModelessDialog
12 {
13 public class MyCommands : IExtensionApplication
14 {
15 //Static member
16 private static Dictionary<Document, frmDrawing> _modelessForms = null;
17
18 //non-static member, will be created with each document
19 private frmDrawing _frm = null;
20
21 public void Initialize()
22 {
23 DocumentCollection dwgCol = AcadApp.DocumentManager;
24 dwgCol.DocumentBecameCurrent += dwgCol_DocumentBecameCurrent;
25 dwgCol.DocumentToBeDestroyed += dwgCol_DocumentToBeDestroyed;
26
27 _modelessForms = new Dictionary<Document, frmDrawing>();
28
29 Document dwg = AcadApp.DocumentManager.MdiActiveDocument;
30 Editor ed = dwg.Editor;
31
32 try
33 {
34 ed.WriteMessage("\nInitializing {0}...", this.GetType().Name);
35
36 ed.WriteMessage("comleted\n");
37 }
38 catch (System.Exception ex)
39 {
40 ed.WriteMessage("failed:\n{0}", ex.ToString());
41 }
42 }
43
44 private void dwgCol_DocumentToBeDestroyed(
45 object sender, DocumentCollectionEventArgs e)
46 {
47 //When a document is closed (to be destroyed by AutoCAD),
48 //its corresponding form, if exists, will be disposed, and
49 //the form's reference is removed from the static Dictionary
50 if (_modelessForms.ContainsKey(e.Document))
51 {
52 _modelessForms[e.Document].Dispose();
53 _modelessForms.Remove(e.Document);
54 }
55 }
56
57 private void dwgCol_DocumentBecameCurrent(
58 object sender, DocumentCollectionEventArgs e)
59 {
60 //Loop through the static Dictionary clooection to see whether
61 //the current document has a corresponding form instantiated or not.
62 //If the form exists and was not closed by user previously, then
63 //bring it to visible. In the meantime, forms corresponding to other
64 //documents are closed (set to invisible).
65 foreach (KeyValuePair<Document, frmDrawing> item in _modelessForms)
66 {
67 if (e.Document == item.Key)
68 {
69 if (item.Value.ShowMe)
70 {
71 item.Value.Visible = true;
72
73 //If necessary, we could refresh the data showing on
74 //the form
75 DrawingData data =
76 DwgDataView.DrawingDataUtil.GetCurrentDrawingData();
77 item.Value.SetDataView(data);
78 }
79 else
80 item.Value.Visible = false;
81 }
82 else
83 {
84 item.Value.Visible = false;
85 }
86 }
87 }
88
89 public void Terminate()
90 {
91
92 }
93
94
95 //non-static command method, when executed, each document will
96 //instantiate a class "MyCommand" with a private member of frmDrawing
97 [CommandMethod("DwgDlg")]
98 public void RunMyCommand()
99 {
100 if (_frm == null)
101 {
102 DrawingData data =
103 DwgDataView.DrawingDataUtil.GetCurrentDrawingData();
104 _frm = new frmDrawing();
105 _frm.SetDataView(data);
106
107 _modelessForms.Add(
108 AcadApp.DocumentManager.MdiActiveDocument, _frm);
109 }
110
111 _frm.ShowMe = true;
112 AcadApp.ShowModelessDialog(_frm);
113 }
114 }
115 }
Pay attention to the use of the non-static CommandMethod in this project, which ensures that the class MultiModelessDialog.MyCommand is instantiated for each document when the CommandMethod is called, thus one modeless form of frmDrawing type can be instantiated. For more information on "static" or "non-static" CommandMethod, see one of my old article here.
See this video clip showing how the multiple modeless dialogs behave.
In both cases of singleton form and multiple forms projects here the form or forms will never be disposed once opened. It simply changes its visibility as needed.
In the singleton case, the form simply be destroyed when AutoCAD session is closed. We can choose to add some code in IExtenstionApplication.Terminate() implementation to dispose the modeless form properly, if there is some complicated resources being used by the form and should be released nicely.
In the case of multiple modeless forms, the modeless form corresponding to each document is disposed when the document is to be closed.
Well, I could have chosen to actually dispose/close the modeless form when user clicks "Close" button or clicks the "x" on the form, instead of hiding the form. This may actually save some memory usage, if the form size is big (has a lots of controls, showing a lots of data, showing some images...). Then, this may results in some performance penalty: each time the form is to show, it has to be re-established from scratch, even user just simply switch the active document. Modern AutoCAD capable computer usually comes with huge amount of memory, and keeping a few moderately sized forms in memory with a few extra MB would not be a much of issue, in my opinion.
2 comments:
Hi Norman,
I'd like to know if you know how to close the modeless form when the Escape key is pressed while current focus is on the drawing?
Thanks.
No, I do not know there is simple/easy way in AutoCAD .NET API to do that. Also, I do not think it makes sense to close modeless window while the focus in on AutoCAD's main window by hitting Esc, at least not how user expects when using Window desktop application/AutoCAD.
However, you might want to try to handle Application.PreTranlateMessage event and use the raw Widows Message to see if use hits the Esc key, and if yes, you could then go ahead to find if the said modeless form is visible, if yes, hide it, or close it. Just a thought.
Post a Comment