This article extends the API exposed by the wrapper library and provides some simple examples to show off the extension's features.
Introduction
This series of articles introduces a new wrapper class for Shell_NotifyIcon
, a notoriously ornery Win32 API. Although WinForms provides a NotifyIcon
class, its API is only slightly better than the Win32 API and likewise for its documentation. I wanted to be independent of WinForms, so I created this wrapper class.
This article (Part 2: MinimizeToSystemTray
) extends the API exposed by the wrapper library and provides some simple examples to show off the extension's features.
Background
MinimizeToSystemTray
Part 1 of this series focused on NotifyIconWrapper
and its API. This part of the series explores one application of NotifyIconWrapper
. The idea is to take an application with a top-level window and modify it as little as possible in order to make that top-level window minimize to a Notify Icon rather than to a Taskbar Icon. This doesn't sound like a huge challenge, but it's harder than it looks. For example, it's not easy to suppress the Taskbar Icon while keeping the window in question a top-level window. I chose to hide the top-level window rather than trying to alter its appearance in some other way. The Notify Icon is synchronized with the hiding and showing of the top-level window, but it is not the window to which the NotifyIcon
is attached. While switching applications, e.g., by using Alt-Tab, you might still notice the hidden top-level window in the animation. Microsoft has been inconsistent with showing or hiding windows in different contexts. This is the best compromise I have come up with so far.
I put together a sample program to demonstrate the ability to accomplish this task. I soon realized that there was quite a bit of code that was not specific to the needs of the application. I sought to factor out this common code to allow more of a drop-in experience for a user who wants add this ability to a new or existing application. The result is a new API.
Using the Code
This is the main window of MinimizeToSystemTrayDemo1
, scrolled to the top of the document:
This is the main window of MinimizeToSystemTrayDemo1
, scrolled to the bottom of the document:
This is the main window of MinimizeToSystemTrayDemo2
:
This is the target window of MinimizeToSystemTrayDemo2
:
Let's take a look at the main window constructor for MinimizeToSystemTrayDemo1
:
public MainWindow()
{
InitializeComponent();
_ = new MinimizeToSystemTray(this);
}
You can see that there is really only one additional line of code required to make this work. Do note that in addition to a reference to MinimizeToSystemTrayLibrary
, a reference to System.Drawing
is required as explained in the above comment.
When running MinimizeToSystemTrayDemo1
, the text displayed on the main screen explains what you get in this out-of-the-box implementation.
In contrast, MinimizeToSystemTrayDemo2
uses the optional parameters of MinimizeToSystemTray
's constructor to provide an initial custom icon and tool tip for the NotifyIcon
:
public TargetWindow(IconSource iconSource, string notifyIconToolTip)
{
InitializeComponent();
IconSource = iconSource;
NotifyIconToolTip = notifyIconToolTip;
_minimizeToSystemTray =
new MinimizeToSystemTray(this, IconSource.Icon, NotifyIconToolTip);
}
MinimizeToSystemTrayDemo2
introduces a class named IconSource
as an aid in setting and coordinating the window icon with the Notify Icon. The APIs require icons in different formats. IconSource
initializes itself from a single resource twice in order to let the framework deal with the conversion. Note the all-important calls to BeginInit()
and EndInit()
. You don't see calls to these routines often. They are used here because UriSource
is normally called from within InitializeComponent()
, which provides calls to BeginInit()
and EndInit()
to coordinate the overall parsing of the XAML code:
public IconSource(string key)
{
Uri uri = new Uri($"pack://application:,,,/{key}.ico", UriKind.Absolute);
System.IO.Stream s = Application.GetResourceStream(uri)?.Stream;
if (s == null) throw new InvalidOperationException();
Icon = new Icon(s);
BitmapImage img = new BitmapImage();
img.BeginInit();
img.UriSource = uri;
img.EndInit();
Source = img;
}
MinimizeToSystemTrayDemo2
uses the Tag
properties of radio buttons to select an icon by color and to select one of several tool tip texts. You can change these while the Notify Icon is shown or hidden. The result will be visible immediately or the next time the Notify Icon is shown. Using the Tag
properties in this way allows a single event handler to handle each group of radio buttons.
There is a private
method in MinimizeToSystemTrayDemo2
that is responsible for synchronizing the state of the window with the state of the Notify Icon. It is called initially, when the state of a radio button changes and when the window is minimized:
private void SynchronizeState()
{
if (_client.WindowState == WindowState.Minimized)
{
_client.ShowInTaskbar = false;
_client.Hide();
_notifyIconWrapper.Update();
}
}
There is another private
method in MinimizeToSystemTrayDemo2
that is called to restore the window to its previous state when an appropriate NotifyIconWrapper
event occurs. Note the call to Delete()
here. This causes NotifyIconWrapper
to remove the Notify Icon via the Win32 API. The NotifyIconWrapper
will create a new Notify Icon when it is needed.
private void RestoreClientWindowState()
{
_notifyIconWrapper.Delete();
_client.ShowInTaskbar = true;
_client.Show();
_client.WindowState = WindowState.Normal;
}
MinimizeToSystemTray Class
public class MinimizeToSystemTray : IDisposable
Namespace: MinimizeToSystemTrayLibrary
Assembly: MinimizeToSystemTrayLibrary.dll
Remarks
This class uses NotifyIconWrapper
to provide minimize to system tray functionality.
Constructors
MinimizeToSystemTray
public MinimizeToSystemTray(int callbackMessage = WindowMessages.User)
This is the constructor for the MinimizeToSystemTray
class.
client
is a top-level window to be served. icon
is an optional icon of class System.Drawing.Icon
containing multiple icon images from which a suitable small icon will be extracted. tooltip
is an optional string
to be used for the Notify Icon's tool tip.
Properties
Icon
public System.Drawing.Icon Icon { get; set; }
Get or set the Icon to be displayed as the classic notify icon.
Tip
public String Tip { get; set; }
Get or set the tool tip text to be displayed when hovering over the notify icon. The string
is limited to 63 characters.
Methods
Dispose
public void(Dispose)
Implement the IDisposible
pattern.
Points of Interest
Rudimentary exception handling has been provided for each executable project. In each case, you will find it in a file named program.cs. This file also contains the entry point (Main
) for the project. I did not use the default startup code because I felt that it did not suit my purposes, in particular, it doesn't provide a single point at which to handle exceptions.
The next article in this series will deal with the front end of a Notify Icon-based ad blocking application.
History
- 1st December, 2020: Initial version