C#で書かれたシンプルで使いやすいWindowsスクリーンショット強化ツール

C#で書かれたシンプルで使いやすいWindowsスクリーンショット強化ツール

シンプルで使いやすいWindowsスクリーンショット強化ツール

最終更新 2022/05/12 8:15
IT技术分享社区
読了目安 4 分
カテゴリ
.NET
タグ
.NET C# MySQL スクリーンショット

半年前に私は DreamScene2 をオープンソース化しました。これは小さくて速く、かつ強力なWindows用動的デスクトップソフトウェアです。多くの人に好評で、これによりオープンソースを続ける自信がつきました。これが私の2つ目のオープンソース作品 ScreenshotEx です。シンプルで使いやすいWindows用スクリーンショット拡張ツールです。

Star と Fork を歓迎します https://github.com/he55/ScreenshotEx

はじめに

Windowsのスクリーンショットショートカット PrintScreen を使用してスクリーンショットを撮る際、ファイルに保存したい場合は、まずペイントツールに貼り付けてから別名で保存する必要があります。以前はそれほど面倒だと感じていませんでしたが、macOSのスクリーンショットツールを使った後、小さなスクリーンショットツールでもこんなにシンプルで使いやすくできることを知りました。そこでmacOSのスクリーンショットツールを参考にして、Windows版を作成しました。

機能

  • スクリーンショットをデスクトップに自動保存

  • スクリーンショットプレビューをクリックすると編集可能

実装原理

システムのスクリーンショットショートカットを押した後に何か処理を行いたい場合、考えられる方法はキーボードイベントを監視することです。WIN32 API が提供する SetWindowsHookExA フック関数はこの要件を正確に満たします。idHook パラメータを WH_KEYBOARD_LL に設定すると、低レベルキーボードフックとなり、キーボードメッセージをキャプチャできます。

SetWindowsHookExA 関数の定義

HHOOK SetWindowsHookExA(
  [in] int       idHook,    // フックの種類
  [in] HOOKPROC  lpfn,      // フック処理関数
  [in] HINSTANCE hmod,      // モジュールハンドル
  [in] DWORD     dwThreadId // スレッドID
);

キーボード処理関数の定義

LRESULT CALLBACK LowLevelKeyboardProc(
  _In_ int    nCode,
  _In_ WPARAM wParam, // キーボードメッセージ
  _In_ LPARAM lParam // KBDLLHOOKSTRUCT 構造体へのポインタ
);

コード

C# PInvoke 定義

const int HC_ACTION = 0;
const int WH_KEYBOARD_LL = 13;
const int WM_KEYUP = 0x0101;
const int WM_SYSKEYUP = 0x0105;
const int VK_SNAPSHOT = 0x2C;

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct KBDLLHOOKSTRUCT
{
    public uint vkCode;
    public uint scanCode;
    public uint flags;
    public uint time;
    public UIntPtr dwExtraInfo;
}

[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate IntPtr HookProc(int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);

[DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hmod, int dwThreadId);

[DllImport("User32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);

[DllImport("User32.dll", SetLastError = false, ExactSpelling = true)]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);

[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle([Optional] string lpModuleName);

キーボードフックの登録

注意点:SetWindowsHookEx はアンマネージ関数であり、第2引数はデリゲート型です。GC はアンマネージ関数による .NET オブジェクトへの参照を記録しません。そのため、ローカル変数でデリゲートを保持するとスコープを抜けた時点で GC に解放され、SetWindowsHookEx が解放済みのデリゲートを呼び出そうとするとエラーが発生します。

SetWindowsHookEx 関数の第1引数に WH_KEYBOARD_LL(低レベルキーボードフック)を渡し、第2引数にキーボードメッセージ処理関数のデリゲートを渡し、第3引数には GetModuleHandle 関数でモジュールハンドルを取得し、第4引数には0を渡します。

HookProc _hookProc;
IntPtr _hhook;

void StartHook()
{
    _hookProc = new HookProc(LowLevelKeyboardProc); // メンバー変数でデリゲートを保持
    _hhook = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, GetModuleHandle(null), 0); // キーボードフックを登録、戻り値はフック解除時に使用。GetModuleHandle(null) で現在のモジュールハンドルを取得
}

キーボードメッセージ処理関数

キーボードメッセージ処理関数内で PrintScreen キーメッセージをキャプチャし、プレビュー表示と画像保存のロジックを実行します。

IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam)
{
    if (nCode == HC_ACTION)
    {
        if (lParam.vkCode == VK_SNAPSHOT) // PrintScreen キーメッセージをキャプチャ
        {
            if ((int)wParam == WM_KEYUP || (int)wParam == WM_SYSKEYUP) // キーを離した時に画像を保存
                SaveImage();
            else
                _previewWindow.SetHide();
        }
    }
    return CallNextHookEx(_hhook, nCode, wParam, ref lParam);
}

画像の保存

システムクリップボードから画像を取得

void SaveImage()
{
    if (Clipboard.ContainsImage())
    {
        if (!Directory.Exists(_settings.SavePath))
            Directory.CreateDirectory(_settings.SavePath);

        string ext = "png";
        ImageFormat imageFormat = ImageFormat.Png;
        switch (_settings.SaveExtension)
        {
            case 0:
                imageFormat = ImageFormat.Png;
                ext = "png";
                break;
            case 1:
                imageFormat = ImageFormat.Jpeg;
                ext = "jpg";
                break;
            case 2:
                imageFormat = ImageFormat.Bmp;
                ext = "bmp";
                break;
        }

        if (_settings.SaveName == 0)
        {
            string name = DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss");
            _saveFilePath = Path.Combine(_settings.SavePath, $"{PrefixName} {name}.{ext}");
        }
        else
        {
            do
            {
                _saveFilePath = Path.Combine(_settings.SavePath, $"{PrefixName} {_nameIndex}.{ext}");
                _nameIndex++;
            } while (File.Exists(_saveFilePath));
        }

        Image image = Clipboard.GetImage();
        image.Save(_saveFilePath, imageFormat);

        if (_settings.IsPlaySound)
            _soundPlayer.Play();

        if (_settings.IsShowPreview)
            _previewWindow.SetImage(_saveFilePath);
    }
}

完全なコード https://github.com/he55/ScreenshotEx

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2026/04/22

各OSバージョンの.NETサポート状況(250707更新)

仮想マシンとテストマシンを使用して、各OSバージョンの.NETサポート状況を確認します。OSインストール後、対応するランタイムをインストールし、Stardustエージェントを実行できることを確認します(合格条件)。

続きを読む
同じカテゴリ / 同じタグ 2026/02/07

AOTの使用経験のまとめ

プロジェクト作成当初から、新機能を追加したり新しい構文を使用したりした場合には、すぐにAOT公開テストを実施するという良い習慣を身につけるべきです。

続きを読む