Show/Hide Outlook Express Tray App






4.38/5 (8 votes)
Dec 30, 2002
4 min read

143952

3147
An application to effectively place an application in the system tray
Introduction
With this application it is possible to effectively load another application (in this case Outlook Express) into the System Tray
Background
I usually load my Email Client, usually Outlook Express on my Microsoft OS, at startup. I have it set to poll the Email Server every 3 minutes. The problem I find is that OE takes up room on the taskbar, and I sometimes inadvertantly shut it down.
The other day I downloaded a C++ application called Window, written by by Steve Kemp at http://www.steve.org.uk/, which Hides and Unhides the main window of any program, and decided I could do something like that in C#. So I decided to write an application that would allow me to load OE with a hidden window and hide or unhide that window on demand.
This is my first real C# program (at least one that does something useful). I usually code in VB.NET, and the code for the About box was copied in from my VB code and converted to C#.
It was at this point that I realised how much VB lets you get away with, for example the lines like
oCompany = (System.Reflection.AssemblyCompanyAttribute)
oCustomAttributes[Index];
which in VB reads as
oCompany = oCustomAttributes(Index)
because VB will perform this implicitly, whereas C# will not (this threw me at first, I couldn't understand why a perfectly good peice of code wouldn't work).
My first attempt used DLLImport
and called various Win32 library functions (EnumWindows
, GetWindowText
and ShowWindow
) and used ShellExecute
to launch an application. I then discovered System.Diagnostics.Process
and decided to use this to load and control the behaviour of the applications Window.
However, I soon discovered a couple of problems with this approach, while I could load any application with any of 4 window styles (Hidden, Maximized, Minimized and Normal) using Process.Start
, and later kill that application using Process.Kill
, I couldn't find a way change the Window style using the Process object, and to make this worse the Process.MainWindowHandle
property was returning zero, so I was unable to locate a valid Window handle for the recently launched application.
To overcome these problems I resorted to DLLImport
and the Win32 library functions EnumWindows
, GetWindowText
and ShowWindow
.
Then I realised that with the window hidden at startup the process might not be able to locate a window handle, a quick test starting Process with the window minimised proved this to be the case.
The main class is ExternalApplication
which inhertits from System.Diagnostics.Process.
I extended this class by overloading the Constructor and the Start method, these additions are for convenience only. I added the public methods HideApplication
and ShowApplication
.
Initially these methods called EnumWindows
and attempted to locate a Window with the specified window title, the Property WindowTitle
was initialised with the required window title or partial window title, this was parsed using System.Text.RegularExpressions.Regex
(necessary to locate windows that match a partial window title string). The variable hWND
is set to the value of the located windows hwnd and the Enum exited.
However by starting the process with the application's window minimised it is possible to locate a valid window handle, using Process.MainWindowHandle
, if this is then stored in the variable hWND
, it is then possible to eliminate the call to EnumWindows
, and the WindowTitle property becomes redundant.
If the window is not already in the state required, as determined by a call to IsWindowVisible
, ShowWindow
is called and the hWND
value and the appropriate style value (SW_HIDE
or SW_RESTORE
) passed to it.
When the Window is to be shown SetForegroundWindow
is also called to ensure that the window will be the top most.
using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
namespace ExternalApplication
{
/// <summary>
/// Summary description for OutlookExpress.
/// </summary>
public class ExternalApplication : System.Diagnostics.Process
{
// private delegate bool CallBack(int hwnd, int lParam);
private const int SW_HIDE = 0;
private const int SW_RESTORE = 9;
[DllImport("User32")]
private static extern int SetForegroundWindow(int hwnd);
[DllImport("User32")]
private static extern int ShowWindow(int hwnd, int nCmdShow);
[DllImport("User32")]
private static extern int IsWindowVisible(int hwnd);
// [DllImport("User32")]
// private static extern int GetWindowText(int hwnd,
// System.Text.StringBuilder lpString, int cch);
//
// [DllImport("User32")]
// private static extern int EnumWindows(CallBack lpEnumFunc,
// int lParam);
private int hWND;
// private string mvarWindowTitle;
//
// //The Window Title, may be a partial window Title.
// //It is used to locate the window when we want to
// //Show or hide the window
// public string WindowTitle
// {
// get
// {
// if ( mvarWindowTitle == "" )
// {
// MessageBox.Show ("The window title string is missing.",
// "Unknown Window Title",
// MessageBoxButtons.OK, MessageBoxIcon.Error);
// }
// return mvarWindowTitle;
// }
// set
// {
// mvarWindowTitle = value;
// }
// }
public ExternalApplication()
{
//
// TODO: Add constructor logic here
//
}
public ExternalApplication(string FileName)
{
//
// TODO: Add constructor logic here
//
//Initalise the Process passing it the Application Name
//or a file name associated with the application
this.StartInfo.FileName = FileName;
}
public ExternalApplication(string FileName,
ProcessWindowStyle WindowStyle)
{
//
// TODO: Add constructor logic here
//
//Instantiate the application, using the specified Window Style
this.StartInfo.FileName = FileName;
StartTheApplication(WindowStyle);
}
public void Start(ProcessWindowStyle WindowStyle)
{
if ( this.StartInfo.FileName != "" )
{
//Instantiate the application, using the specified
//Window Style
StartTheApplication(WindowStyle);
}
else
{
MessageBox.Show ("The name of the application," +
" or a valid file type for the application is missing.",
"Unknown External Application",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
public void Start(string FileName, ProcessWindowStyle WindowStyle)
{
//Instantiate the application, using the specified Window Style
this.StartInfo.FileName = FileName;
StartTheApplication(WindowStyle);
}
private void StartTheApplication(ProcessWindowStyle WindowStyle)
{
if ( WindowStyle == ProcessWindowStyle.Hidden )
{
//Starting the application with the Window
//Style set to Hidden will return a mainWindow Handle of 0
//So we set the Window Style to Minimised
this.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
this.StartInfo.CreateNoWindow = true;
this.Start();
this.WaitForInputIdle();
//By starting the application with the window minimised
//I am able to obtain a handle to the window
hWND = (int)this.MainWindowHandle;
//We can then set the Widnow hidden, using the
//MainWindow Handle
this.HideApplication();
}
else
{
this.StartInfo.WindowStyle = WindowStyle;
this.StartInfo.CreateNoWindow = true;
this.Start();
this.WaitForInputIdle();
hWND = (int)this.MainWindowHandle;
}
}
public void HideApplication()
{
// Hide the window
//CallBack EnumWindowProcCallback = new CallBack(
//this.EnumWindowProc);
//EnumWindows(EnumWindowProcCallback, 0);
if ( IsWindowVisible( this.hWND ) != 0 )
{
//Hide the window if it is not already hidden
ShowWindow( this.hWND, SW_HIDE );
}
}
public void ShowApplication()
{
// Restore the window
//CallBack EnumWindowProcCallback= new CallBack(
//this.EnumWindowProc);
//EnumWindows(EnumWindowProcCallback, 0);
if ( IsWindowVisible( this.hWND ) == 0 )
{
//Show the window, if it not already displaying
ShowWindow( this.hWND, SW_RESTORE );
//Make sure the window is the top window
SetForegroundWindow(this.hWND);
}
}
// // Callback routine that is called with the handle
// // of all the windows found.
// //
// private bool EnumWindowProc(int hwnd, int parm)
// {
//
// // Buffer to store the title of the window found
// System.Text.StringBuilder windowTitle =
// new System.Text.StringBuilder(2050);
//
// // Get the window title bar text
// GetWindowText(hwnd, windowTitle, 2048);
//
// // If the window title is empty ignore it,
// if ( windowTitle.Length < 1 )
// {
// return true;
// }
//
// string WindowTitelResult = windowTitle.ToString().Trim();
//
// System.Text.RegularExpressions.Regex GRE =
// new System.Text.RegularExpressions.Regex(WindowTitle);
// System.Text.RegularExpressions.MatchCollection Result;
// Result = GRE.Matches(WindowTitelResult);
// string MatchString = "";
// if ( Result.Count > 0 )
// {
// MatchString = Result[0].Value;
// }
//
// if (MatchString == WindowTitle)
// {
// hWND = hwnd;
// return false;
// }
// return true;
// }
}
}
Changes
- 31/01/2003 - I have added funtionality to this application. It is now possible to restart the child application (Outlook Express, in this case), when the child application has been closed down outside of the control of the Shoe_Tray application. To achieve this I made the following changes to the Show and Hide Methods.
public void HideApplication()
{
//Find Out if the Application is actually loaded,
//if it is we get it's current hWnd
//CallBack EnumWindowProcCallback = new CallBack(
//this.EnumWindowProc);
//EnumWindows(EnumWindowProcCallback, 0);
//If the application has been closed external to
//this application
//hWnd will be the old hWnd and the windowTitle
//will not be found, so we will
//attempt to restart the application
//Otherwise we will set the CurrentWindowTitle
string windowTitle = GetWindowTitle( this.hWND );
if (windowTitle == "")
{
StartTheApplication(this.WindowStyle);
}
else
{
CurrentWindowTitle = windowTitle;
//Then we will check to see if the applications
//window is InVisible
//If it's not we will make it so
if ( IsWindowVisible( this.hWND ) != 0 )
{
//Hide the window if it is not already hidden
int rtn = ShowWindow( this.hWND, SW_HIDE );
}
}
}
public void ShowApplication()
{
//Find Out if the Application is actually loaded,
//if it is we get it's current hWnd
//CallBack EnumWindowProcCallback = new CallBack(
//this.EnumWindowProc);
//EnumWindows(EnumWindowProcCallback, 0);
//If the application has been closed external
//to this application
//hWnd will be the old hWnd and the windowTitle
//will not be found, so we will
//attempt to restart the application
//Otherwise we will set the CurrentWindowTitle
string windowTitle = GetWindowTitle( this.hWND );
if (windowTitle == "")
{
StartTheApplication(this.WindowStyle);
}
else
{
CurrentWindowTitle = windowTitle;
//Then we will check to see if the applications
//window is Visible
//If it's not we will make it so
if ( IsWindowVisible( this.hWND ) == 0 )
{
//Show the window, if it not already displaying
int rtn = ShowWindow( this.hWND, SW_RESTORE );
}
//Make sure the window is the top window
SetForegroundWindow(this.hWND);
}
Added the following properties.
// The main form
public Form CallingForm
{
get
{
return mvarCallingForm;
}
set
{
mvarCallingForm = value;
}
}
//The message Form
public Form MessageForm
{
get
{
return mvarMessageForm;
}
set
{
mvarMessageForm = value;
}
}
And changed the Start
Method as below
private void StartTheApplication(ProcessWindowStyle WindowStyle)
{
if ( WindowStyle == ProcessWindowStyle.Hidden )
{
//Starting the application with the Window Style set
//to Hidden will return a mainWindow Handle of 0
//So we set the Window Style to Minimised
this.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
this.StartInfo.CreateNoWindow = true;
this.Start();
if (ReStarting)
{
//By displaying a message we can ensure
//that Input is idle when we reatart after
//a premature closedown of the Called Application.
//Otherwise the wrong hWnd is returned
MessageForm.ShowDialog(CallingForm);
}
this.WaitForInputIdle();
//By starting the application with the window minimised
//I am able to obtain a handle to the window
this.hWND = (int)this.MainWindowHandle;
//Instead of getting the current hWnd, we now Set the
//variable CurrentWindowTitle - I use this later on
CurrentWindowTitle = mvarWindowTitle;
//We can then set the Widnow hidden, using the MainWindow
//Handle
this.HideApplication();
}
else
{
this.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
this.StartInfo.CreateNoWindow = true;
this.Start();
if (ReStarting)
{
//By displaying a message we can ensure that
//Input is idle when we reatart after
//a premature closedown of the Called Application.
//Otherwise the wrong hWnd is returned
MessageForm.ShowDialog(CallingForm);
}
this.WaitForInputIdle();
this.hWND = (int)this.MainWindowHandle;
//Instead of getting the current hWnd, we now Set the
//variable CurrentWindowTitle - I use this later on
CurrentWindowTitle = mvarWindowTitle;
//Hide it first other wise it will not come to the front
this.HideApplication();
this.ShowApplication();
}
ReStarting = false;
}
The Message
Form not only provides useful information when the child application is restarted, but also provides a decent timeout while the reloaded child application completes loading so that this - the ExternalApplication
object can retrieve the correct hWnd
for the reloaded Child application. I have replaced the Demo Project and the Source code.