Introduction
The other day, I received an email response to a sales follow up I'd sent to a potential customer which stated he was not going to buy my software, Corner Bowl Log Manager, because he was unable to get it to work. I gathered my thoughts, then called him up because surely if he couldn't get the software to work, others were in the same predicament. In summary, the user interface was not intuitive. To make matters worse, he did not know he could simply press F1 to access the associated help. This was unexpected. As I recall, the F1 help command was introduced by Microsoft as a usability standard back when Windows 95 was released, which all Windows developers should now religiously implement. Well, maybe, we all do, but even some IT professionals, such as those that use my software, do not know about the power of the F1 key. As I search for words to write this article, I even see Help printed on the F1 key. So what to do? Simple, add a question mark icon next to the controls of confusion, and popup a multi-line tooltip that explains in detail how to use the dialog to configure the software. This article offers a C# alternative to the built-in tooltip control, which is both visually pleasing and highly customizable.
Requirements
Provide a simple icon users can easily identify as a help provider. Once a user moves their mouse over the icon, a visually pleasing popup window must display context sensitive help that remains visible until they choose to close the view.
Design
This control consists of a fixed size UserControl that handles MouseOver events. When you move the mouse over the control, a popup window is displayed. This popup window uses properties contained within the UserControl to determine the size, content, and UI styles to apply. Knowing that the built-in tooltips were dated, I searched for a UI solution. I found the toolbar tooltips in Microsoft Office Word 2007 offered a good solution that would fulfill my requirements. The Word tooltips popup a window which has round corners, a border, a gradient background, a title, a message, a horizontal line, an F1 information message, and portrays a transparent based shadow (see Figure 1).
Figure 1 (Microsoft Word 2007 Tooltip)
Since I had never created a window with transparent attributes, I searched this site to find a transparent window sample. red_moon's A ToolTip with Title, Multiline Contents, and Image provided a sample which taught me the necessary steps to implement transparency. In summary, to display round corners or a transparent shadow, the WS_EX_LAYERED
style must be applied to the window. This is accomplished by overriding the CreateParams
property. Once overridden, paint messages are no longer fired. Instead, within the constructor, the code must draw to a memory mapped bitmap which is selected into a memory device context for drawing via the UpdateLayeredWindows
user32 API. Lastly, the window is displayed by calling the ShowWindow
user32 API. For more information, see Layer Windows on MSDN.
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= WS_EX_LAYERED;
return cp;
}
}
private void SelectBitmap(Bitmap bmp)
{
IntPtr hDC = GetDC(IntPtr.Zero);
try
{
IntPtr hMemDC = CreateCompatibleDC(hDC);
try
{
IntPtr hBmp = bmp.GetHbitmap(Color.FromArgb(0));
try
{
IntPtr previousBmp = SelectObject(hMemDC, hBmp);
try
{
Point ptDst = new Point(Left, Top);
Size size = new Size(bmp.Width, bmp.Height);
Point ptSrc = new Point(0, 0);
BLENDFUNCTION blend = new BLENDFUNCTION();
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
UpdateLayeredWindow(
Handle,
hDC,
ref ptDst,
ref size,
hMemDC,
ref ptSrc,
0,
ref blend,
ULW_ALPHA);
}
finally
{
SelectObject(hDC, previousBmp);
}
}
finally
{
DeleteObject(hBmp);
}
}
finally
{
DeleteDC(hMemDC);
}
}
finally
{
ReleaseDC(IntPtr.Zero, hDC);
}
}
Implementation
I created a new control, assigned a background image, and set the minimum and maximum size to 16x16. I added Title
and Message
properties which provide a means to assign the necessary help information at either design time or runtime. To prevent the control from receiving focus, I overrode the TabStop
property and set the value to false
. I also added properties for all UI aspects of the control, enabling the look to be quickly customized.
To get all fonts and colors used by Word, I took a screenshot of the Word tooltip and pasted it into Paint. Using the color picker tool, I figured out which colors they used. Finally, I took screenshots of each potentially matching font and pasted the bitmap into Paint, comparing widths, heights, and styles until I found what I think is the correct font.
Next, I added MouseEnter
and MouseLeave
event handlers which show and hide the popup window accordingly. Please note, I did not include a timer that automatically closes the popup window. Instead, to dismiss the popup window, the user simply moves their mouse off the control. This format provides everyone with as much time as necessary to read and understand the help without having to worry the tooltip is going to disappear on them.
Aside from the few nuances described earlier, the Form code is fairly simple. Once the bitmap is created, it's simple bit math to get the correct positions and sizes of the internal drawing components. The exception to this is the shadow code. I looked at many different solutions, but ultimately went with red_moon's concept.
Testing
Once the control was complete, I created a test application which used a timer to show and hide the popup window infinitely while I watched the memory in Task Manager. I used a large message that would display a huge bitmap so I was able to more quickly identify any issues.
Sample Application
Once I was satisfied the code was not leaking, I created a sample application for this article (Figure 2). I added a properties grid which quickly surfaced many properties I needed to hide. I overrode those properties, and set the Browsable
attribute to false
, which has the effect of hiding each tagged property within Visual Studio's design mode.
Figure 2 (Sample Application)
What's Missing
Many of the tooltip controls that are available offer a fading option; however, Corner Bowl Log Manager is targeted at Windows Server and is typically used through Remote Desktop. For those of you with experience managing servers through Remote Desktop, you already know that although fading looks great on your desktop, it performs very poorly over a network link. This control does not include a fading option although it would be very easy for you to add.
Source Control
The source code contained within this article has been posted to CodePlex and therefore contains source control information. Although this article contains a working version of this control, the source code tip must be downloaded from CodePlex at: http://cbpopuphelpcontrol.codeplex.com.
Conclusion
The code contained within this article is quite simple, and I hope self-explanatory. Hopefully my work here will be appreciated by other C# developers that need simple inline context-sensitive help without the requirement of loading a .CHM or other help files.