Saturday, October 22, 2011

About Terminate() Method of IExtensionApplication

IExtensionApplication interface allows chances for AutoCAD .NET developers to do some initializing task when their custom AutoCAD .NET assemblies are loaded and do some necessary clean-up tasks when AutoCAD is terminating.

In this post, I use some code to show what happen when AutoCAD is closed by user (via clicking "Exit" menu, click "x" on AutoCAD main window, or issue command "quit").

In order to track what happen during the AutoCAD closing process, various event handlers are attached to drawings that were open when AutoCAD starts closing, drawing databases and AutoCAD application itself. Because AutoCAD is closing, AutoCAD test window cannot be used to show message regarding the closing process, I created a TextLogger class that holds all closing messages captured in various event handlers and save them to a text file at the end of Terminate() call.

Here is the code of class TextLogger:

Code Snippet
  1. using System.Text;
  2. using System.IO;
  3.  
  4. namespace IExtensionApp.Terminate
  5. {
  6.     public class TextLogger
  7.     {
  8.         private StringBuilder _textToWrite;
  9.         private string _logFile;
  10.  
  11.         public TextLogger(string fileName)
  12.         {
  13.             _logFile = fileName;
  14.             _textToWrite = new StringBuilder();
  15.         }
  16.  
  17.         public void AddMessageText(string text)
  18.         {
  19.             //Append new text message at the end of StringBuilder
  20.             //with "|" for separating from previous
  21.             _textToWrite.Append(text + "|");
  22.         }
  23.  
  24.         public void SaveToFile()
  25.         {
  26.             //remove "|" at the end
  27.             if (_textToWrite.Length > 1) _textToWrite.Length -= 1;
  28.  
  29.             //Split message into array
  30.             string[] output=_textToWrite.ToString().Split(new char[]{'|'});
  31.  
  32.             //Write to file
  33.             File.WriteAllLines(_logFile,output);
  34.         }
  35.     }
  36. }

Here is the code that runs an AutoCAD:

