Click here to Skip to main content
15,881,852 members
Articles / Desktop Programming / MFC

Balloon Help as a non-modal replacement for MessageBox()

Rate me:
Please Sign up or sign in to vote.
4.98/5 (62 votes)
7 Aug 2002CPOL13 min read 1M   12.3K   228  
Although sometimes useful, message boxes used to display information are often just annoying. This article describes a non-modal replacement.
<!-- HTML for article "Balloon Help as a non-modal replacement for MessageBox()" by Shog<font color=green><sup>9</sup></font>
     URL: http://www.codeproject.com/miscctrl/balloonhelp.asp

     Article content copyright Shog<font color=green><sup>9</sup></font>
     All formatting, additions and alterations copyright CodeProject
-->

<!----------------------------- Article Starts ----------------------------->


<ul class=download>
<li><a href='BalloonHelp/BalloonHelp_src.zip'>Download source files - 13 Kb</a></li>
<li><a href='BalloonHelp/BalloonHelp_demo.zip'>Download demo project and .exe - 57 Kb</a></li>
<li><a href='BalloonHelp/BH-ATL.zip'>Download Fil Mackay's ATL port - 40Kb</a></li>
</ul>

  
      <P align="center">
         <img src="BalloonHelp/Balloons.jpg" width="498" height="512" alt="Balloons"><br>
         <i>CBalloonHelp test app, running under Windows XP.</i></P>
      <P>
         <a href="#Intro">Introduction</a><br>
         <a href="#Using">Using CBalloonHelp</a><br>
         <a href="#API">API</a><br>
         &nbsp;&nbsp;&nbsp;<a href="#API Important">Important stuff</a><br>
         &nbsp;&nbsp;&nbsp;<a href="#API SetAnchorPoint">SetAnchorPoint()</a><br>
         &nbsp;&nbsp;&nbsp;<a href="#API SetBackgroundColor">SetBackgroundColor()</a><br>
         &nbsp;&nbsp;&nbsp;<a href="#API SetForegroundColor">SetForegroundColor()</a><br>
         &nbsp;&nbsp;&nbsp;<a href="#API SetTitle">SetTitle()</a><br>
         &nbsp;&nbsp;&nbsp;<a href="#API SetContent">SetContent()</a><br>
         &nbsp;&nbsp;&nbsp;<a href="#API SetTitleFont">SetTitleFont()</a><br>
         &nbsp;&nbsp;&nbsp;<a href="#API SetContentFont">SetContentFont()</a><br>
         &nbsp;&nbsp;&nbsp;<a href="#API SetURL">SetURL()</a><br>
         &nbsp;&nbsp;&nbsp;<a href="#API SetIcon">SetIcon()</a><br>
         &nbsp;&nbsp;&nbsp;<a href="#API LaunchBalloon">LaunchBalloon()</a><br>
         <a href="#Features">Features</a><br>
         <a href="#Compatibility">Compatibility</a><br>
         <a href="#Internals">Internals</a><br>
         <a href="#Updates">Updates</a><br>
         <a href="#Links">Related Articles</a><br>
         <a href="#TODO">TODO</a><br>
         <a href="#Thanks">Thanks</a><br>
      </P>


      <h2><a name="Intro">Introduction</a></h2>
      <h3>I hate message boxes.</h3>
      <P>Or really,&nbsp;I hate <code>MessageBox()</code>s. Something about having a 
         form of output <I>sooo</I> convenient, a single function call, no need for 
         resources to be added, for interfaces to be designed, for any planning or 
         preparation <I>at all</I> � not even a window handle is needed (!) � seems 
         almost to <I>force</I> programmers to use it whenever there is any doubt about 
         what the user should know. Is there a chance a user will perform an action in 
         error? Better get confirmation... Just pop up a <code>MessageBox()</code>! 
         &nbsp;I can't delete a file in the windows explorer without having to click 
         through at least one <code>MessageBox()</code>. Am I sure? But, the file is 
         executable... Am i <I>really</I> sure? But, it might affect registered 
         programs... Am&nbsp;I <I>really, <B>really</B></I> sure? ...&nbsp;If a check 
         box was provided to let you hide the message in the future... But that would 
         require extra effort, and there's this <B>ready to use, almost good enough</B> function 
         that will suffice...</P>
      <P>But worse yet are programs that feel the need to inform you, after performing 
         an action that you <I>explicitly requested</I>, that the action has indeed been 
         performed, using � you guessed it � <code>MessageBox()</code>. Nothing wrong 
         with a little feedback, especially if the action had effects not necessarily 
         intuitive to first-time users, but to do it in a way that requires you to stop 
         what you're doing, (and what is sometimes worse, what the <I>program</I> is 
         doing) just to acknowledge it... That is not funny.
      </P>
      <h3>OK...</h3>
      <P>Enough hypocritical griping however. Solutions do exist � creating message 
         boxes with check boxes or �yes to all� / �no to all� buttons is not difficult. 
         For the purpose of feedback, displaying a short message in a status bar will 
         often suffice, or possibly adding a log pane. But both of these options really 
         only work for the main window of an application, and have other drawbacks in 
         terms of visibility and screen real-estate as well.</P>
      <P>With Windows 2000, Microsoft began a practice of using balloons (of the comic 
         strip dialog variety) for displaying messages from system tray icons. This 
         seems to work quite well; when I dial into the Internet, once the connection is 
         made, a small icon appears in the tray, along with a balloon giving details on 
         my connection speed. A <code>MessageBox()</code> here would be inexcusable, but 
         a balloon is small, unobtrusive, and does not require any action on my part. 
         After a few seconds, it quietly fades away. Windows XP provides this feature 
         for many more tray icons, from system updates to the activation reminder, 
         adding another feature to them in the form of a close button that will dismiss 
         the balloon instantly, in case I'm annoyed by it and don't want to wait for it 
         to time out. (Windows XP also uses similar balloons in other situations, but 
         that fits more in line with the general XP aping of MacOS...)</P>
      <P>Note that balloons as described above are not ToolTips. ToolTips, also in use 
         by tray icons BTW, serve well in their current form: small unobtrusive pieces 
         of text that show up when you hover the mouse over a control, and disappear 
         immediately upon moving the mouse away. Anyone who has used BalloonHelp on the 
         MacOS should know the advantages ToolTips have when used for this purpose. 
         (Ref: <A HREF="http://developer.apple.com/techpubs/mac/HIGuidelines/HIGuidelines-240.html">
            http://developer.apple.com/techpubs/mac/HIGuidelines/HIGuidelines-240.html</A>)</P>
      <P>Unfortunately, Microsoft does not yet have a generic API for using these cool 
         little balloons. (Exception: tray icons. See <A HREF="http://www.codeproject.com/shell/systemtray.asp">
            http://www.codeproject.com/shell/systemtray.asp</A>) And even when (if?) it 
         is released, those of us who must write applications compatible with older 
         versions of Windows must still do without.
      </P>
      <h3>So get to the point already...</h3>
      <P>The point of all this of course is that I've written an easy to use balloon 
         control. I stress easy to use: in order to convince myself and others to go 
         this way, I wanted it to be as easy to pop up a help balloon as popping up a <code>
            MessageBox()</code>.</P>


      <h2><a name="Using">Using CBalloonHelp</a></h2>
      <P>Add BalloonHelp.cpp and BalloonHelp.h in your project.</P>
      <P>Near the top of BalloonHelp.cpp, there are four lines defining the timer IDs.
      </P>
      <pre>#define ID_TIMER_SHOW      3333
#define ID_TIMER_HIDE      3334
#define ID_TIMER_CLOSE     3335
#define ID_TIMER_HOTTRACK  3336</pre>
      <P>
         These values do not matter, so long as they are unique. If you are using timers 
         elsewhere in your application, you may want to define these in your resource 
         header instead.</P>


      <h2><a name="API">API</a></h2>
      <h3><a name="API Important">The important stuff</a></h3>
      <P>The easiest way to create a balloon is to use the LaunchBalloon() static 
         function:</P>
      <pre>void CBalloonHelp::LaunchBalloon(const CString&amp; strTitle, const CString&amp; strContent, 
      const CPoint&amp; ptAnchor, 
      LPCTSTR szIcon /*= IDI_EXCLAMATION*/
      unsigned int unOptions /*= unSHOW_CLOSE_BUTTON*/,
      CWnd* pParentWnd /*= NULL*/,
      const CString strURL /*= &quot;&quot;*/,
      unsigned int unTimeout /*= 10000*/)</pre>
      <P>
         This will allocate a new <code> CBalloonHelp</code> object, create the window, and show it. 
         When the window closes, the <code> CBalloonHelp</code> object will be deleted automatically. 
         Parameters are as follows:</P>
      <PRE>strTitle    |  Title of balloon
strContent  |  Content of balloon
ptAnchor    |  point tail of balloon will be &quot;anchor&quot;ed to (screen coordinates)
szIcon      |  One of:
     IDI_APPLICATION
     IDI_INFORMATION IDI_ASTERISK (same)
     IDI_ERROR IDI_HAND (same)
     IDI_EXCLAMATION IDI_WARNING (same)
     IDI_QUESTION
     IDI_WINLOGO
unOptions   |  One or more of:
     unCLOSE_ON_LBUTTON_UP   |  closes window on WM_LBUTTON_UP
     unCLOSE_ON_RBUTTON_UP   |  closes window on WM_RBUTTON_UP
     unCLOSE_ON_MOUSE_MOVE   |  closes window when user moves mouse 
                             |    past threshhold
     unSHOW_CLOSE_BUTTON     |  shows close button in upper right
     unSHOW_INNER_SHADOW     |  draw inner shadow in balloon
     unSHOW_TOPMOST          |  place balloon above all other windows
     unDISABLE_FADE          |  disable the fade-in/fade-out effects 
                             |   (overrides system and user settings)
     unDISABLE_FADEIN        |  disable the fade-in effect
     unDISABLE_FADEOUT       |  disable the fade-out effect
pParentWnd  |  Parent window.  If NULL will be set to AfxGetMainWnd()
strURL      |  If not empty, when the balloon is clicked ShellExecute() 
            |    will be called, with strURL passed in.
unTimeout   |  If not 0, balloon will automatically close after unTimeout 
            |    milliseconds.</PRE>
      <h3><a name="API Use">Use:</a></h3>
      <pre>CBalloonHelp::LaunchBalloon(&quot;BoogaBooga&quot;, 
     &quot;What the hell is \&quot;Booga Booga\&quot; supposed to mean, anyway?&quot;, 
     CPoint(0,0));

CBalloonHelp::LaunchBalloon(&quot;You are holding down the right mouse button!&quot;, 
     &quot;Blah&quot;, Cpoint(0,0), IDI_WARNING, 
      CballoonHelp::unCLOSE_ON_RBUTTON_UP|CBalloonHelp::unSHOW_INNER_SHADOW, 
                                          this, &quot;&quot;, 0);</pre>
      <P>
         The first line above will show a balloon with the title �BoogaBooga� and 
         associated message anchored to the top left corner of the screen (point is in 
         screen coordinates). Ideally, you'd anchor it to something more meaningful, 
         such as the center of a control, or a status bar icon. The default options will 
         cause this balloon to disappear after 10 seconds, and to show the standard 
         information icon at top left, and a close button at top right.</P>
      <P>The second line above will show a balloon, still anchored to the top left 
         corner, this time with the standard warning icon. The 
         CBalloonHelp::unCLOSE_ON_RBUTTON_UP option will cause it to be destroyed when 
         the right mouse button is released � it's a good idea not to use this option 
         unless the right mouse button is being held down. The 
         CballoonHelp::unSHOW_INNER_SHADOW option will cause the balloon to be drawn 
         with an inner hilight and shadow... Not all that interesting, but if you're not 
         running on Windows XP, it's the only shadow you can get.</P>
      <P>It is also possible to create a balloon using the Create() function, in which 
         case you can opt not to have the object automatically deleted when the window 
         closes (the LaunchBalloon() function always adds the option 
         CBalloonHelp::unDELETE_THIS_ON_CLOSE to force this, but it may not be desirable 
         if, for instance, you allocate a CBalloonHelp object from the stack). This 
         function gives much greater potential for customization as well (see below for 
         more info).</P>
      <h3>Complete API</h3>
      <pre><a name="API Create"></a>BOOL CBalloonHelp::Create(const CString&amp; strTitle, const CString&amp; strContent, 
               const CPoint&amp; ptAnchor, unsigned int unOptions,
               CWnd* pParentWnd /*=NULL*/,
               const CString strURL /*= &quot;&quot;*/,
               unsigned int unTimeout /*= 0*/,
               HICON hIcon /*= NULL*/);
      </pre>
      <p>This will create and display a balloon window. Title and content override any 
         set for the object prior to this call. <code>ptAnchor</code> indicates anchor 
         location in screen coordinates. <code>unOptions</code> should be a combination 
         of one or more of the following:
      </p>
<pre>CBalloonHelp::unCLOSE_ON_LBUTTON_UP   //  closes window on WM_LBUTTON_UP
CBalloonHelp::unCLOSE_ON_RBUTTON_UP   //  closes window on WM_RBUTTON_UP
CBalloonHelp::unCLOSE_ON_MOUSE_MOVE   //  closes window when user moves mouse past 
                                      //  threshhold
CBalloonHelp::unCLOSE_ON_KEYPRESS     //  closes window on the next keypress message sent
                                      //  to this thread.    (!!! probably not thread safe !!!)
CBalloonHelp::unDELETE_THIS_ON_CLOSE  //  deletes object when window is closed.  Used by 
                                      //  LaunchBalloon(), use with care
CBalloonHelp::unSHOW_CLOSE_BUTTON     //  shows close button in upper right
CBalloonHelp::unSHOW_INNER_SHADOW     //  draw inner shadow in balloon
CBalloonHelp::unSHOW_TOPMOST          //  place balloon above all other windows
CBalloonHelp::unDISABLE_FADE          //  disable the fade-in/fade-out effects (overrides system 
                                      //  and user settings)
CBalloonHelp::unDISABLE_FADEIN        //  disable the fade-in effect
CBalloonHelp::unDISABLE_FADEOUT       //  disable the fade-out effect
      </pre>
      <p>
         If <code>pParentWnd</code> is NULL, it will be set to the result of <code>AfxGetMainWnd()</code>. 
         If that is NULL, creation will fail. <code>strURL</code> if not empty will be 
         passed to <code>ShellExecute()</code> when the balloon is clicked. If not 0, <code>
            unTimeout</code> specifies the time in milliseconds until the balloon closes 
         automatically. And <code>hIcon</code> if not NULL specifies the icon shown in 
         the upper left corner of the balloon; it is copied on creation, so the icon can 
         safely be destroyed after <code>Create()</code> returns.
      </p>
      <pre><a name="API SetAnchorPoint"></a>      void CBalloonHelp::SetAnchorPoint(CPoint ptAnchor);      </pre>
      <p>Sets the point to which the balloon is anchored (the point the balloon's tail 
         attaches to). Calling this before the balloon is created has no effect, since 
         the anchor is a required parameter of <code>Create()</code>.
      </p>
      <pre><a name="API SetBackgroundColor"></a>      void CBalloonHelp::SetBackgroundColor(COLORREF crBackground);</pre>
      <p>Sets the background color of the balloon. Can be called before or after the 
         balloon is created.
      </p>
      <pre><a name="API SetForegroundColor"></a>      void CBalloonHelp::SetForegroundColor(COLORREF crForeground);</pre>
      <p>Sets the foreground (borders & text) color of the balloon. Can be called before 
         or after the balloon is created.
      </p>
      <pre><a name="API SetTitle"></a>      void CBalloonHelp::SetTitle(const CString&amp; strTitle);</pre>
      <p>Sets the title of the balloon. Can be called before or after the balloon is 
         created.
      </p>
      <pre><a name="API SetContent"></a>      void CBalloonHelp::SetContent(const CString&amp; strContent);</pre>
      <p>Sets the content of the balloon. Can be called before or after the balloon is 
         created.
      </p>
      <pre><a name="API SetTitleFont"></a>      void CBalloonHelp::SetTitleFont(CFont* pFont);</pre>
      <p>Sets the font used to draw the title of the balloon. Can be called before or 
         after the balloon is created. The font and the CFont object are stored and 
         eventually deleted by the balloon; do not use either after calling this 
         function.
      </p>
      <pre><a name="API SetContentFont"></a>      void CBalloonHelp::SetContentFont(CFont* pFont);    </pre>
      <p>Sets the font used to draw the contents of the balloon. Can be called before or 
         after the balloon is created. If this is called before creation, and the title 
         font is not explicitly set (via <code>SetTitleFont()</code>) then a bold 
         version of this font is used for the title (even if this font is already bold). 
         The font and the CFont object are stored and eventually deleted by the balloon; 
         do not use either after calling this function.
      </p>
      <pre><a name="API SetURL"></a>      void CBalloonHelp::SetURL(const CString&amp; strURL);    </pre>
      <p>Sets the URL or file to be opened by the balloon when clicked. Set to "" to 
         disable. Can be called before or after the balloon is created.
      </p>
      <pre><a name="API SetIcon"></a>      void CBalloonHelp::SetIcon(HICON hIcon);    </pre>
      <p>Sets the icon shown at the top left of the balloon. Pass in NULL to show no 
         icon. Icon will not be scaled. Can be called before or after the balloon is 
         created.
      </p>
         <pre>      void CBalloonHelp::SetIcon(HBITMAP hBitmap, COLORREF crMask);
      </pre>
      <p>Sets the icon shown at the top left of the balloon. <code>crMask</code> indicates 
         transparent color. Pass in NULL for <code>hBitmap</code> to show no icon. Icon 
         will not be scaled. Can be called before or after the balloon is created.
      </p>
         <pre>      void CBalloonHelp::SetIcon(HBITMAP hBitmap, HBITMAP hMask);</pre>
      <p>Sets the icon shown at the top left of the balloon. <code>hMask</code> indicates 
         the transparent areas. Both parameters must be valid. Icon will not be scaled. 
         Can be called before or after the balloon is created.
      </p>
         <pre>      void CBalloonHelp::SetIcon(CImageList* pImageList, int nIconIndex);</pre>
      <p>Sets the icon shown at the top left of the balloon. <code>nIconIndex</code> indicates 
         image to use. Both parameters must be valid. Icon will not be scaled. Can be 
         called before or after the balloon is created.
      </p>
      <pre><a name="API LaunchBalloon"></a>void CBalloonHelp::LaunchBalloon(const CString&amp; strTitle, const CString&amp; strContent,&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const CPoint&amp; ptAnchor,&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LPCTSTR szIcon /*= IDI_EXCLAMATION*/,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; unsigned int unOptions /*= unSHOW_CLOSE_BUTTON*/,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CWnd* pParentWnd /*= NULL*/,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const CString strURL /*= &quot;&quot;*/,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; unsigned int unTimeout /*= 10000*/)</pre>
      <p>Creates and shows a balloon. This is a static function, so calling this on an 
         instance of CBalloonHelp is not too useful. If you need more control, create a 
         derived class, or use <code>Create()</code> and associated property functions. 
         See <a href="#API Use">Use</a> for more information.
      </p>


      <h2><a name="Features">Features</a></h2>
      <UL>
         <LI>
         Easy to use interface.
         <LI>
         Adjusts position depending on anchor position; window will always be completely 
         visible, assuming content size is less than screen size.
         <LI>
         Uses translucency effects if available.
         <LI>
            Uses drop-shadow effect if available.</LI>
      </UL>


      <h2><a name="Compatibility">Compatibility</a></h2>
      <P>Tested on Windows XP, 2000, 98, and 95</P>
      <P>Compiles with VC++ 6.0 / MFC 6 and VC++.NET / MFC 7</P>


      <h2><a name="Internals">Internals</a></h2>
      <P><code>CBalloonHelp</code> is an MFC class, derived from <code>CWnd</code>. The first time a balloon is 
         created, a window class named �BalloonHelpClass� is registered. First, a window 
         class with the <code> CS_DROPSHADOW</code> style is registered; if registration fails, the 
         style is removed, and registration is attempted again. This allows use of the 
         style on Windows XP, while avoiding problems on previous versions of Windows.
      </P>
      <P>The window is created hidden, and then the size of the content and title are 
         calculated. Margins and space for the tail are added to this, and the window is 
         sized and positioned relative to the anchor point. Finally, the window region 
         is set and the window is shown.</P>
      <P>Tip: when doing shaped windows, put the shaped bits in the non-client area; 
         it's a <I>lot</I> easier to deal with if you don't have to account for that 
         stuff when dealing with drawing the client area.</P>
      <P>Timers are used to do the fade-in / fade-out effects (if available). If someone 
         knows of a better way to deal with timers than hard-coded IDs, please let me 
         know?</P>


      <h2><a name="Updates">Updates</a></h2>
      <p>1/23/02
         <ul>
            <li>
               Posted Fil Mackay's ATL port</li>
         </ul>
      <p>12/31/01
         <ul>
            <li>
               Changed utilization of transparency (again), hopefully this will workaround 
               problems with Win2k</li>
            <li>
               Altered border calculation code slightly</li>
            <li>
               Fixed bug where no title would cause balloon to be full screen sized.</li>
         </ul>
      <p>12/21/01
         <ul>
            <li>
               Expanded API to allow greater customization</li>
            <li>
               Added example of balloon created manually that moves with parent window</li>
            <li>
               Implemented keyboard hooks to allow close-on-keypress</li>
            <li>
               Separate flags to disable fade-in/fade-out</li>
            <li>
               Smooth scaling of small icons used for <code>LauchBalloon()</code> (on Win2k/XP 
               only)</li>
            <li>
               Misc. code cleanup.</li>
         </ul>
      <p>12/12/01
         <ul>
            <li>
               Fixed bug with mouse not being released until fade-out had completed.</li>
            <li>
               Added option for disabling fade effects; this is forced if the user has 
               disabled them via the control panel.</li>
            <li>
               Added option for making balloons topmost windows.</li>
         </ul>


      <h2><a name="Links">Related Articles</a></h2>
         <ul>
            <LI>
               <A href="http://www.codeproject.com/useritems/xinfotip.asp">CXInfoTip - 
                  Information Tooltip <i>Mark Bozeman</i></A>
            <LI>
               <A href="http://www.codeproject.com/useritems/message_balloons.asp">Message 
                  Balloons <i>Prateek Kaul</i></A>
            <LI>
               <A href="http://www.codeproject.com/shell/systemtray.asp">Adding Icons to the 
                  System Tray <i>Chris Maunder</i></A>
            (this one is on a different topic, but uses the Windows shell's tooltip 
            functionality available in Windows 2000 and later)
            <LI>
               <A href="http://www.codeproject.com/gdi/rgncreator.asp">The RGN Generator <i>Richard 
                     de Oude</i></A>
            (information on using regions to create shaped windows)
            <LI>
               <A href="http://www.codeproject.com/gdi/holes.asp">Creating holes in a window <i>Amir 
                     Salzbe</i></A> (same as above, but a bit simpler.)<BR>
            </LI>
         </ul>


      <h2><a name="TODO">TODO</a></h2>
      <P>Clean up code a bit... most of it is pretty straight forward, but documentation 
         could be better.</P>
      <P>Use better timer code... The current use of hard-coded IDs bothers me, though I 
         haven't found any problems yet.</P>
      <P>Move to straight Win32, not MFC derived class.</P>
      <P>Make safer for use in multiple threads. (or at least test it...)</P>
      <p>Add better code walkthrough to this article.</p>
      <h2><a name="Thanks">Thanks...</a></h2>
      <p>...To all the people who've provided feedback, positive and negative. It all 
         helps.</p>
      <p>...To Mustafa Demirhan, for his suggestion and information on using keyboard 
         hooks.</p>
      <p>...To The Code Project, for providing a useable forum for all of us.</p>


<h2>History</h2>
<p>4 Feb 2002 - updated downloads.


<!----------------------------- Article Ends ----------------------------->

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
Poke...

Comments and Discussions