C# Popup Killer

 


Closing banned windows automatically

 

Popup Killer is yet another program that automatically closes banned windows. Popup Killer works in the system tray, checks browser windows on a regular interval basis, updates banned windows declared in an Xml file, and finally provides ease of use thanks to hotkeys.

There is already a similar program developed in C++, with an article published in Code Project. But the one presented in this article is done with C#, and has a better and faster implementation for banned window lookup.

That's not exactly a new topic, and there are many such programs on the Internet. But I didn't want to miss the opportunity to show how these things can easily be built using C# :

 

Building a System tray app

Start a new Windows C# Application, and drag&drop the NotifyIcon component from the Toolbox window as shown below :


Adding a systray to your Windows C# application

 

To make sure the system tray icon and the Application .exe icon match, choose a .ico file and set it in the notifyIcon1 Appearance / Icon property. Then, edit the general properties of the project, and choose this icon as value for Application Icon.
A systray app would be a little weird if there was a visible task for it in the task bar. To avoid it, we just need to set the .ShowInTaskbar Form property to false. This can be done by changing it directly in the Form Properties Window :
	this.ShowInTaskbar = false;
The systray display is almost ready ! Of course, we don't have a context menu and are not yet able to make the application visible or hide it.

 

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();
}

 

Adding a Context menu

Adding a context menu is the same than adding a system tray, there is a context menu component awaiting in the Toolbox window. By associating it to the system tray Behavior / ContextMenu property, the context menu is popped up automatically on right click.

An interesting thing to mention at this point is that there is no other mean to popup a context menu than doing this association : although the System.Windows.Forms.ContextMenu class provides a method which is likely to do so :
public void Show(Control ctrl, Point pos);
this method cannot be used because in our particular case, the only available ctrl object is the application Form, and the context menu Show() method fails when the Form is hidden, which is exactly where we are.

Adding menu items to this menu is straight forward. Click on the context menu so it shows the real context menu instance, and start adding items or separators. Double-click on each item to add a click event handler.


Building context menus in the .Net environment

 

Once the context menu is set, the option items must be enabled or disabled depending on application state. For that purpose, the context menu always raises the associated BeforePopup event to allow us to set the .Enabled property on each menu item, or even rebuild the context menu from scratch (by code) :
private void menu_App_BeforePopup(object sender, System.EventArgs e)
{
    if ( this.WindowState == FormWindowState.Minimized )
    {
        App_Show.Enabled = true; // App_Show is a System.Windows.Forms.MenuItem object
        App_Hide.Enabled = false; // App_Hide is a System.Windows.Forms.MenuItem object
    }
    else
    {
        App_Show.Enabled = false;
        App_Hide.Enabled = true;
    }
}

 

Implementing a timer

The .Net framework Timer is much the same than a standard WIN32 timer (it doesn't even need an id). The implementation does not need threads either, which is just fine. All we do is declare a timer, assign proper settings, and attach a callback to it.
m_Timer = new System.Timers.Timer(); // explicit namespace because Timer is declared in System.Threading too
m_Timer.Elapsed += new ElapsedEventHandler(OnTimerKillPopup);
m_Timer.Interval = m_nInterval; // for instance 3000 milliseconds
m_Timer.Enabled = true; // start timer

protected void OnTimerKillPopup(Object source, ElapsedEventArgs e)
{
	m_Timer.Enabled = false; // pause the timer

	FindPopupToKill();

	m_Timer.Enabled = true;
}

 

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.

 


Home
Blog