Code Snippet
  1. using System;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.EditorInput;
  5. using Autodesk.AutoCAD.Runtime;
  6.  
  7. [assembly: CommandClass(typeof(IExtensionApp.Terminate.MyCommands))]
  8. [assembly: ExtensionApplication(typeof(IExtensionApp.Terminate.MyCommands))]
  9.  
  10. namespace IExtensionApp.Terminate
  11. {
  12.     public class MyCommands : IExtensionApplication
  13.     {
  14.         private static TextLogger _logger = null;
  15.         private const string LOG_FILE_NAME=@"E:\Temp\AcadClosingEvents.txt";
  16.         private static DocumentCollection _dwgs = null;
  17.  
  18.         private static Form1 _disposedForm = null;
  19.         private static Form2 _hiddenForm = null;
  20.  
  21.         public void Initialize()
  22.         {
  23.             Document dwg = Application.DocumentManager.MdiActiveDocument;
  24.             Editor ed = dwg.Editor;
  25.  
  26.             try
  27.             {
  28.                 _logger = new TextLogger(LOG_FILE_NAME);
  29.  
  30.                 _dwgs = Application.DocumentManager;
  31.  
  32.                 //Add event handler on DocumentCollection object
  33.                 _dwgs.DocumentToBeDestroyed +=
  34.                     new DocumentCollectionEventHandler(_dwgs_DocumentToBeDestroyed);
  35.                 _dwgs.DocumentDestroyed +=
  36.                     new DocumentDestroyedEventHandler(_dwgs_DocumentDestroyed);
  37.                 _dwgs.DocumentCreated +=
  38.                     new DocumentCollectionEventHandler(_dwgs_DocumentCreated);
  39.  
  40.                 //Add application event handler
  41.                 Application.QuitWillStart +=
  42.                     new EventHandler(Application_QuitWillStart);
  43.                 Application.BeginQuit +=
  44.                     new EventHandler(Application_BeginQuit);
  45.             }
  46.             catch (System.Exception ex)
  47.             {
  48.                 ed.WriteMessage("\nAcad Addin initializing error: {0}\n", ex.Message);
  49.             }
  50.         }
  51.  
  52.         
  53.         void _dwgs_DocumentCreated(object sender, DocumentCollectionEventArgs e)
  54.         {
  55.             Document dwg = e.Document;
  56.  
  57.             //Add event handler on Document to track document closing
  58.             dwg.CloseWillStart += new EventHandler(dwg_CloseWillStart);
  59.             dwg.BeginDocumentClose +=
  60.                 new DocumentBeginCloseEventHandler(dwg_BeginDocumentClose);
  61.  
  62.             //Database events
  63.             Database db = dwg.Database;
  64.  
  65.             //Add event handler to track when database is to be removed from momery
  66.             db.DatabaseToBeDestroyed += new EventHandler(db_DatabaseToBeDestroyed);
  67.         }
  68.  
  69.         void db_DatabaseToBeDestroyed(object sender, EventArgs e)
  70.         {
  71.             Database db = sender as Database;
  72.             string msg =
  73.                 "Database in document " + db.Filename + " is about to be destroyed";
  74.             _logger.AddMessageText(msg);
  75.         }
  76.  
  77.         void dwg_BeginDocumentClose(object sender, DocumentBeginCloseEventArgs e)
  78.         {
  79.             Document d = sender as Document;
  80.             string msg = "Document " + d.Name + " closing begins";
  81.             _logger.AddMessageText(msg);
  82.         }
  83.  
  84.         void dwg_CloseWillStart(object sender, EventArgs e)
  85.         {
  86.             Document d=sender as Document;
  87.             string msg = "Document " + d.Name + " is abount to be closed";
  88.             _logger.AddMessageText(msg);
  89.         }
  90.  
  91.         void Application_QuitWillStart(object sender, EventArgs e)
  92.         {
  93.             string msg = "Autodesk is about to quit";
  94.             _logger.AddMessageText(msg);
  95.         }
  96.  
  97.         void Application_BeginQuit(object sender, EventArgs e)
  98.         {
  99.             string msg = "Quiting Autodesk begins";
  100.             _logger.AddMessageText(msg);
  101.         }
  102.  
  103.         void _dwgs_DocumentToBeDestroyed(object sender, DocumentCollectionEventArgs e)
  104.         {
  105.             string msg = "Document " + e.Document.Name + " is about to be destroyed";
  106.             _logger.AddMessageText(msg);
  107.         }
  108.         void _dwgs_DocumentDestroyed(object sender, DocumentDestroyedEventArgs e)
  109.         {
  110.             string msg = "Document " + e.FileName + " has been destroyed";
  111.             _logger.AddMessageText(msg);
  112.         }
  113.  
  114.         public void Terminate()
  115.         {
  116.             string msg;
  117.  
  118.             //Log the beginning of Terminate() call
  119.             msg = "Terminate() is called";
  120.             _logger.AddMessageText(msg);
  121.  
  122.             //Log if there is still Document open when Terminate() begins
  123.             msg = "Document count is " + _dwgs.Count;
  124.             _logger.AddMessageText(msg);
  125.  
  126.             //Proves that although the form itself is disposed,
  127.             //its reference is still in scope when Terminate() runs
  128.             if (_disposedForm.IsDisposed)
  129.             {
  130.                 msg = "Form1 is disposed, but its reference is still alive";
  131.                 _logger.AddMessageText(msg);
  132.             }
  133.  
  134.             //Proves that the hidden form object is still alive
  135.             //when terminate() executed.
  136.             if (_hiddenForm != null)
  137.             {
  138.                 if (!_hiddenForm.IsDisposed)
  139.                 {
  140.                     msg = "Form2 has not been disposed";
  141.                     _logger.AddMessageText(msg);
  142.  
  143.                     //Calling Dispose() is not necessary in most cases,
  144.                     //after all it will be gone with the hosting AutoCAD
  145.                     //process. However, if the form object holds other
  146.                     //resources outside AutoCAD open, such as files, data
  147.                     //connections, graphic devices...(which could be bad
  148.                     //practice in most cases, if a form holds these kind
  149.                     //of resources open for entire AutoCAD session), then
  150.                     //you may want to call Dispose() here  with the
  151.                     //appropriate overridden Form.Dispose().
  152.                     _hiddenForm.Dispose();
  153.                 }
  154.             }
  155.  
  156.             //Log Terminate() completion
  157.             msg = "Terminate() call is completed";
  158.             _logger.AddMessageText(msg);
  159.  
  160.             //Save logs into log file.
  161.             _logger.SaveToFile();
  162.         }
  163.  
  164.         /// <summary>
  165.         /// Open 2 modeless forms. Close one (i.e. disposed), so that its
  166.         /// reference is still in scope in the Acad session, but the form
  167.         /// object itself is gone (disposed); Close the other
  168.         /// one as invisible (i.e. handling Form_Closing event, and cancel
  169.         /// its closing, set it to invisible instead, so that the form object
  170.         /// and its reference variable stays alive in the Acad session
  171.         /// </summary>
  172.         [CommandMethod("ShowForms")]
  173.         public static void ShowForms()
  174.         {
  175.             if (_disposedForm == null)
  176.             {
  177.                 _disposedForm = new Form1();
  178.             }
  179.             else if (_disposedForm.IsDisposed)
  180.             {
  181.                 _disposedForm = new Form1();
  182.             }
  183.  
  184.             Application.ShowModelessDialog(_disposedForm);
  185.  
  186.             if (_hiddenForm == null)
  187.             {
  188.                 _hiddenForm = new Form2();
  189.             }
  190.  
  191.             Application.ShowModelessDialog(_hiddenForm);
  192.         }
  193.     }
  194. }

