Click here to Skip to main content
14,741,792 members
Articles » Desktop Development » Miscellaneous » General
Article
Posted 2 Dec 2020

Tagged as

Stats

1.5K views
159 downloads
4 bookmarked

A WPF-Friendly Shell_NotifyIcon Wrapper Class - Part 2: Minimize to System Tray

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
2 Dec 2020CPOL
This series of articles explores a new WPF-friendly wrapper class for Shell_NotifyIcon.
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:

Main window of MinimizeToSystemTray1 (top)

This is the main window of MinimizeToSystemTrayDemo1, scrolled to the bottom of the document:

Main window of MinimizeToSystemTray1 (bottom)

This is the main window of MinimizeToSystemTrayDemo2:

Main window of MinimizeToSystemTray2

This is the target window of MinimizeToSystemTrayDemo2:

Target window of MinimizeToSystemTray2

Let's take a look at the main window constructor for MinimizeToSystemTrayDemo1:

public MainWindow()
{
    InitializeComponent();
    _ = new MinimizeToSystemTray(this);
    // Because one of the parameters that was defaulted in the above call is of type
    // System.Drawing.Icon, a reference to System.Drawing had to be added to this
    // project.
}

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:

/// <summary>
/// This is the public constructor for the TargetWindow class.
/// </summary>
/// <param name="iconSource">

/// This parameter provides the initial icons for the window and its NotifyIcon.
/// </param>
/// <param name="notifyIconToolTip">

/// This parameter provides the initial tool tip text for this window's NotifyIcon.
/// </param>
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:

/// <summary>
/// This is the public constructor for the <c>IconSource</c> class. It uses
/// <paramref name="key"/> to construct a Uri for the .ico resource file containing
/// the matching icon, then loads the icon for the notify icon and constructs the icon for
/// the window, both based on this uri.
/// </summary>
/// <param name="key">
/// This is the key that provides the variable portion of the .ico file name.
/// </param>
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:

/// <summary>
/// Synchronize the notify icon with the window state of the client.
/// </summary>
    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.

/// <summary>
/// Restore the state of the window before it was minimized.
/// </summary>
private void RestoreClientWindowState()
{
	// If the client was created in a minimized window state or, more specifically,
	// if our constructor was called while our client was in a minimized window
	// state, there will be no notify icon to delete the first time we get here.
	// This is not a problem, however, because the notify icon source protects
	// itself against this case.
	_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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ronald M. Martin
Retired
United States United States
Began programming at the age of 15 in 1961.

As a student at Dartmouth College, was one of the developers of three computer time-sharing systems.

In 1969, was a founder of The Cyphernetics Corporation, a computer time-sharing services company, purchased by Automatic Data Processing (ADP) in 1975.

Spent most of career working in and around the automotive industry, working in areas such as Gauging, Size Control, Balancing and Paint Quality Measurement.

For amusement, creates software development tools and explores an ever-widening range of specialties.

Comments and Discussions

 
-- There are no messages in this forum --