C#(.NET)で他のウィンドウのクライアント領域のスクリーンショットを撮る

C#で他のウィンドウのクライアント領域内部のスクリーンショットを撮ろうとした。
.NET Frameworkは多くのクラスライブラリが揃っているから簡単にできるだろう!
Googleで探すこと数十分・・

無いっ・・!!!圧倒的無いっ・・!!!

Win32APIの呼び出しが必要

.NET Frameworkでは自身以外の他のウィンドウを制御する機能はあまりないようで、
どうしてもP/InvokeでWin32APIを叩く必要があるみたい。

できるだけこれは避けたかったのだが、他に方法が見つかりそうにないので仕方ない・・
(C#でWin32APIを今まで一度も使ったことがなかったのですごくためらった・・)

必要なWin32API関数

■ClientToScreen(IntPtr hwnd, out POINT lpPoint);

クライアント座標をスクリーン座標に変換する

  • hwnd : クライアント領域を持つウィンドウのハンドル
  • lpPoint : クライアント領域内の座標を持つ構造体。この構造体がスクリーン座標に書き換えられる。

クライアントの左上の絶対座標は、ウィンドウの位置によって異なるので
この関数を使ってクライアント場では(0, 0)(左上)の座標が
スクリーン全体ではどのような座標かという情報に変換することができる。

■GetClientRect(IntPtr hwnd, out RECT lpRect);

指定したウィンドウのクライアント領域を取得する

  • hwnd : クライアント領域を持つウィンドウのハンドル
  • lpRect : クライアント領域の上下左右の座標(クライアント座標)がこの構造体に書き込まれる。

lpRectで指定した構造体のleft, topは必ず(0, 0)となる(なぜなら、クライアント座標で代入されるから)
この関数を使ってクライアント領域の大きさ(幅と高さ)を計算できる。
その結果を使って画面全体の中で指定したウィンドウの領域がどこまでなのかを知ることができる。

■IsIconic(IntPtr hwnd);

指定したウィンドウが最小化されているかどうか調べる

  • hwnd : 調べる対象となるウィンドウハンドル

スクリーンショットを撮る場合撮りたいウィンドウが最小化されていると画像に映らないのはもちろん、
クライアント座標を取得することもできないので、最小化されている場合はもとに戻す必要がある。
そのために最小化されているかどうかを調べるこの関数を使う。

■ShowWindow(IntPtr hwnd, int nCmdShow);

指定されたウィンドウの表示状態を設定する

  • hwnd : 設定する対象のウィンドウハンドル
  • nCmdShow : 設定したい状態

ここでは最小化されているウィンドウを元に戻すために使うので
nCmdShowには9(SW_RESTORE)を入れる。

実際のコード

2クラス以上になったのでgithubに置いてみた!
(前からgithubに何か置くのが夢だったので・・・)
Windowクラスが他のウィンドウを制御するためのクラス。
NativeCallがWin32APIを呼び出している部分。
https://github.com/castaneai/WindowController

あまりに単純だけど使い方は↓のような感じ。

Window window = new Window("ProcessName"); // ProcessNameはウィンドウのタイトルではなくexeの名前
Bitmap capturedImage = window.CaptureImage();