I used 2 forms, which are shown in AutoCAD as modeless dialog box. Both forms are very simple with only one button "Close" on the forms. Clicking the button triggers this.Close() method.

In order to make a point in Terminate() process, I let one form to be closed normally (i.e. when call Form.Close() on a modeless form, the form is disposed). I let the other form change to invisible when Form.Close() is called by handling Form_Closing event, like this:

Code Snippet
  1. private void Form2_FormClosing(object sender, FormClosingEventArgs e)
  2.         {
  3.             e.Cancel = true;
  4.             this.Visible = false;
  5.         }

Build the code into an assembly (dll file) with VS. Now it is ready to run the code and see what happen during AutoCAD closing process. I do these steps:

1. Start AutoCAD;
2. Netload the DLL file;
3. Open a few drawing. In my case, I open 3 saved drawing: drawing1, drawing 2 and drawing 3 from a folder;
4. Execute command "ShowForms" to bring up the 2 modeless forms, then cloce them by clicking their "Close" button;
5. Close AutoCAD without closing drawing first by going to big "A" button->Exit AutoCAD, or simply click "x" on main AutoCAD window;
6. Open the log file ("E:\Temp\AcadClosingEvents.txt") in NotePad to see what have been logged.

Here is the content of the log file:

Document C:\Users\norm\Documents\Drawing3.dwg closing begins
Document C:\Users\norm\Documents\Drawing3.dwg closing begins
Document C:\Users\norm\Documents\Drawing3.dwg is about to be destroyed
Database in document C:\Users\norm\Documents\Drawing3.dwg is about to be destroyed
Document C:\Users\norm\Documents\Drawing3.dwg has been destroyed
Document C:\Users\norm\Documents\Drawing2.dwg closing begins
Document C:\Users\norm\Documents\Drawing2.dwg closing begins
Document C:\Users\norm\Documents\Drawing2.dwg is about to be destroyed
Database in document C:\Users\norm\Documents\Drawing2.dwg is about to be destroyed
Document C:\Users\norm\Documents\Drawing2.dwg has been destroyed
Document C:\Users\norm\Documents\Drawing1.dwg closing begins
Document C:\Users\norm\Documents\Drawing1.dwg closing begins
Document C:\Users\norm\Documents\Drawing1.dwg is about to be destroyed
Database in document C:\Users\norm\Documents\Drawing1.dwg is about to be destroyed
Document C:\Users\norm\Documents\Drawing1.dwg has been destroyed
Quiting Autodesk begins
Autodesk is about to quit
Terminate() is called
Document count is 0
Form1 is disposed, but its reference is still alive
Form2 has not been disposed
Terminate() call is completed 
 
Form the messages logged in various event handlers we can see:

1. After AutoCAD receives "quit" command, if there is open drawing, AutoCAD will attempt to close all open drawings, which may trigger prompting message asking user to save changes. The quiting process can be cancelled if user click "Cancel" button in the message box.

