專案功能
- 常駐系統匣 (NotifyIcon),主視窗隱藏不佔用工作列
- 定時器週期性掃描 IE 視窗,標題符合封鎖清單者自動關閉
- 兩種掃描演算法:EnumThreadWindows (依執行緒) 與 FindWindowEx (依類別名)
- 系統熱鍵 (Ctrl+Shift+J) 快速將前景視窗標題加入封鎖清單
- 右鍵選單顯示/隱藏主視窗、結束程式
- 關閉視窗改為最小化至系統匣,不真正退出
核心模組實作
1. 系統匣與視窗狀態管理
public partial class MainForm : Form
{
private NotifyIcon trayIcon;
private ContextMenuStrip trayMenu;
public MainForm()
{
InitializeComponent();
this.ShowInTaskbar = false;
this.Visible = false; // 啟動即隱藏
// 建立 NotifyIcon
trayIcon = new NotifyIcon
{
Icon = new Icon("app.ico"),
Visible = true,
Text = "彈出視窗殺手"
};
// 右鍵選單
trayMenu = new ContextMenuStrip();
trayMenu.Items.Add("顯示主視窗", null, (s,e)=>ShowApp());
trayMenu.Items.Add("隱藏主視窗", null, (s,e)=>HideApp());
trayMenu.Items.Add("-");
trayMenu.Items.Add("結束", null, (s,e)=>ExitApp());
trayIcon.ContextMenuStrip = trayMenu;
// 雙擊圖示切換顯示
trayIcon.DoubleClick += (s,e) =>
{
if (this.WindowState == FormWindowState.Minimized) ShowApp();
else HideApp();
};
}
void ShowApp() { this.Show(); this.WindowState = FormWindowState.Normal; }
void HideApp() { this.WindowState = FormWindowState.Minimized; this.Hide(); }
// 關閉鈕改為最小化
protected override void OnFormClosing(FormClosingEventArgs e)
{
e.Cancel = true;
HideApp();
}
void ExitApp()
{
NativeMethods.UnregisterHotKey(Handle, HOTKEY_ID);
trayIcon.Visible = false;
Application.Exit();
}
}
2. 定時掃描與關閉彈出視窗
private System.Timers.Timer scanTimer;
private List blockList = new List(); // 從 ListView 同步
private void StartScanner(int intervalMs = 3000)
{
scanTimer = new System.Timers.Timer(intervalMs);
scanTimer.Elapsed += (s,e) =>
{
scanTimer.Enabled = false;
FindAndKillPopups();
scanTimer.Enabled = true;
};
scanTimer.Start();
}
// 方法 A:EnumThreadWindows (較精確,僅掃 IE 進程)
void FindAndKillPopups()
{
foreach (Process ie in Process.GetProcessesByName("iexplore"))
{
foreach (ProcessThread t in ie.Threads)
{
NativeMethods.EnumThreadWindows(t.Id, EnumWindowCallback, IntPtr.Zero);
}
}
}
bool EnumWindowCallback(IntPtr hwnd, IntPtr lParam)
{
var sb = new StringBuilder(256);
NativeMethods.GetWindowText(hwnd, sb, sb.Capacity);
string title = sb.ToString();
if (string.IsNullOrEmpty(title)) return true;
foreach (string block in blockList)
{
if (title.StartsWith(block))
{
NativeMethods.SendMessage(hwnd, NativeMethods.WM_SYSCOMMAND, NativeMethods.SC_CLOSE, IntPtr.Zero);
break;
}
}
return true; // 繼續列舉
}
3. 更高效掃描:FindWindowEx 依類別名 "IEFrame"
void FindAndKillPopups_Fast()
{
IntPtr hParent = IntPtr.Zero, hNext = IntPtr.Zero;
const string IE_CLASS = "IEFrame";
while (true)
{
hNext = NativeMethods.FindWindowEx(hParent, hNext, IE_CLASS, IntPtr.Zero);
if (hNext == IntPtr.Zero) break;
var sb = new StringBuilder(256);
NativeMethods.GetWindowText(hNext, sb, sb.Capacity);
string title = sb.ToString();
if (!string.IsNullOrEmpty(title))
{
foreach (string block in blockList)
{
if (title.StartsWith(block))
{
NativeMethods.SendMessage(hNext, NativeMethods.WM_SYSCOMMAND, NativeMethods.SC_CLOSE, IntPtr.Zero);
break;
}
}
}
}
}
4. 系統熱鍵註冊與處理
const int HOTKEY_ID = 100;
const int WM_HOTKEY = 0x0312;
[Flags]
enum KeyModifiers { None=0, Alt=1, Control=2, Shift=4, Windows=8 }
[DllImport("user32.dll")]
static extern bool RegisterHotKey(IntPtr hWnd, int id, KeyModifiers fsModifiers, Keys vk);
[DllImport("user32.dll")]
static extern bool UnregisterHotKey(IntPtr hWnd, int id);
private void RegisterGlobalHotKey()
{
KeyModifiers mod = KeyModifiers.Control | KeyModifiers.Shift;
RegisterHotKey(Handle, HOTKEY_ID, mod, Keys.J); // Ctrl+Shift+J
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_HOTKEY && m.WParam.ToInt32() == HOTKEY_ID)
{
ProcessHotKey();
}
base.WndProc(ref m);
}
void ProcessHotKey()
{
IntPtr fgWnd = NativeMethods.GetForegroundWindow();
if (fgWnd != IntPtr.Zero)
{
var sb = new StringBuilder(256);
NativeMethods.GetWindowText(fgWnd, sb, sb.Capacity);
if (sb.Length > 0)
{
AddToBlockList(sb.ToString()); // 加入 ListView 並同步 blockList
}
}
}
5. NativeMethods P/Invoke 定義彙整
class NativeMethods
{
public const int WM_SYSCOMMAND = 0x0112;
public const int SC_CLOSE = 0xF060;
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
public delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, IntPtr lpszWindow);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
關鍵優勢
- FindWindowEx + "IEFrame" 直接過濾 IE 視窗,避免列舉所有進程/執行緒,效能顯著優於 EnumThreadWindows 版本。
- 熱鍵操作無需切換到主視窗,即時將惱人彈窗標題加入黑名單。
- 系統匣常駐不干擾工作,關閉視窗不退出保護持續運行。