Author Topic: [Reference][C#, VB.NET] Making better tray apps in .NET  (Read 4316 times)

0 Members and 1 Guest are viewing this topic.

Offline MyndFyre

  • Boticulator Extraordinaire
  • x86
  • Hero Member
  • *****
  • Posts: 4540
  • The wait is over.
    • View Profile
    • JinxBot :: the evolution in boticulation
[Reference][C#, VB.NET] Making better tray apps in .NET
« on: June 20, 2005, 02:26:57 pm »
Have you ever noticed that when you have a notification icon on your Form and you Hide() your Form, that the icon disappears too?  That defeats the purpose of the icon, don't you think?

I've put together a sample project in both C# and VB.NET that demonstrate how to inherit the System.Windows.Forms.ApplicationContext class to eliminate this behavior.

(Requires Visual Studio 2003)
C# Tray Icon Demo
VB.NET Tray Icon Demo

Please note: the VB code was a LOT of copy-paste from the C# project followed by adapting the code to work with VB syntax.  Note, though, that the C# code has XML documentation comments written in.

This article will focus mainly on the C# implementation.  Classes are the same across both projects.

First, I created a Windows Application project, which started me off with a Form (Form1).  I customized the form a little bit, but primarily, I added a CheckBox object (cbShouldClose) and a property on the class (ShouldClose), defined:

Code: [Select]
/// <summary>
/// Gets or sets whether or not the Form should close when the close button is
/// clicked.  If this property returns <b>false</b>, the Form will minimize to
/// the system tray.
/// </summary>
[Category("Behavior"),
Description("Specifies whether the Form should close when the close button " +
"is selected or, alternatively, just minimize to the tray."),
Browsable(true)]
public virtual bool ShouldClose
{
get
{
// checks to make sure the CheckBox is not null.  If it is null, it always
// returns false.
return (this.cbShouldClose == null) ? false : this.cbShouldClose.Checked;
}
set
{
if (cbShouldClose != null)
cbShouldClose.Checked = true;
}
}

I also removed the Main function from the Form (I'll tell you why in a minute).

Then, I created a class to inherit from the System.Windows.Forms.ApplicationContext class.  I called it TestAppContext.

Essentially, the TestAppContext class maintains an instance of a ContextMenu object (cmIcon) and a NotifyIcon object (m_ni).  The ContextMenu is set to the ContextMenu property of m_ni and initialized with its menu items. 

During the constructor function, TestAppContext creates a new instance of the Form1 class (calls it Form), and sets it to the MainForm property of the ApplicationContext class (from which we derived).  This does a lot for you, including automatic monitoring of the Form's closure, which automatically exits your thread.  After creating the Form object, we register its Resize and Closing events, and then display the Form:

Code: [Select]
Form1 form = new Form1();
this.MainForm = form;
m_ni.Icon = form.Icon;
form.Resize += new EventHandler(form_Resize);
form.Closing += new System.ComponentModel.CancelEventHandler(form_Closing);
form.Show();

Note that I set the tray icon to the same icon used on the Form; you can set it to whatever you want, though.

That's about it!  Using simple event logic (see the source code), I capture the Minimize event (through the Resize event) and Hide() the form while displaying the icon.  When I want to restore the Form, I simply set its window state back to Normal (if I chose Restore from the menu) or Maximized (if I chose Maximuze from the menu) and tell it to Show().

I mentioned that I took the Main method out of the Form.  That's because the default Main() method calls:
Code: [Select]
Application.Run(new Form1());

We're not running the Form anymore.  I created a new class called EntryPoint, which provides the application entry point:

Code: [Select]
using System;
using System.Windows.Forms;

namespace CsTrayIconDemo
{
/// <summary>
/// Provides an entry point for the application.  This class cannot be inherited.
/// </summary>
public sealed class EntryPoint
{
/// <summary>
/// Prevent the class from being instantiated.
/// </summary>
private EntryPoint()
{
}

[STAThread()]
public static void Main(string[] args)
{
TestAppContext context = new TestAppContext();
Application.Run(context);
}
}
}

We're actually creating the application context and running that -- a little-known overload of the Application.Run() method!

For further information -- Skywing of Valhalla Legends made an excellent point in this post about preventing the loss of your icon when the shell program restarts.
I have a programming folder, and I have nothing of value there

Running with Code has a new home!

Our species really annoys me.