2. After all open drawing documents are closed, AutoCAD starts quiting, it is only then the IExtensionApplication.Terminate() is called. That is, one cannot do any document/database related clean-up task in Terminate(), because all document is gone. However, DocumentCollection object is still reachable, but it does not contain document any more.

3. Custom object declared at command class level may or may not exist when Terminate() is executed. In my example, since the 2 forms is called in a static command method, thus, they are instantiated at AutoCAD session level, not per document level. Since they are instantiated in static method, they also has to be declared as "static". About the difference of "static" command method and "non-static" command method, one of my previous post discussed it in more details here.

4. As I commented in the code, what clean-up tasks we need to do in the Terminate() method depends on what our custom AutoCAD Addin does, what external (to AutoCAD) resources the code uses and how they are used. If your code have to do some clean-up in Terminate(), such as opened database connection, opened data file...You may want to look back to the code to answer the question: why your code holds those resource until the end of AutoCAD session? In most cases, it might not be a good practice. So, in real practice, there aren't many cases one has to stuff Terminate() method with a lot of code, if things are done correctly.

Sunday, October 16, 2011

Prompting User With Message Bubble Window

Well, it's been quite some time I have not posted anything. Although I haven't dealt with AutoCAD at my work lately, I always keep my eyes on AutoCAD related topics found online. 

In this post, I focus on showing message bubble window in various ways. There are some information/blog posts we can find online, especially from Kean Walmsley's excellant blog.

It would be nice when AutoCAD runs our custom-developed tools and something happens, user gets prompted in a way not too intrusive. Of course, the "something" is not too critical to continue the tool running, in most cases. Things like update availability checking, background work status, and so on.

I list 3 ways of showing message in bubble window from AutoCAD: using AutoCAD status bar, using AutoCAD InfoCenter and using Window's system tray.

Firstly, since there are mutiple ways to show bubble window from AutoCAD, in order to simplify the calling method from AutoCAD, I created an interface and have each bubble window showing class implement the interface. This way, the custom tool that wants to show bubble window would be effectively separated from the concrete implementation of how bubble window is shown in easy approach, as long as the approach implements the interface. Here is the code of the interface:

Code Snippet
  1. namespace MessageBubbles
  2. {
  3.     public interface IMyMessageBubble
  4.     {
  5.         void ShowBubbleWndow(
  6.                 string title,
  7.                 string message1,
  8.                 string message2="",
  9.                 string linkText="",
  10.                 string linkUrl="",
  11.                 string acadCommand="");
  12.     }
  13. }

You may noticed, this is C# code and I uses optional parameters, which is newly adopted in C# 4.0. Obviously I run my code with AutoCAD2012. If you run earlier AutoCAD version and develop with .NET2.0/3.x, then you can only use optional parameter with VB.NET. If you use C#, you'd have to write a few overloaded ShowBubbleWindow() methods that take different parameters.

As I mentioned here I show three 3 ways of showing bubble window, which is done with 3 classes all implementing the IMyMessageBubble interface: MyAcadStatusBarBubble, MyAcadInfoCenterBubble and MyWindowSystemTrayBubble. Thanks to the interface, I can go ahead to write my custom tool without knowing how the 3 classes actually show their bubble window. Here is my simple custom tool that just shows the bubble window with different command. Optionally, upon the bubble window being clicked/closed, an Acad command can be called and executed. Here is the code:

