Introduction
Some time ago, I noticed strange behavior of the Network Connections tray icon hint in Windows 7. I know that WinAPI limits tray icon hint with 64 characters (including the null
-terminator), but this hint was multiline, was much longer than 64 characters text and some text was italic! (in Win8 italic disappears, just long multiline hint) So, it seems that Win7 knows much more about the tray icon hint that I know and that WinForms knows. After some lurking in stackoverflow and MSDN, I found that since Windows Vista WinAPI of tray icons was extended. But there isn't any documentation and/or .NET wrappers for new features (just MSDN enum
/struct
member descriptions). Even the WinAPI Code Pack ignores them.
It was found that noticed strange hint was not extended tray icon hint. New API allows applications to use their fully defined UI. And that is not the one new opportunity.
So, I decided to create my own implementation. It may be useful for anyone who builds applications with tray icons with C#/WinForms.
Background
As mentioned in MSDN, new API provides the following new features:
- GUID-based icons identification (instead of old style
HWND
and icon index). - Large (32x32 instead of 16x16 pixels) system icons in notification balloons.
- Custom icons in notification balloons.
- Using application-defined UI instead of standard 64-characters limited hint.
I've researched the decompiled sources of the built-in NotifyIcon
component and found it fully non-improvable. Firstly, it uses much of WinForms internal methods. I can call them via reflection, but there is too much code to be written to do this and too many resources will be consumed in runtime. Especially, if 70% of that was just WinAPI P/Invoke wrappers.
By design of WinAPI, there cannot be a tray icon without the window to handle its messages. NotifyIcon
creates its own invisible window for this purpose and contains many hacks to make context menu from another window (user code window) work with it. This was the second and the main difficulty.
So, if I cannot use a part of existing and working code (and if this code contains too many "workarounds"), my implementation will be almost completely new. Also I don't like the solution with own window (to have a new window dedicated to handle tray icon messages while I always have an own window - at least the app's main form?..). Handling window messages requires access to WndProc
of the window and I didn't find a solution to do this automatically, so my component will not be so easy for integration as the standard control... But I think it's worth it.
In common case, using of the extended tray icon tooltip requires writing this tooltip by your own. If there is no need in something unusual, there will be just a large multiline (but custom, in terms of new tray icon API) tooltip. Built-in WinForms ToolTip
control is mostly dedicated to show tooltips for controls. It has a timer to show and hide and is a much linked with other controls. So, the new implementation of hint-like window was born too. Someone may say that I'm inventing bicycles. And may be someone will be right. But I don't think that we could force built-in ToolTip
to show wherever we want something like this:
Using the Code
To use TrayIcon
in your code, you must do the following:
- Add TrayIcon.cs and its dependencies to your project
- Rebuild it, to component appear in Forms Designer toolbox
- Place it on the form
- Set component's property
OwnerForm
to this
(containing form, you may do it from PropertyGrid
of design mode), set other properties as you desire (Icon
, HintText
, GUID
and others). Set Enabled
property to true
to enable tray icon. - In form's code, override
WndProc
and call there the WndProc
of a TrayIcon
. Somehow like this:
protected override void WndProc(ref Message m)
{
if (myTrayIcon.WndProc(ref m))
return;
base.WndProc(ref m);
}
TrayIcon
will catch only tray-related messages and pass all others.
After all of this is done, you can enjoy a new tray icon with new notifications :). Like this:
Almost all new features are accessible from the TrayIcon
properties and methods.
Extended tooltip is not the part of tray icon API, but this demo contains common implementation. To use it, set the ShowDefaultTips
to false
and set LongHintText
to required long (and multiline) text. Demo works in Windows XP in compatible mode, so it is recommended to set also HintText
to some short version of your hint. It will also be shown if ShowDefaultTips
is enabled.
You can fully override the size and look of the tooltip. To do that, you must set your own handlers to TooltipMeasure
and TooltipPaint
events and do all measuring and render there. This demo also does this to show an icon in tooltip. To show completely different UI instead of the hint, handle the TooltipShown
and TooltipClosed
events to show and close your UI.
As there is a custom tooltip for tray icon, I've also done the implementation for other purposes. This is not the replacement of the standard ToolTip
component, but it is also useful. CustomHint
component uses the same principles of custom measuring and render. As it is fully custom, it has no connection with the controls, so it should be shown manually. Like this:
private void lblCustomHintTest_MouseLeave(object sender, EventArgs e)
{
customHint.Hide();
}
private void lblCustomHintTest_MouseEnter(object sender, EventArgs e)
{
customHint.Show(PointToScreen(new Point(lblCustomHintTest.Left,
lblCustomHintTest.Top + lblCustomHintTest.Height)));
}
After overriding its render, you may use in forms such strange things as this:
History
- 22 Oct, 2014 - Discovered problem and made mostly working solution
- 23 Oct, 2014 - Integrated in one of my projects and tested; made a demo project and uploaded it here
- 29 Oct, 2014 - Update with custom hint and custom tray hint implementations