[C#]スクリーンキャプチャで非クライアント領域のみ重なってキャプチャされる。
解決したいこと
C#で特定のウィンドウ表示を定期的にスクリーンチャプチャーするソフトを作成しています。
Webで紹介される幾つかのプログラム例を参考に作ったのですが、問題が発生しています。
キャプチャを取るプログラムAを起動した後に、対象とするプログラムBを起動しその後プログラムAのキャプチャー開始のボタンを押すと、プログラムBのキャプチャーされたウィンドウタイトルバーの部分にプログラムAの表示の一部が重なってキャプチャーされます。
この現象は、プログラムAをフォアグランドに設定してからキャプチャーすれば無くなるのですが、プログラムAを最前面にしたくない事情があり、出来れば他のウィンドウとの重なり具合(Zオーダー)をなるべく崩さずに
キャプチャーをしたいと思っています。
キャプチャーのコードを書くのは今回が初めてで、イメージの操作、、ウィンドウの描画の仕組みが理解できていないからかも知れませんが、改善方法をご教授頂きたいです。
よろしくお願いします。
発生している問題・エラー
Form1がキャプチャするソフト
TestBenchUIAutomationがキャプチャの対象
まず、Form1を起動した後に対象を起動したときの重なり。
ここでForm1をクリックして重なりを逆にする。
falseボタンをクリックすると、timerイベントが発生すしキャプチャーを始める。
Form1上にキャプチャーした画像が張り付くが、タイトル部分のみ重なって切れている。最初の重なり方によっては、Form1の一部も表示される。
キャプチャーが定期的に実行されていることを確認するために、対象のTestBenchUIAutomationに
何か書き込むとクライアント領域は更新されるが、タイトル部分は重なったまま。
その他、プログラムB(TestBenchUIAutomation)のウィンドウサイズを変更すると、キャプチャー画面がもっと崩れる。
該当するソースコード
public partial class Form1 : Form
{
private const int SRCCOPY = 13369376;
private const int DWMWA_EXTENDED_FRAME_BOUNDS = 9;
[StructLayout(LayoutKind.Sequential)]
private struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
static extern IntPtr GetWindowRect(IntPtr hWnd, out Rect rect);
[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("user32.dll")]
extern static IntPtr GetForegroundWindow();
[DllImport("dwmapi.dll")]
extern static int DwmGetWindowAttribute(IntPtr hWnd, int dwAttribute, out Rect rect, int cbAttribute);
[DllImport("user32.dll")]
static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("gdi32.dll")]
static extern int BitBlt(IntPtr hDestDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);
[DllImport("user32.dll")]
static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hdc);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetForegroundWindow(IntPtr hWnd);
IntPtr hWnd;
public Form1()
{
InitializeComponent();
this.AutoScroll = true;
button2.Text = "false";
}
private void Button1_Click(object sender, EventArgs e)
{
if (timer1.Enabled)
{
timer1.Enabled = false;
button2.Text = "false";
}
else
{
// キャプチャの対象とするwindowのハンドルを取得する。
System.Diagnostics.Process[] TargetP = GetMainProcess("TestBench");
if (TargetP.Length != 1) return;
hWnd = TargetP[0].MainWindowHandle;
timer1.Enabled = true;
button2.Text = "true";
}
}
public Bitmap CaptureWindow(IntPtr hWnd)
{
// SetForegroundWindow(hWnd);
// 対象のウィンドウをフォアグランドにしてからCaptureすれば問題は起こらないが、
// 別のウィンドウの表示が隠れてしまうので、出来ればフォアグランドにしたくない。
Rect bounds, rect;
IntPtr winDC = GetWindowDC(hWnd);
DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, out bounds, Marshal.SizeOf(typeof(Rect)));
GetWindowRect(hWnd, out rect);
var offsetX = bounds.left - rect.left;
var offsetY = bounds.top - rect.top;
Bitmap bitmap = new Bitmap(bounds.right - bounds.left, bounds.bottom - bounds.top);
Graphics graphics = Graphics.FromImage(bitmap);
IntPtr hDC = graphics.GetHdc();
BitBlt(hDC, 0, 0, bitmap.Width, bitmap.Height, winDC, offsetX, offsetY, SRCCOPY);
graphics.ReleaseHdc(hDC);
graphics.Dispose();
ReleaseDC(hWnd, winDC);
return bitmap;
}
private void timer1_Tick(object sender, EventArgs e)
{
pictureBox1.Image = CaptureWindow(hWnd);
}
public static System.Diagnostics.Process[] GetMainProcess(string TargetTitle)
{
List<System.Diagnostics.Process> TargetP = new List<System.Diagnostics.Process>();
/* 現在稼働中のプロセスを取得 */
foreach (System.Diagnostics.Process p in System.Diagnostics.Process.GetProcesses())
{
if (p.ProcessName.Contains(TargetTitle))
{
TargetP.Add(p);
}
}
System.Diagnostics.Process[] ret = new System.Diagnostics.Process[TargetP.Count];
int i = 0;
foreach (System.Diagnostics.Process p in TargetP)
{
ret[i] = p;
i++;
}
return ret;
}
}
自分で試したこと
プログラムAをフォアグランドに設定するコードを追記すれば重なりは無くなります。
なので、一度フォアグランドにしてからもとの位置に戻すことを考えたが、こちらも
一つ前のウィンドウが旨く取得できない状態(別の問題があり)。
対象を再描画するWM_NCPAINTをキャプチャー前に対象のウィンドウに送信してみたが、変化無し。