Code Snippet
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.EditorInput;
  3. using Autodesk.AutoCAD.Runtime;
  4.  
  5. [assembly: CommandClass(typeof(MessageBubbles.MyCommands))]
  6.  
  7. namespace MessageBubbles
  8. {
  9.     public class MyCommands
  10.     {
  11.         private static IMyMessageBubble _bubble = null;
  12.  
  13.         [CommandMethod("Hello")]
  14.         public static void HelloWorld()
  15.         {
  16.             Document dwg = Application.DocumentManager.MdiActiveDocument;
  17.             Editor ed = dwg.Editor;
  18.             ed.WriteMessage("\nHELLO WORLD!\n");
  19.         }
  20.  
  21.         [CommandMethod("InfoCenterBubble")]
  22.         public static void ShowInfoCenterBubble()
  23.         {
  24.             _bubble = new MyAadInfoCenterBubble();
  25.             _bubble.ShowBubbleWndow(
  26.                 title:      "My Message from InfoCenter",
  27.                 message1:   "",
  28.                 message2:   "",
  29.                 linkText:   "Go to Google",
  30.                 linkUrl:    "http://www.google.com",
  31.                 acadCommand: "Hello ");
  32.         }
  33.  
  34.         [CommandMethod("StatusBarBubble")]
  35.         public static void ShowTrayItemBubble()
  36.         {
  37.             _bubble = new MyAcadStatusBarBubble();
  38.             _bubble.ShowBubbleWndow(
  39.                 title:      "My Message from Status Bar",
  40.                 message1:   "This message is important",
  41.                 message2:   "Your AutoCAD has been instructed to show " +
  42.                             "bubble window at its status bar. " +
  43.                             "Go to Google for more details",
  44.                 linkText:   "Go to Google",
  45.                 linkUrl:    "http://www.google.com",
  46.                 acadCommand:"Hello ");
  47.         }
  48.  
  49.         [CommandMethod("SystemTrayBubble")]
  50.         public static void ShowSystemTrayBubble()
  51.         {
  52.             _bubble = new MyWindowSystemTrayBubble();
  53.             _bubble.ShowBubbleWndow(
  54.                 title:      "My Message from Window System Tray",
  55.                 message1:   "This is a message from Google website.",
  56.                 message2:   "",
  57.                 linkText:   "Google",
  58.                 linkUrl:    "http://www.google.com",
  59.                 acadCommand: "Hello ");
  60.         }
  61.     }
  62. }

You must notice there is command "Hello". This command is there to demonstrate that when the something happens to the bubble window (clicked, closed), we can make a call to an AutoCAD command, if needed. Read on.

Here are 3 classes that implements IMyMessageBubble interface.

1. Using AutoCAD Status Bar

The code of the class MyAcadStatusBarBubble is here:

Code Snippet
  1. using System;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.Windows;
  4.  
  5. namespace MessageBubbles
  6. {
  7.     public class MyAcadStatusBarBubble : IMyMessageBubble
  8.     {
  9.         TrayItem _item;
  10.         TrayItemBubbleWindow _bubble;
  11.         string _commandOnLinkClick = "";
  12.  
  13.         public MyAcadStatusBarBubble()
  14.         {
  15.             _item = new TrayItem();
  16.             _item.Icon = System.Drawing.SystemIcons.Exclamation;
  17.             _item.ToolTipText = "My bubble window item";
  18.         }
  19.  
  20.         public void ShowBubbleWndow(
  21.                         string title,
  22.                         string message1,
  23.                         string message2="",
  24.                         string linkText="",
  25.                         string linkUrl="",
  26.                         string acadCommand="")
  27.         {
  28.             _commandOnLinkClick = acadCommand;
  29.  
  30.             //Add tray item to status bar
  31.             Application.StatusBar.TrayItems.Add(_item);
  32.  
  33.             //Create TrayItemBubbleWindow object
  34.             _bubble = new TrayItemBubbleWindow();
  35.             _bubble.IconType = IconType.Information;
  36.  
  37.             _bubble.Title = title;
  38.             _bubble.Text = message1;
  39.  
  40.             if (!String.IsNullOrEmpty(message2)) _bubble.Text2 = message2;
  41.             if (!String.IsNullOrEmpty(linkText) &&
  42.                 !String.IsNullOrEmpty(linkUrl))
  43.             {
  44.                 _bubble.HyperText = linkText;
  45.                 _bubble.HyperLink = linkUrl;
  46.             }
  47.  
  48.             _bubble.Closed +=
  49.                 new TrayItemBubbleWindowClosedEventHandler(_bubble_Closed);
  50.  
  51.             //Show bubble window
  52.             _item.ShowBubbleWindow(_bubble);
  53.             Application.StatusBar.Update();
  54.         }
  55.  
  56.         private void _bubble_Closed(object sender,
  57.                     TrayItemBubbleWindowClosedEventArgs e)
  58.         {
  59.             Document dwg = Application.DocumentManager.MdiActiveDocument;
  60.  
  61.             if (e.CloseReason ==
  62.                 TrayItemBubbleWindowCloseReason.HyperlinkClicked)
  63.             {
  64.                 if (!String.IsNullOrEmpty(_commandOnLinkClick))
  65.                 {
  66.                     dwg.SendStringToExecute(
  67.                         _commandOnLinkClick, true, false, true);
  68.                 }
  69.             }
  70.             else
  71.             {
  72.                 dwg.Editor.WriteMessage(
  73.                        "\nStatus Bar bubble window closed due to {0}\n",
  74.                        e.CloseReason.ToString());
  75.             }
  76.  
  77.             Application.StatusBar.TrayItems.Remove(_item);
  78.  
  79.             _bubble.Dispose();
  80.             _bubble = null;
  81.         }
  82.     }
  83. }

