Saturday, March 26, 2005 #

Minimizing a window to the System Tray on a Close event

A question that .Net programmers does frequently ask is the following : how to minimize a window in the systray when the user clicks the "Close" button ? This post exposes solutions to the various problems this quite simple need implies.

The first step is to prevent the window to close. This can be done either by handling the Closing event or by overriding the OnClosing protected virtual method. For performance and memory consumption reasons, I'd prefer not to use event handlers whenever possible. The code is quite simple since whe only need to cancel the operation, and looks like the following...

Code Copy
/// <summary>
///
Occurs when the window is requested to be closed.
/// </summary>
///
<param name="e">The event arguments</param>
protected override void OnClosing(CancelEventArgs e)
{
// The window must only be minimized in tray
e.Cancel = true;
MinimizeInTray();

base.OnClosing(e);
}

... and the MinimizeInTray method must do the following:

  • Minimize the window,
  • Hide the window item in the taskbar.
Code Copy
private void MinimizeInTray()
{
this.ShowInTaskbar = false;
this.WindowState = FormWindowState.Minimized;
}

Now, supposing that you have a context menu attached to the NotifyIcon object that represents the systray icon for your application, you can add the following menu items:

  • An openMenu menu item that restores the window,
Code Copy
private void openMenu_Click(object sender, System.EventArgs e)
{
ShowFromTray();
}

private void ShowFromTray()
{
this.WindowState = FormWindowState.Normal;
this.ShowInTaskbar = true;
}
  • And an exitMenu item that closes the application.
Code Copy
private void exitMenu_Click(object sender, System.EventArgs e)
{
Application.Exit();
}

Note that calling Close() fails because our OnClosing method prevents the window to close...

Now, we should think that our job is completed. But (yes, there's always a but in programming), what happens if the computer is being shutdown ? The answer is quite simple : our OnClosing implementation prevents the window to be closed, which prevents the computer to be shutdown...

To solve this issue, we need some lightweight Interop :

  • First, override the WndProc virtual method to handle the WM_QUERYENDSESSION message:
Code Copy
private const int WM_QUERYENDSESSION = 0x11;
private bool endSessionPending;

protected
override void WndProc(ref Message m)
{
if (m.Msg == WM_QUERYENDSESSION)
endSessionPending = true;
base.WndProc(ref m);
}
  • Then, modify the OnClosing method to handle the event in a different way :
Code Copy HideScrollFull
/// <summary>
///
Occurs when the window is requested to be closed.
/// </summary>
///
<param name="e">The event arguments</param>
protected override void OnClosing(CancelEventArgs e)
{
if (endSessionPending)
{
// The session is ending.
e.Cancel = false;
}
else
{
// The window must only be minimized in tray
e.Cancel = true;
MinimizeInTray();
}

base.OnClosing(e);
}
. . .

And that's all!

The full C# sample code is available below. There's only missing icon resources, but you will correct them by yourself!

Code Copy HideScrollFull
#region References

using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Security.Permissions;

#endregion

namespace
MinimizeOnClose
{
/// <summary>
/// Represents the main window of the application.
/// </summary>
public class MinimizeOnClose : System.Windows.Forms.Form
{
#region Interop Constants

private const int WM_QUERYENDSESSION = 0x11;

#endregion

#region
Fields

private System.Windows.Forms.NotifyIcon notifyIcon;
private System.Windows.Forms.ContextMenu notifyIconMenu;
private System.Windows.Forms.MenuItem openMenu;
private System.Windows.Forms.MenuItem hideMenu;
private System.Windows.Forms.MenuItem exitMenu;
private System.ComponentModel.IContainer components;
private System.Windows.Forms.MenuItem blankMenu;

private bool endSessionPending;

#endregion

#region
Instance Management

public MinimizeOnClose()
{
InitializeComponent();
}

#endregion

#region
Protected Overrides

/// <summary>
/// Cleans all the resources.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
components.Dispose();
}
base.Dispose(disposing);
}

/// <summary>
/// Occurs when the window is requested to be closed.
/// </summary>
/// <param name="e">The event arguments</param>
protected override void OnClosing(CancelEventArgs e)
{
if (endSessionPending)
{
// The session is ending
e.Cancel = false;
}
else
{
// The window must only be minimized in tray
e.Cancel = true;
MinimizeInTray();
}

base.OnClosing(e);
}
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode=true)]
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_QUERYENDSESSION)
endSessionPending = true;
base.WndProc(ref m);
}
#endregion
#region Private Members

#region Event Handlers

private void openMenu_Click(object sender, System.EventArgs e)
{
ShowFromTray();
}

private void hideMenu_Click(object sender, System.EventArgs e)
{
MinimizeInTray();
}

private void exitMenu_Click(object sender, System.EventArgs e)
{
Application.Exit();
}

#endregion

#region
Private Methods

private void MinimizeInTray()
{
this.ShowInTaskbar = false;
this.WindowState = FormWindowState.Minimized;

openMenu.Visible = true;
hideMenu.Visible = false;
}

private void ShowFromTray()
{
this.WindowState = FormWindowState.Normal;
this.ShowInTaskbar = true;

openMenu.Visible = true;
hideMenu.Visible = false;
}

#endregion

#region
Windows Form Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(MinimizeOnClose));
this.notifyIcon = new System.Windows.Forms.NotifyIcon(this.components);
this.notifyIconMenu = new System.Windows.Forms.ContextMenu();
this.openMenu = new System.Windows.Forms.MenuItem();
this.hideMenu = new System.Windows.Forms.MenuItem();
this.blankMenu = new System.Windows.Forms.MenuItem();
this.exitMenu = new System.Windows.Forms.MenuItem();
//
// notifyIcon
//
this.notifyIcon.ContextMenu = this.notifyIconMenu;
this.notifyIcon.Icon = ((System.Drawing.Icon)(resources.GetObject("notifyIcon.Icon")));
this.notifyIcon.Text = "MinimizeOnClose";
this.notifyIcon.Visible = true;
//
// notifyIconMenu
//
this.notifyIconMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
this.openMenu,
this.hideMenu,
this.blankMenu,
this.exitMenu});
//
// openMenu
//
this.openMenu.Index = 0;
this.openMenu.Text = "&Open";
this.openMenu.Visible = false;
this.openMenu.Click += new System.EventHandler(this.openMenu_Click);
//
// hideMenu
//
this.hideMenu.Index = 1;
this.hideMenu.Text = "&Hide";
this.hideMenu.Click += new System.EventHandler(this.hideMenu_Click);
//
// blankMenu
//
this.blankMenu.Index = 2;
this.blankMenu.Text = "-";
//
// exitMenu
//
this.exitMenu.Index = 3;
this.exitMenu.Text = "E&xit";
this.exitMenu.Click += new System.EventHandler(this.exitMenu_Click);
//
// MinimizeOnClose
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(240, 78);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "MinimizeOnClose";
this.Text = "MinimizeOnClose";
}
#endregion

/// <summary>
/// The application entry point.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new MinimizeOnClose());
}

#endregion
}
}
. . .

Have a nice week-end, and do not overindulge in Easter eggs too much!

posted @ 12:02 PM | Feedback (6)