
Introduction
I needed an OSD-like window to show some important information from my WinForms application. I looked everywhere for samples in C#/.NET, but the samples that I could find were weak (from my point of view) and written in C++ (or C). So, I had no choice but to write my own OSD window (hereafter, just OW). But the excellent article "Floating, collapsible transparent window in C#" by olvio had some great code that helped me a lot. Thank you very much, olvio!
Background
First of all, what is an OSD? OSD is a short text message, displayed to the user when some event occurs � like the user presses the key, file downloading is complete, a new mail has arrived, and so on. An OSD (and OW, obviously) must possess three main characteristics:
- Should always be the top most window: User must see our message even if he/she is working in Word, IE, or reading a mail.
- Should always be completely transparent: User must see the text, and not the background of OW.
- Should always be completely transparent to mouse/keyboard events: This means that the user must be able to select an icon positioned "under" OW through OW. Or, if the user is working in Word, and OSD's text is displayed "above" Word's main area and the user moves the mouse pointer in OSD's client area, then the mouse pointer must not change its appearance to "Arrow". Instead, it must keep the "Text select" form. In other words, the mouse/keyboard doesn't "see" OW, but the user does. :) Needless to say, OW must be transparent to the system as a whole � no button on the task bar, no entry in Task Manager, etc.
The suggested solution completely fulfills all the three requirements. Moreover, it offers additional features (optional, of course) � a bunch of animation effects when OW is shown/hidden, the OSD text itself may be fully opaque, fully transparent, or in between, and some others.
General architecture
As said earlier, the great code from olvio serves as the starting point. But I have "stripped off" a few redundant functions from the original FloatingWindow
and kept only the required ones for OSD. So, my FloatingWindow
is much, much simpler than the original. To be brief, the FloatingWindow
is created as a System.Windows.Forms.NativeWindow
with some special ExStyle
flags:
WS_EX_TOPMOST
helps OW to be the top most window.
WS_EX_TRANSPARENT
helps OW to be completely transparent (by sight).
WS_EX_LAYERED
(this flag is supported by Windows 2000 and above!) and WS_EX_TOOLWINDOW
help OW to be completely transparent to the mouse/keyboard/system.
Now, we can show a new window (with or without animation) and also hide it (also with optional animation). From this window inherits FloatingOSDWindow
, our main "work horse". It overrides the PerformPaint()
method of the base class to show the OSD text, and gives us the public method Show(�)
with many parameters. As a matter of fact, I have used the OW suffice to know what each parameter of this single method means, and nothing more. Let's jump to this method.
Using the code
We instantiate the new OW by calling a parameter-less constructor:
FloatingOSDWindow osd1 = new FloatingOSDWindow();
Now, we are ready to call Show(�)
:
public void Show(Point pt, byte alpha,
Color textColor, Font textFont, int showTimeMSec,
AnimateMode mode, uint time, string text)
The purpose of these parameters:
pt
- Coordinate of the top-left corner of OW in screen coordinates.
alpha
- Degree of transparency of the OSD text. This is not the transparency of OW's background. This window doesn't have the background. So this parameter is for the text's transparency. As you may expect, 0 means totally transparent (no text) and 255 means totally opaque � any object "under" the text is hidden. As a general rule, any value in the range 100-210 is fine.
textColor
- OSD text color.
textFont
- OSD text font. Again, the font must be large enough (18-48 point) to attract the user's attention.
showTimeMSec
- For how long the OSD text will be displayed on the screen, in milliseconds. After this, OW will be automatically closed and disposed.
mode
- One of the AnimateMode
enum
members. Determines the effect that will be used when OW opens/closes. Also see the next parameter, time
.
time
- If this parameter is zero, no animation effect is used, the mode
parameter is just ignored, and OW opens/closes at once. If it is greater than 0, it specifies how long it takes to play the animation, in milliseconds. This time is not included in showTimeMSec
but used twice � when OW opens, and when it closes. Example: showTimeMSec
= 3500 and time
= 500. In this case, after calling Show(�)
, for 0.5 sec, OW will be opened with the effect specified in mode
. Next, the fully opened window will be displayed for 3.5 sec. Finally, during the same 0.5 sec, OW will be closed with the same effect. Again, a general (if we want animation) value in the range 100-500 is fine for this parameter.
text
- OSD text itself, at last. :)
Example of calling the function:
osd1.Show(new Point(20, 45), 155, Color.Lime,
new Font("Verdana", 26f, FontStyle.Bold | FontStyle.Italic),
2400, FloatingWindow.AnimateMode.ExpandCollapse,
370, "Hello! I am OSD window...");
After calling Show(�)
, you don't need to call the Hide()
/Close()
method explicitly. See the description of the showTimeMSec
parameter if you have forgotten. But, of course, you can do it if, for example, if you want to force OW to close before showTimeMSec
expires.
Points of interest
When I tried to override the PerformPaint()
method in the FloatingOSDWindow
class to provide the drawing of the OSD text, my first approach was to draw the string with:
Graphics g = e.Graphics;
g.TextRenderingHint =
System.Drawing.Text.TextRenderingHint.AntiAlias;
g.DrawString(�);
It works, but in spite of the AntiAlias rendering mode for the text, the OSD text comes into view with a black edge around the letters. This edging is very thin, but noticeable. So I tried another way � create GraphicsPath
from the given string and fill up this path with the given color:
Graphics g = e.Graphics;
this._gp = new GraphicsPath();
this._gp.AddString("OSD text", �..);
g.SmoothingMode = SmoothingMode.HighQuality;
g.FillPath(this._brush, this._gp);
This approach works perfectly as you can see from the demo project. The interesting problem was with this._gp.AddString(�)
. This string of code, as you expect, adds the given text string to the path. One of the parameters for this method, float emSize
, is the font size. Of course, this size must be the same as the size of the textFont
font, which is passed to Show(�)
. At first glance, there is no problem: use textFont.SizeInPoints
and we have the needed value. But this is tricky: AddString(�)
expects float emSize
in pixel, but we try to pass it as point. So, we need to convert between points and pixels, which in turn requires detailed knowledge of the DPI of the device we are drawing on. The last value returned by the Graphics.DpiY
property:
Graphics g = e.Graphics;
�
this._gp.AddString(..., ..., ...,
g.DpiY * textFont.SizeInPoints / 72, ..., ...);
We divide by 72, since point is 1/72 of an inch, no matter on what device we draw. And, Graphics.DpiY
returns in dots (and, in fact, pixels) per inch. So this formula works just fine.
Final notes
This solution was developed under VS2005/FW2.0. So it may not open in VS2003. But the code will, no doubt, work in VS2003/FW1.1(1.0). Just create a new solution and use the *.cs files from the pack.
History
- 1/1/2006: Version 1.0 - First release.
- 5/25/2006: Version 1.1 - Fixed problem with handles leak.