WinFormsアプリケーション向けに「指定時間操作されなければログアウトする機能」を自作しましたので、実装方法をまとめます。
- ユーザー操作(マウス・キー)を監視
- 指定時間操作が無ければ自動的にログアウト
- IMessageFilter でグローバルに操作を監視
- ApplicationContext によるアプリ管理
業務アプリにも安心して使えるレベルを目指しました✨
0.イメージ図
1.前提条件
- Visual studio 2022 Version 17.13.6
- .Net 9
なお、すべてのソースコードを公開しています。
2.なぜ IMessageFilter を選んだのか? 🎯
Windowsアプリで「ユーザー操作の監視」を行う手段は複数ありますが、本記事では IMessageFilter を採用しました。その理由を説明します。
(1) Win32 API を使った DLL ベースのグローバルフックは危険
グローバルにマウスやキーボードの操作をフックするには、SetWindowsHookEx を使って DLL を別プロセスに挿入する方式があります。
ですが、これは以下の理由から本プロジェクトでは採用しませんでした:
- 管理者権限が必要な場合がある(UAC との相性が悪い)
- アンチウイルスソフトに引っかかる可能性がある
- アプリがクラッシュした際に OS 全体に影響するリスクがある
- コードが煩雑で保守性が下がる
つまり、業務アプリのような 安定性と信頼性が求められるシステム では不適です。
(2) すべてのコントロールにイベントハンドラを設定するのは重い
当初、以下のような実装をしていました:
foreach (Control ctrl in parent.Controls)
{
ctrl.MouseDown += OnMouseDown;
ctrl.KeyDown += OnKeyDown;
...
}
しかしこの方式では以下の課題があります:
- フォーム内のコントロールが多いと、イベントフック数が指数的に増える
- 一部の動的に追加されるコントロールが漏れる可能性がある
- ループが深くなり、起動時や画面切り替え時のパフォーマンスが落ちる
これは UI が複雑になるほど問題になっていきます。
(3) IMessageFilter のメリット
代わりに採用した IMessageFilter は以下の点で優れています:
- すべての WinForms メッセージを低コストで横取りできる
- キーボードやマウスのクリック操作を簡単に検知できる
- フォームやコントロールの構造に依存せず、一括で操作を監視できる
- 軽量で安定性も高く、商用利用にも安心
この方法で、「コントロール数が多いと重くなる」「マウス・キーボードの操作を正確に拾えない」といった課題を すべて解消 できます。
3.実装ポイント
A) IMessageFilter の導入
public class UserActivityMessageFilter : IMessageFilter
{
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case 0x0100: // WM_KEYDOWN
case 0x0201: // WM_LBUTTONDOWN
UserActivityTracker.Update();
break;
}
return false;
}
}
→ Application.AddMessageFilter(...) により一括フック。マウス移動を除外して負荷軽減。
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// ユーザーアクティビティを監視するフィルタを追加
Application.AddMessageFilter(new UserActivityMessageFilter());
var loginForm = new FormLogin();
AppContextInstance = new MyAppContext(loginForm);
Application.Run(AppContextInstance);
}
🔸この中の Application.AddMessageFilter(new UserActivityMessageFilter()) の行が、ユーザー操作の検知の要です。
B) アクティビティ監視の共通クラス
public static class UserActivityTracker
{
public static DateTime LastActionTime { get; private set; } = DateTime.Now;
public static void Update() => LastActionTime = DateTime.Now;
public static bool IsInactive(TimeSpan timeout) =>
DateTime.Now - LastActionTime > timeout;
}
C) 自動ログアウトの呼び出しをFormの基底クラスで共通化する
public partial class BaseForm : Form
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// 未操作タイマーを開始
StartInactivityTimer();
}
private void StartInactivityTimer()
{
_inactivityTimer.Interval = CheckIntervalMilliseconds;
_inactivityTimer.Tick += (_, _) => CheckTimeout();
_inactivityTimer.Start();
}
private void CheckTimeout()
{
if (UserActivityTracker.IsInactive(Timeout))
{
_inactivityTimer.Stop();
BeginInvoke(() =>
{
Program.AppContextInstance!.ShowLoginAndCloseOthers();
});
}
}
}
→ 各フォームで BaseForm を継承するだけで自動ログアウト対応。
D) 自動ログアウト処理
public class MyAppContext : ApplicationContext
{
// 新しいログインフォームを表示し、他のフォームを全て閉じる
public void ShowLoginAndCloseOthers()
{
// ログインフォームは常にNEWする
var loginForm = new FormLogin();
loginForm.Show();
foreach (Form f in Application.OpenForms.Cast<Form>().ToList())
{
// 過去のログインフォームが残っていても、NEWしたインスタンス以外は閉じる
if (f != loginForm)
f.Close();
}
}
}
4.最終構成 📦
- UserActivityMessageFilter.cs(ユーザーの操作を監視するフィルタ)
- UserActivityTracker.cs(ユーザー操作の間隔を取得)
- BaseForm.cs(すべてのフォームが継承する監視用の継承元)
- MyAppContext.cs(ログアウト処理)
5.最後に 📝
この構成は、
✅ シンプル
✅ 軽量
✅ 実務アプリでも安定
をすべて両立できることを目指しました。
現場のWinFormsアプリに自動ログアウトを追加したい場面はよくあります。
色々試した結果、IMessageFilterの採用が一番有効でした🌸