返回列表 回复 发帖

[转帖]弹出窗口杀手

弹出窗口杀手是一个可以自动关闭IE弹出窗口的程序,它工作在系统的托盘中,按照一定的间隔来检测IE窗口,然后关闭弹出窗体。最后,还提供了用热键来杀掉弹出窗口的功能。

虽然已经有类似的用C++写的程序,但是本文讲述的是用C#来实现这些功能,并且本文所讲的方案在查找窗口上的方法要比更快一些。

这是一个崭新的话题,在Internet上我们还可以看到许多类似的程序。但是我也还是要借这个机会来讲述一些下面的技术在C#中如何实现:
l         系统托盘
l         程序切换
l         计时控件
l         查找窗口
l         系统热键

生成一个系统托盘程序
首先,产生一个新的C# Windows form程序, 将NotifyIcon控件从工具箱中拖到窗体中,如下图所示:

在C# windows form程序中添加托盘

为了保证系统托盘的图标和应用程序的图标一致,我们用一个共同的图标文件a.ico来设置系统托盘的图标和应用程序的图标。
为了使程序不显示在工具栏上,我们可以设置窗体的visible属性为false. 这个可以在窗体属性窗口中直接实现
this.ShowInTaskbar = false;

到目前为止,系统托盘已基本好了,但是我们还没有设置右键菜单,也没有使程序显示和隐藏的功能.
程序切换
首先,程序的主窗体可以根据不同的状态来选择显示或者是隐藏,除此之外,我们可以用WindowState设置窗体的状态:

public void HideApp()
{
   this.WindowState = formWindowState.Minimized;
   Hide();
}
public void ShowApp()
{
   Show();
   this.WindowState = formWindowState.Normal;
}

一个非常有趣的功能是让用户关闭窗体的时候程序并不是退出,为了实现这个功能,我们必须要重写窗体的OnClosing事件.

protected override void OnClosing(CancelEventArgs e)
{
   // 用最小化来代替关闭操作d
   e.Cancel = true;
   // 最小化,并且隐藏窗体
   this.WindowState = formWindowState.Minimized;
   Hide();
}

当然,我们必须要提供一个必须的退出方法.这个可以在托盘的右键菜单的exit中实现,

private void menu_App_Exit(object sender, System.EventArgs e)
{
   NativeWIN32.UnregisterHotKey(Handle, 100);
   //隐藏托盘
   notifyIcon1.Visible = false;
   Application.Exit();
}

添加右键菜单
添加一个右键菜单和添加托盘基本一样,从工具箱中添加context menu就可以.右键菜单在你鼠标右键按下的时候是会自动弹出的.

当设置好右键菜单以后,我们必要要根据不同的情况来启用或停用右键菜单,这个可以通过在菜单的BeforePopup设置.Enabled属性来实现.

private void menu_App_BeforePopup(object sender, System.EventArgs e)
{
   if ( this.WindowState == formWindowState.Minimized )
   {
       App_Show.Enabled = true;
       App_Hide.Enabled = false;
   }
   else
   {
       App_Show.Enabled = false;
       App_Hide.Enabled = true;
   }
}

计时工具
.Net Framework的 Timer能和系统的Win32 timer实现一样的功能.我们要做的就是设置一个timer,然后合理的设置属性.

m_Timer = new System.Timers.Timer(); // explicit namespace (Timer also in System.Threading)
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;
}

本地win32窗体查找
本程序的实现原理是这样,先检查所有的IE窗口标题,然后于已经有的列表来比较,如果有相同的,我们就关闭这个窗口.

按照上面的方法,我们每n妙使用KillPopup()来检查.比较遗憾的是我们无法使用安全代码来完成所有的工作. 我们可以使用 System.Diagnostics.Proces来检查所有的IE进程,然后得到主窗体.但是每一个IE进程可以打开好几个窗口,虽然每一个窗体都于一个进程相关,但是还没有办法来使每一个窗体于进程对应起来.
一个可行的办法使用System.Diagnostics.Process列举出所有的运行的进程,然后System.Diagnostics.ProcessThreadCollection 来得到他们的.Threads属性,为了得到thread Id,我们使用Win32 API EnumThreadWindows(DWORD threadId, WNDENUMPROC lpfn, LPARAM lParam) 来实现,这是一个回调(call back)函数,他可以列举出于进程相关的窗体. 当我们得到了窗体的句柄以后,我们可以使用另一个API函数 GetWindowText(HWND hwnd, /*out*/LPTSTR lpString, int nMaxCount)来得到窗体的标题,然后根据已经有的窗体,调用API函数SendMessage(HWND hWnd, int msg, int wParam, int lParam)来关闭窗口. 下面使演示代码

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);
}
上面的方法在性能上是不错的,因为他过滤了其他非IE的窗口.但是我们可以用一个更简单的方法来实现,就是调用API FindWindowEx(HWND hWndParent, HWND hWndNext, /*in*/LPCTSTR szClassName, /*in*/LPCTSTR szWindowTitle)方法.比较有用的是这句,我们可以使用registered window class name来找到IE窗口(IEFrame是所有打开的IE的标识).

protected void FindPopupToKill()
{
   IntPtr hParent = IntPtr.Zero;
   IntPtr hNext = IntPtr.Zero;
   String sClassNameFilter = "IEFrame"; // 所有IE窗口的类
   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);
}
代码下载: http://www.codeproject.com/useritems/popupkiller/popupkiller_src_update.zip
演示程序: http://www.codeproject.com/useritems/popupkiller/popupkiller_demo_update.zip
注册系统热键
系统热键用在像弹出窗口杀手这种应用程序非常有用, Ctrl+Shift+J是缺省热键.
说道实现,我们继续用RegisterHotkey(HWND hWnd, int id, UINT fsModifiers, UINT vkey). 完成,代码如下:
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 HOTKEYs in a C# application -------
  -- code snippet by James J Thompson --
在form的load 中 : Ctrl+Shift+J
        bool success = RegisterHotKey(Handle,
                                     100,
                                     KeyModifiers.Control | KeyModifiers.Shift,
                                     Keys.J);

在 form的closing中 :
        UnregisterHotKey(Handle, 100);

如何处理热键 :
    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 ------- */
当我们按下热键以后,流程是这样:首先用HWND GetForegroundWindow()来得到窗体,然后要抓出窗体的标题, GetWindowText(HWND hwnd, /*out*/LPTSTR lpString, int nMaxCount). 具体如下:

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 to the ListView (form)
   }
}

代码下载: http://www.codeproject.com/useritems/popupkiller/popupkiller_src_update.zip
演示程序: http://www.codeproject.com/useritems/popupkiller/popupkiller_demo_update.zip

(全文完)


帖子版权所有:自由程序员协会,请转贴的朋友注意,以免引来法律纠纷!

[color=#FF00FF]『有一种天空,喜欢接近阴霾 有一种生活,不知是否存在』  『有一种希望,注定走向毁灭 有一种死亡,渐渐被人期待』  『有一种心境,分辨不清好坏 有一种爱情,不再渴望表白』  『有一种眼泪,只能流向心底 有一种悲伤,从不表现出来』
返回列表