Application switch
First of all, the main Form must be either shown or hidden depending on state. In addition to this, we may minimize the Form or let it return to normal state by adjusting the Form .WindowState property :
public void HideApp()
{
this.WindowState = FormWindowState.Minimized;
Hide();
}
public void ShowApp()
{
Show();
this.WindowState = FormWindowState.Normal;
}
An interesting feature is to let the user close the Form while not exiting the application itself. In order to do this, we must override the OnClosing Form class implementation :
protected override void OnClosing(CancelEventArgs e)
{
// method overidden so the form can be minimized, instead of closed
e.Cancel = true;
// let's minimize the form, and hide it
this.WindowState = FormWindowState.Minimized;
Hide();
}
Of course we must provide an explicit alternative to exit the application. That's done through the Exit option from the systray context menu, whose click event is implemented like this :
private void menu_App_Exit(object sender, System.EventArgs e)
{
NativeWIN32.UnregisterHotKey(Handle, 100);
// hide icon from the systray
notifyIcon1.Visible = false;
Application.Exit();
}
Native WIN32 Window lookup
What the application does is check all open Internet Explorer windows for their captions, and compare them against a known list of banned names. For each match, we automatically close the Internet Explorer window as if we had manually right clicked on the window border to show the sys menu, and clicked on Close.
Compared to the C++ implementation mentioned at the beginning of the article, we have to say that we are more than willing to be smarter and avoid the overhead of enumerating all open windows each 3 seconds. Why this ? I have done some performance testing, and while only 8 applications were running on my system and shown in the task bar, no less than 180 windows were actually available to me. From those 180 windows, only 2 were top-level Internet Explorer windows, and only one out of the 2 was banned.
At this point, we are in the KillPopup() implementation called once every n seconds. We need to retrieve all Internet Explorer window captions. The trouble is that there is no easy way to do this using solely .Net framework code. Namely, we can actually use the System.Diagnostics.Process methods to search for all "iexplore.exe" processes, and get the main window handle from it, but that doesn't solve the problem. Each Internet Explorer process may have many windows open, one for each thread actually, but there is no way to get the window attached to each running thread.
The first available implementation we have uses System.Diagnostics.Process to list running processes, then System.Diagnostics.ProcessThreadCollection obtained from its .Threads property, in order to get the thread Id Then we use a native WIN32 API call, namely EnumThreadWindows(DWORD threadId, WNDENUMPROC lpfn, LPARAM lParam) along with a callback, to enumerate all windows from this particular thread. Once we have a window handle, note that we use IntPtr because there is no HWND object in C#, we call again a native WIN32 API method, GetWindowText(HWND hwnd, /*out*/LPTSTR lpString, int nMaxCount) to get the window caption. Based on known banned windows, we choose to send a close method using again a native WIN32 API method call, SendMessage(HWND hWnd, int msg, int wParam, int lParam). Code goes like this (for sample purpose only, because we have a smarter implementation) :
Process[] myProcesses = Process.GetProcessesByName("IEXPLORE");
foreach(Process myProcess in myProcesses)
{
FindPopupToKill(myProcess);
}
protected void FindPopupToKill(Process p)
{
// traverse all threads from the current process and enum all windows attached to the thread
foreach (ProcessThread t in p.Threads)
{
int threadId = t.Id;
NativeWIN32.EnumThreadProc callbackProc = new NativeWIN32.EnumThreadProc(MyEnumThreadWindowsProc);
NativeWIN32.EnumThreadWindows(threadId, callbackProc, IntPtr.Zero /*lParam*/);
}
}
// callback used to enumerate Windows attached to one of the threads of the current process being traversed
bool MyEnumThreadWindowsProc(IntPtr hwnd, IntPtr lParam)
{
public const int WM_SYSCOMMAND = 0x0112;
public const int SC_CLOSE = 0xF060;
// get window caption
NativeWIN32.STRINGBUFFER sLimitedLengthWindowTitle;
NativeWIN32.GetWindowText(hwnd, out sLimitedLengthWindowTitle, 256);
String sWindowTitle = sLimitedLengthWindowTitle.szText;
if (sWindowTitle.Length==0) return true;
// find this caption in the list of banned captions
foreach (ListViewItem item in listView1.Items)
{
if ( sWindowTitle.StartsWith(item.Text) )
NativeWIN32.SendMessage(hwnd, NativeWIN32.WM_SYSCOMMAND,
NativeWIN32.SC_CLOSE,
IntPtr.Zero); // try soft kill
}
return true;
}
public class NativeWIN32
{
public delegate bool EnumThreadProc(IntPtr hwnd, IntPtr lParam);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern bool EnumThreadWindows(int threadId, EnumThreadProc pfnEnum, IntPtr lParam);
// used for an output LPCTSTR parameter on a method call
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public struct STRINGBUFFER
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)]
public string szText;
}
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int GetWindowText(IntPtr hWnd, out STRINGBUFFER ClassName, int nMaxCount);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
}
In terms of performance, this code does a pretty good job because it filters out automatically from all windows those that are from Internet Explorer processes, which is much like filtering the running applications shown in the task bar.
But we have an even simpler implementation for that. We are going to use the native WIN32 FindWindowEx(HWND hWndParent, HWND hWndNext, /*in*/LPCTSTR szClassName, /*in*/LPCTSTR szWindowTitle) method, whose interest lies in that we can call it multiple times to get all windows that match the registered window class name criteria (IEFrame for all windows open by Internet Explorer) :
protected void FindPopupToKill()
{
IntPtr hParent = IntPtr.Zero;
IntPtr hNext = IntPtr.Zero;
String sClassNameFilter = "IEFrame"; // Internet Explorer CLASSNAME of all its windows
do
{
hNext = NativeWIN32.FindWindowEx(hParent,hNext,sClassNameFilter,IntPtr.Zero);
// we've got a hwnd to play with
if ( !hNext.Equals(IntPtr.Zero) )
{
// get window caption
NativeWIN32.STRINGBUFFER sLimitedLengthWindowTitle;
NativeWIN32.GetWindowText(hNext, out sLimitedLengthWindowTitle, 256);
String sWindowTitle = sLimitedLengthWindowTitle.szText;
if (sWindowTitle.Length>0)
{
// find this caption in the list of banned captions
foreach (ListViewItem item in listView1.Items)
{
if ( sWindowTitle.StartsWith(item.Text) )
NativeWIN32.SendMessage(hNext, NativeWIN32.WM_SYSCOMMAND,
NativeWIN32.SC_CLOSE,
IntPtr.Zero); // try soft kill
}
}
}
}
while (!hNext.Equals(IntPtr.Zero));
}
public class NativeWIN32
{
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr FindWindowEx(IntPtr parent, IntPtr next, string sClassName, IntPtr sWindowTitle);
}
Registering a Windows Hotkey
Windows hot keys ease a lot the use of applications such like Popup Killer. A single registered key combination, Ctrl+Shift+J by default, allows Popup Killer when it is pressed to figure out the active window and add it to the list of banned windows. This avoids manual Adding and Editing of window names (also implemented in the Form by the way as a List view context menu).
The registered hot key can be changed on-the-fly with the system tray context menu, thanks to a one-shot modal dialog. The registered hot key is also saved in the Xml file along with the list of banned window names.
Now about implementation, again, we have to use native WIN32 API calls, namely RegisterHotkey(HWND hWnd, int id, UINT fsModifiers, UINT vkey). Code goes like this :
public void SetHotKey(Keys c, bool bCtrl, bool bShift, bool bAlt, bool bWindows)
{
m_hotkey = c;
m_ctrlhotkey = bCtrl;
m_shifthotkey = bShift;
m_althotkey = bAlt;
m_winhotkey = bWindows;
// update hotkey
NativeWIN32.KeyModifiers modifiers = NativeWIN32.KeyModifiers.None;
if (m_ctrlhotkey)
modifiers |= NativeWIN32.KeyModifiers.Control;
if (m_shifthotkey)
modifiers |= NativeWIN32.KeyModifiers.Shift;
if (m_althotkey)
modifiers |= NativeWIN32.KeyModifiers.Alt;
if (m_winhotkey)
modifiers |= NativeWIN32.KeyModifiers.Windows;
NativeWIN32.RegisterHotKey(Handle, 100, modifiers, m_hotkey); //Keys.J);
}
Using a hot key in an application requires a few steps, which are listed below :
/* ------- using HOTKEYs in a C# application -------
-- code snippet by James J Thompson --
in form load : Ctrl+Shift+J
bool success = RegisterHotKey(Handle, 100, KeyModifiers.Control | KeyModifiers.Shift, Keys.J);
in form closing :
UnregisterHotKey(Handle, 100);
handling a hot key just pressed :
protected override void WndProc( ref Message m )
{
const int WM_HOTKEY = 0x0312;
switch(m.Msg)
{
case WM_HOTKEY:
MessageBox.Show("Hotkey pressed");
ProcessHotkey();
break;
}
base.WndProc(ref m );
}
public class NativeWIN32
{
[DllImport("user32.dll", SetLastError=true)]
public static extern bool RegisterHotKey( IntPtr hWnd, // handle to window
int id, // hot key identifier
KeyModifiers fsModifiers, // key-modifier options
Keys vk // virtual-key code
);
[DllImport("user32.dll", SetLastError=true)]
public static extern bool UnregisterHotKey( IntPtr hWnd, // handle to window
int id // hot key identifier
);
[Flags()]
public enum KeyModifiers
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8
}
}
------- using HOTKEYs in a C# application ------- */
When the hot key is pressed, the workflow is as follows : we get the active window thanks to the native WIN32 API HWND GetForegroundWindow() method call, then the only thing left to do is to retrieve its caption, which is done by a call to the native WIN32 API GetWindowText(HWND hwnd, /*out*/LPTSTR lpString, int nMaxCount) method call :
protected void ProcessHotkey()
{
IntPtr hwnd = NativeWIN32.GetForegroundWindow();
if (!hwnd.Equals(IntPtr.Zero))
{
NativeWIN32.STRINGBUFFER sWindowTitle;
NativeWIN32.GetWindowText(hwnd, out sWindowTitle, 256);
if (sWindowTitle.szText.Length>0)
AddWindowTitle( sWindowTitle.szText ); // add window caption to the ListView (Form)
}
}
Stephane Rodriguez-
August 19, 2002.