Pay attention to the line of code in the Constructor:

_item.Icon = System.Drawing.SystemIcons.Exclamation;

TrayItem's icon must be set to a valid System.Drawing.Icon object, or the TrayItemBubbleWindow will not be shown. That is, when TrayItem.ShowBubbleWindow() is called, the TrayItemBubbleWindow will close immediately with TrayItemBubbleWindowCloseReason.FailedToCreate.

From the code we can see, although TrayItemBubbleWindow is an object separated from TrayItem, but it does not have a method to show itself. It relies on an TrayItem to be shown. Thus, the need to create a TrayItem and added it to AutoCAD status bar. In my case, the TrayItem is only there for showing the bubble window, so, I added it to AutoCAD status bar right before the bubble window is shown and remove it from AutoCAD status bar after the bubble window is closed. If you have other use of the TrayItem, you could add it to AutoCAD status bar in the class' Constructor and not remove it at all.

It is also possible to just loop through AutoCAD.StatusBar.TrayItems collection and find an existing TrayItem and "borrow" it to show your own bubble window.

2. Using AutoCAD InfoCenter

Kean Walmsley posted example of this here. But I'll go ahead with my similar code in class MyAcadInfoCenterBubble that implements IMyMessageBubble anyway:

Code Snippet
  1. using System;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.Internal.InfoCenter;
  4. using Autodesk.AutoCAD.AcInfoCenterConn;
  5.  
  6. namespace MessageBubbles
  7. {
  8.     public class MyAadInfoCenterBubble: IMyMessageBubble
  9.     {
  10.         private string _url="";
  11.         private string _command = "";
  12.  
  13.         public void ShowBubbleWndow(
  14.                         string title,
  15.                         string message1,
  16.                         string message2 = "",
  17.                         string linkText = "",
  18.                         string linkUrl = "",
  19.                         string acadCommand = "")
  20.         {
  21.             if (!String.IsNullOrEmpty(linkUrl))
  22.             {
  23.                 _url = linkUrl;
  24.             }
  25.  
  26.             _command = acadCommand;
  27.  
  28.             InfoCenterManager icm = new InfoCenterManager();
  29.  
  30.             ResultItem ri = new ResultItem();
  31.             ri.Category = title;
  32.             ri.Title = linkText;
  33.             ri.Uri =new System.Uri(linkUrl);
  34.             ri.IsFavorite = true;
  35.             ri.IsNew = true;
  36.             ri.ResultClicked +=
  37.                 new EventHandler&lt;ResultClickEventArgs>(ri_ResultClicked);
  38.  
  39.             icm.PaletteManager.ShowBalloon(ri);
  40.  
  41.         }
  42.  
  43.         void ri_ResultClicked(object sender, ResultClickEventArgs e)
  44.         {
  45.             Document dwg = Application.DocumentManager.MdiActiveDocument;
  46.             dwg.Editor.WriteMessage("\nInfo center is clicked\n");
  47.             if (_url.Length>0)
  48.             {
  49.                 System.Diagnostics.ProcessStartInfo proc =
  50.                     new System.Diagnostics.ProcessStartInfo();
  51.                 proc.FileName = _url;
  52.                 System.Diagnostics.Process.Start(proc);
  53.             }
  54.  
  55.             if (!String.IsNullOrEmpty(_command))
  56.             {
  57.                 dwg.SendStringToExecute(_command, true, false, true);
  58.             }
  59.         }
  60.     }
  61. }

