下班打卡常忘?寫一個常駐小工具,**在 Windows 關機、重啟、登出前彈窗提醒「您簽退了嗎?」**,若使用者選「否」則取消關機流程。核心技術點:Microsoft.Win32.SystemEvents.SessionEnding 事件。
using System;
using System.Windows.Forms;
using Microsoft.Win32;
namespace ShutdownReminder
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var mainForm = new MainForm();
// 訂閱 SessionEnding 事件(靜態事件,必須手動移除)
SystemEvents.SessionEnding += mainForm.OnSessionEnding;
Application.Run(mainForm);
// 程式正常結束時移除訂閱(防記憶體洩漏)
SystemEvents.SessionEnding -= mainForm.OnSessionEnding;
}
}
}
using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.Win32;
namespace ShutdownReminder
{
public partial class MainForm : Form
{
private const string MsgText = "您簽退了嗎?";
private const string MsgTitle = "下班提醒";
public MainForm()
{
InitializeComponent();
// 可選:啟動時隱藏至系統匣
this.Load += (s, e) =>
{
this.WindowState = FormWindowState.Minimized;
this.ShowInTaskbar = false;
notifyIcon.Visible = true;
};
}
///
/// 系統關機/登出前觸發
///
internal void OnSessionEnding(object sender, SessionEndingEventArgs e)
{
// 僅在關機/重啟/登出時提醒(排除其他原因)
if (e.Reason == SessionEndReasons.Logoff
|| e.Reason == SessionEndReasons.SystemShutdown)
{
DialogResult result = MessageBox.Show(
MsgText, MsgTitle,
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1,
// 關鍵:強制置頂,防止被其他視窗遮擋
(MessageBoxOptions)0x40000 /* MB_TOPMOST */);
// 使用者選「否」→ 取消關機
e.Cancel = (result == DialogResult.No);
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
// 若使用者嘗試關閉視窗,改為最小化到匣(常駐特性)
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
this.WindowState = FormWindowState.Minimized;
this.ShowInTaskbar = false;
}
base.OnFormClosing(e);
}
protected override void OnClosed(EventArgs e)
{
// 表單真正關閉時(如系統關機強制結束)移除事件
SystemEvents.SessionEnding -= OnSessionEnding;
base.OnClosed(e);
}
}
}
關機流程中,系統可能已停止建立新視窗或調整 Z-Order。若不加 `MB_TOPMOST`,`MessageBox` 可能被其他視窗遮蓋,使用者看不到提醒,導致關機被意外取消或錯過提醒。透過 P/Invoke `MessageBox` 並傳入 `MB_TOPMOST | MB_SETFOREGROUND` 最可靠,但 WinForms `MessageBox.Show` 多載不支援直接傳選項,故用 `(MessageBoxOptions)0x40000` 強制轉型(僅限 Windows)。
**是**。.NET Framework / .NET Core 執行階段本身載入 CLR、BCL、JIT 編譯器、GC 堆等基礎設施,空白 WinForms 程式啟動即佔用 10-20 MB 私有工作集。這非記憶體洩漏,而是**執行階段基礎開銷**。若極度在意體積,可考慮:
改用 `SystemEvents.PowerModeChanged` 事件:
SystemEvents.PowerModeChanged += (s, e) =>
{
if (e.Mode == PowerModes.Suspend) { /* 休眠前 */ }
else if (e.Mode == PowerModes.Resume) { /* 喚醒後 */ }
};
同樣需在結束時 `-=` 移除。
強制關機會**直接終止進程**,不發送 `SessionEnding`,也無法取消。本工具僅能攔截「正常關機/登出」流程。
Microsoft.Win32.SystemEvents.SessionEnding 是 WinForms 攔截關機/登出的標準方式。**關鍵三要素**:僅限 UI 應用、必須手動移除訂閱防洩漏、MessageBox 加 `MB_TOPMOST` 確保可見。記憶體佔用屬執行階段基礎開銷,非洩漏;若極度敏感可用 Native AOT。休眠事件請改用 `PowerModeChanged`。