Skip to content
░▒▓█│【Walkman】│█▓▒░ edited this page Aug 6, 2019 · 4 revisions

Introduction

Some time ago I've noticed strange behavior of the Network Connections tray icon hint in Windows7. 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 are no any documentation and/or .NET wrappers for new features (just MSDN enum/struct member desciptions). 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 own implementation. May be useful for anyone who builds applications with tray icons with C#/WinForms.

Contents

The repository contains the TrayIcon component itself, its dependencies (WindowsVersionDetector, User32, WinAPI) and a small demo project.

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. By first, it uses a 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 them 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 a much of hacks to make context menu from another window (user code window) work with it. This was the second and the main difficalty.

So, if I cannot use a part of existing and working code (and if this code contains too much "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 found a solution to do this automatically, so my component will not be so easy for integration... 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:

TrayIcon hint example

Using the code

To use TrayIcon in your code you must do following:

  1. Add TrayIcon.cs and its dependencies to your project.

  2. Rebuild it, to component appear in Forms Designer toolbox.

  3. Place it on the form.

  4. 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.

  5. 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 other.

After all of this done, you can enjoy new tray icon with new notifications. Like this: TrayIcon example

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:

CustomHint example

Contact information

If you want to watch this project news, you may use one of following:

Facebook

Twitter

Own site

This is really possible that information there will be updated more frequently than here.

Clone this wiki locally