You noticed I used System.Diagnostics.Process to start browser with given link. For some reason, although the ResultItem has been supplied a valid System.Uri object, and a link shows correctly in the bubble window, however, nothing happens as expected when I clicked it. In the case of AutoCAD status bar bubble window, the the link is set, there is no need to do anything. I am not sure I am doing thing correctly here by having to use System.Diagnostics.Process to make the link in the InfoCenter bubble window to work. I have not tried this code in other version of AutoCAD other than 2012.

As you can see, with InfoCenter bubble window, there is fewer options for us to show/format the message than TrayItemBubbleWindow.

3. Using Windows System Tray Nitification Icon

In one of my previous posts, I used System.Windows.Forms.NotifyIcon to show status of async process status. Here is the version of implementing IMyMessageBubble:

Code Snippet
  1. using System;
  2. using System.Windows.Forms;
  3. using Autodesk.AutoCAD.ApplicationServices;
  4.  
  5. namespace MessageBubbles
  6. {
  7.     public class MyWindowSystemTrayBubble : IMyMessageBubble
  8.     {
  9.         private NotifyIcon _bubble = null;
  10.         private string _url = "";
  11.         private string _command;
  12.  
  13.         public void ShowBubbleWndow(
  14.                     string title,
  15.                     string message1,
  16.                     string message2 = "",
  17.                     string linkText = "",
  18.                     string linkUrl = "",
  19.                     string acadCommand = "")
  20.         {
  21.             _url = linkUrl;
  22.             _command = acadCommand;
  23.  
  24.             _bubble = new NotifyIcon();
  25.             _bubble.Icon = System.Drawing.SystemIcons.Exclamation;
  26.             _bubble.BalloonTipIcon = ToolTipIcon.Info;
  27.  
  28.             _bubble.BalloonTipTitle = title;
  29.             _bubble.BalloonTipText =
  30.                 message1 + "\n\nClick to go to " + linkText;
  31.  
  32.             _bubble.BalloonTipClicked +=
  33.                 new EventHandler(_bubble_BalloonTipClicked);
  34.             _bubble.BalloonTipClosed +=
  35.                 new EventHandler(_bubble_BalloonTipClosed);
  36.  
  37.             _bubble.Visible = true;
  38.             _bubble.ShowBalloonTip(5000);
  39.         }
  40.  
  41.         private void _bubble_BalloonTipClosed(object sender, EventArgs e)
  42.         {
  43.             if (_bubble != null)
  44.             {
  45.                 _bubble.Dispose();
  46.                 _bubble = null;
  47.             }
  48.         }
  49.  
  50.         private void _bubble_BalloonTipClicked(object sender, EventArgs e)
  51.         {
  52.             if (_url != null)
  53.             {
  54.                 System.Diagnostics.ProcessStartInfo proc =
  55.                     new System.Diagnostics.ProcessStartInfo();
  56.                 proc.FileName = _url;
  57.                 System.Diagnostics.Process.Start(proc);
  58.             }
  59.  
  60.             if (!String.IsNullOrEmpty(_command))
  61.             {
  62.                 Document dwg = Autodesk.AutoCAD.ApplicationServices.
  63.                     Application.DocumentManager.MdiActiveDocument;
  64.                     
  65.                 dwg.SendStringToExecute(
  66.                         _command, true, false, true);
  67.             }
  68.  
  69.             _bubble.Dispose();
  70.             _bubble = null;
  71.         }
  72.     }
  73. }

It is the same as the case of AutoCAD status bar's TrayItem that NotifyIcon's Icon property must be set to a valid System.Drawing.Icon object (no wonder the class is called NotifyIcon). Or it will not be able to show a bubble window (BalloonTip).

Among these 3 ways of showing message bubble window, I recommend to use either AutoCAD status bar's TrayItem, or use Windows system tray NotifyIcon. Both of them make it easy to handle user interaction to the bubble window, such as how the bubble window is closed (TrayItemBubbleWindow), or single/double clicks on the bubble window (Window system NotifyIcon), or mouse click (left/right button) on the icon itself of the both.

This video clip shows how the bubble window pops up in each of the 3 ways discussed in this post.

Followers

About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.