The blog of dlaa.me

Get out of the way with the tray ["Minimize to tray" sample implementation for WPF]

"Minimize to tray" is a feature in some applications where minimizing the application removes its taskbar button and replaces it with a (much smaller) notification area icon. Clicking that notification icon restores the application's window - just like clicking its taskbar button would have done.

Well, I was considering using this functionality in a project I'm working on, so I looked to see what my options were. This feature didn't seem to be directly supported by WPF, so I searched the web a bit. What I found after a minute or two of searching was plenty of questions about how to implement this, a few suggestions, and not a lot else. So I figured I'd put something together myself and post it to my blog...

 

Here's the sample application I'm about to minimize - note the (boring) green square of an application icon:

About to minimize

And here's what things look like just after the application is minimized to the tray:

Notify balloon

Aside from just looking cool, that notification bubble serves an important purpose: it helps to draw the user's attention to the new icon in the notification area and calls out the application's custom minimize behavior. (To avoid being annoying, the bubble is only shown the first time the application is minimized each time it's run.) This is a nice convenience on most versions of Windows, but is pretty much necessary due to the new Windows 7 behavior of hiding notification area icons as quickly as possible. Without a helpful indication like this bubble, users might "lose" the application when it minimizes to the tray.

Aside: I understand why the Windows 7 team introduced this new behavior and I think it's perfectly reasonable. However, it has implications for this scenario, so it's good to think twice (or thrice!) about choosing to use "minimize to tray" in your application. The document I link to above has lots more guidance on the proper use of notification icons - interested parties are encouraged to review it!

After Windows 7 hides the notification icon for the application, it can be accessed via the "Show hidden icons" popup. Clicking on the hidden notification icon restores the application just like you'd expect:

Hidden by Windows 7
Aside: If you really dislike the new hiding behavior, it's easy to disable - just click the "Customize..." link (shown in the image above) and you'll be presented with a window that lets you disable this behavior for specific applications - or disable it for all of them with a single checkbox.

 

Notes:

  • I've implemented this functionality in a static MinimizeToTray class with a single Enable method that's super-easy to use. Just add a call to it in your Window's constructor and you're done:
    // Enable "minimize to tray" behavior for this Window
    MinimizeToTray.Enable(this);
    
  • WPF doesn't natively offer notify icon support, but there's nothing stopping us from using the NotifyIcon implementation that's part of WinForms! That's what MinimizeToTray does, so it's important to note that you'll need to change your project to add references to the System.Drawing and System.Windows.Forms .NET assemblies. (FYI, they're both part of the .NET Framework and are already present in the GAC (and NGEN-ed), so you don't need to worry about distributing them with your application.)
  • But it's also important to note that I've written my code such that MinimizeToTray doesn't cause either of these assemblies get loaded until the user first minimizes the application. This means neither assembly will impact the startup time of your application! (You can verify this by running the sample application, attaching the debugger, checking the module list to see that neither assembly is present, minimizing the application, and then noting that both assemblies just got loaded.)
  • I only use the most basic notify icon functionality in this sample, so the WinForms implementation is more than adequate for my needs. However, if you're looking for true WPF-style notification icon support, this implementation by Philipp Sumi looks quite promising (though I haven't tried it myself).

 

As a final favor, I'll ask that people please use "minimize to tray" wisely - just because you can minimize your application to the tray doesn't mean you should. :) Minimizing to the tray is something that's only meaningful for a limited set of scenarios - but if you find yourself in one of them, I hope MinimizeToTray is helpful!

 

[Click here to download the sample application and complete source code for MinimizeToTray.]

 

The code is quite straightforward - here it is in its entirety:

/// <summary>
/// Class implementing support for "minimize to tray" functionality.
/// </summary>
public static class MinimizeToTray
{
    /// <summary>
    /// Enables "minimize to tray" behavior for the specified Window.
    /// </summary>
    /// <param name="window">Window to enable the behavior for.</param>
    public static void Enable(Window window)
    {
        // No need to track this instance; its event handlers will keep it alive
        new MinimizeToTrayInstance(window);
    }

    /// <summary>
    /// Class implementing "minimize to tray" functionality for a Window instance.
    /// </summary>
    private class MinimizeToTrayInstance
    {
        private Window _window;
        private NotifyIcon _notifyIcon;
        private bool _balloonShown;

        /// <summary>
        /// Initializes a new instance of the MinimizeToTrayInstance class.
        /// </summary>
        /// <param name="window">Window instance to attach to.</param>
        public MinimizeToTrayInstance(Window window)
        {
            Debug.Assert(window != null, "window parameter is null.");
            _window = window;
            _window.StateChanged += new EventHandler(HandleStateChanged);
        }

        /// <summary>
        /// Handles the Window's StateChanged event.
        /// </summary>
        /// <param name="sender">Event source.</param>
        /// <param name="e">Event arguments.</param>
        private void HandleStateChanged(object sender, EventArgs e)
        {
            if (_notifyIcon == null)
            {
                // Initialize NotifyIcon instance "on demand"
                _notifyIcon = new NotifyIcon();
                _notifyIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetEntryAssembly().Location);
                _notifyIcon.MouseClick += new MouseEventHandler(HandleNotifyIconOrBalloonClicked);
                _notifyIcon.BalloonTipClicked += new EventHandler(HandleNotifyIconOrBalloonClicked);
            }
            // Update copy of Window Title in case it has changed
            _notifyIcon.Text = _window.Title;

            // Show/hide Window and NotifyIcon
            var minimized = (_window.WindowState == WindowState.Minimized);
            _window.ShowInTaskbar = !minimized;
            _notifyIcon.Visible = minimized;
            if (minimized && !_balloonShown)
            {
                // If this is the first time minimizing to the tray, show the user what happened
                _notifyIcon.ShowBalloonTip(1000, null, _window.Title, ToolTipIcon.None);
                _balloonShown = true;
            }
        }

        /// <summary>
        /// Handles a click on the notify icon or its balloon.
        /// </summary>
        /// <param name="sender">Event source.</param>
        /// <param name="e">Event arguments.</param>
        private void HandleNotifyIconOrBalloonClicked(object sender, EventArgs e)
        {
            // Restore the Window
            _window.WindowState = WindowState.Normal;
        }
    }
}
Tags: WPF