Introduction
.NET Framework has a rich set of user notifications, but there is no tray balloon that you can use in message-oriented applications. This article presents you a ready to use component and explains how to customize and use it.
Background
Sometimes when writing user interactive applications, especially those dealing with internet resources, we need to show some information balloons to the user. A MessageBox or a memo in the bottom are not very convenient. The most convenient way of notifications is a tray balloon appearing in the right bottom side of the screen (like in MSN, Skype, and ICQ).
Since I developed this component the first time, it has changed many times to meet all the requirements of its customers. Here is a list of the most significant:
- It has to be configurable (background, sound)
- It hast to work with extended desktop
- It doesn't have to attract input focus when appearing
- It doesn't have to be displayed in Alt-Tab
- It has to support hyper links and open a browser when a link is clicked
- It has to appear stacked on the screen when there are other balloons displayed
- It has to support the multi-threading async model, dealing with service from any thread (not GUI only)
- It hast to stay visible when the mouse is in the balloon's region
Key points
I will explain some the most interesting things here.
How to make the tray balloon appear without grabbing the input focus
It's really inconvenient when you're typing some text in a program and a balloon appears grabbing the input focus. We will get this bad behavior if we use the regular Form.Show
method. To show a balloon without changes in the input focus, we have to deal with the PInvoke ShowWindow
method.
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
int x, int y, int cx, int cy, int flags);
SetWindowPos(Frm.Handle, (IntPtr)(-1), 0, 0, 0, 0, 0x50);
In the snippet above, I call the native Windows API function to show our window on the top of the screen, above any window. Additionally, according to the last parameter, it will be shown without activation. That's what we need. So, the user typing some text will not be redirected to our window.
How to parse input text and create hyperlinks
To setup a LinkLabel
with multiple links is very easy, and described in MSDN in detail. But we need to do that using HTML text in the input (we should be able to copy a link from somewhere and just paste it in the .config file or whatever, and this code has to be parsed and all <a> tags should be converted into links). The most optimal way to do this task is to use Regular Expressions. Usually, Regex can solve text-based problems in the most elegant way than any code-based alternatives. Thus, we just use Regex to parse text and strip all <a> tags, and then we use them as links in the LinkLabel
control.
static readonly System.Text.RegularExpressions.Regex A =
new System.Text.RegularExpressions.Regex(
"\\<a\\w+href=\"(?<href>[^\"]*)\"\\W*\\>(?<text>[^\\<]*)\\",
System.Text.RegularExpressions.RegexOptions.IgnoreCase |
System.Text.RegularExpressions.RegexOptions.Multiline);
private void SetupText()
{
TitleLabel.Text = Title;
string msg = Message ?? string.Empty;
var matches = A.Matches(msg);
if (matches == null || matches.Count == 0)
{
MessageLabel.Text = msg;
MessageLabel.LinkArea = new LinkArea(msg.Length, 0);
}
else
{
StringBuilder sb = new StringBuilder();
int last_index = 0;
foreach (System.Text.RegularExpressions.Match match in matches)
{
var href = match.Groups["href"].Value;
var text = match.Groups["text"].Value;
sb.Append(msg, last_index, match.Index - last_index);
MessageLabel.Links.Add(new LinkLabel.Link(sb.Length,
text.Length) { LinkData = href });
sb.Append(text);
last_index = match.Index + match.Length;
}
if (last_index < msg.Length)
sb.Append(msg.Substring(last_index));
MessageLabel.Text = sb.ToString();
}
}
How it works
After you've created and adjusted the TrayBalloon
object, you should display it calling the Run
method. This method will return immediately because it is designed to be used from multiple threads which want to notify the user, not interact with them. Once you've called this method, you can continue execution and not care about how it works.
Now, let's look at the key steps that TrayBalloon
passes through. First of all, it queues form show into the thread pool. After that, the thread pool thread calls the main entry point of the TrayBalloon
form. In this method, TrayBalloon
checks for other visible instances to choose free space in the screen (remember, it has to be stacked on the screen). If there are no other instances, it is shown at the bottom; if there are other TrayBalloon
s already displayed, it will choose a free space between or above them. If there is no free space in the screen, it will just overlay the top most TrayBalloon
widow. Also, when appearing, it uses animation and fading (however, this can be skipped, depending on settings that you may use before show).
TrayBalloon
keeps itself on the screen for a few seconds and the animation starts disappearing. However, if the user keeps the mouse above the form, it will not be hidden. After the TrayBalloon
is hidden, it unregisters itself in the visible queue to free space in the screen and lets other TrayBalloon
s to reuse its space.
This is a very brief description of the internal infrastructure; please read the source code for more detailed understanding of how it works.
Result
Here is the test application that can be used to learn more about the TrayBalloon.dll component.
Here is the resulting customized balloon window:
Points of interest
During testing of many apps with tray balloons (not just this one), I've accidentally found a very interesting bug. If you make the Windows Taskbar editable and re-attract it to another corner of the screen when the tray balloon is appearing, Explorer may hang up :) Be careful.
Before using this component, we need to setup some properties such as Title
, Message
, etc. Look at the form sample to see the required minimum set of fields to be set. If you need to specify some sound when it appears, or a custom background, please do it. There are many other different tweaks that can be applied to obtain performance, beauty, and so on.
History
This is the initial version of the article. Any new improvements will be posted here in future.