Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# Popup Killer

0.00/5 (No votes)
25 Aug 2002 1  
C# implementation of a tool to close banned windows
In this article, you will see a program called Popup Killer that works in the system tray, checks browser windows on a regular interval basis, updates banned windows declared in an XML file, and is easy to use. This program has better and faster implementation in C# for banned window lookup.

Image 1

Closing banned windows automatically

Introduction

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.

This is 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:

Image 2

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 NotifyIcon's 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 can be done programmatically as:

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's 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 event of the 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 as adding a system tray, there is a ContextMenu component awaiting in the Toolbox window. By associating it to the system tray's 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 means 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 control object is the application Form, and the ContextMenu's 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.

Image 3

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 is a System.Windows.Forms.MenuItem object
        App_Show.Enabled = true; 
        // App_Hide is a System.Windows.Forms.MenuItem object
        App_Hide.Enabled = false; 
    }
    else
    {
        App_Show.Enabled = false;
        App_Hide.Enabled = true;
    }
}

Implementing a Timer

The .NET Framework Timer is much the same as 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.

// explicit namespace (Timer also in System.Threading)
m_Timer = new System.Timers.Timer(); 
m_Timer.Elapsed += new ElapsedEventHandler(OnTimerKillPopup);
// for instance 3000 milliseconds
m_Timer.Interval = m_nInterval; 
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 option.

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 functions. 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 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
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, interesting point of which 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"; // CLASSNAME of all IE 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 /*HWND*/, 
                                             IntPtr next /*HWND*/, 
                                             string sClassName,  
                                             IntPtr sWindowTitle);
}

Registering a Windows Hotkey

Windows hot keys ease a lot, the use of applications like Popup Killer. A single registered key combination, Ctrl+Shift+J by default, allows Popup Killer 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 ListView context menu).

The registered hot key can be changed on-the-fly with the system tray context menu, thanks to an 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;

    //Keys.J);
    NativeWIN32.RegisterHotKey(Handle, 100, modifiers, m_hotkey); 
}

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)
            // add to the ListView (Form)
            AddWindowTitle( sWindowTitle.szText ); 
    }
}

History

  • 18th August, 2002: First version

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here