Shujis1964
@Shujis1964 (Shuji Sunano)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

[C#]スクリーンキャプチャで非クライアント領域のみ重なってキャプチャされる。

解決したいこと

C#で特定のウィンドウ表示を定期的にスクリーンチャプチャーするソフトを作成しています。
Webで紹介される幾つかのプログラム例を参考に作ったのですが、問題が発生しています。

キャプチャを取るプログラムAを起動した後に、対象とするプログラムBを起動しその後プログラムAのキャプチャー開始のボタンを押すと、プログラムBのキャプチャーされたウィンドウタイトルバーの部分にプログラムAの表示の一部が重なってキャプチャーされます。

この現象は、プログラムAをフォアグランドに設定してからキャプチャーすれば無くなるのですが、プログラムAを最前面にしたくない事情があり、出来れば他のウィンドウとの重なり具合(Zオーダー)をなるべく崩さずに
キャプチャーをしたいと思っています。

キャプチャーのコードを書くのは今回が初めてで、イメージの操作、、ウィンドウの描画の仕組みが理解できていないからかも知れませんが、改善方法をご教授頂きたいです。
よろしくお願いします。

発生している問題・エラー

Form1がキャプチャするソフト
TestBenchUIAutomationがキャプチャの対象
まず、Form1を起動した後に対象を起動したときの重なり。
20210808_150508[Greenshot].png
ここでForm1をクリックして重なりを逆にする。
20210808_150551[Greenshot].png
falseボタンをクリックすると、timerイベントが発生すしキャプチャーを始める。
Form1上にキャプチャーした画像が張り付くが、タイトル部分のみ重なって切れている。最初の重なり方によっては、Form1の一部も表示される。
20210808_150647[Greenshot].png
キャプチャーが定期的に実行されていることを確認するために、対象のTestBenchUIAutomationに
何か書き込むとクライアント領域は更新されるが、タイトル部分は重なったまま。
20210808_150746[Greenshot].png
その他、プログラムB(TestBenchUIAutomation)のウィンドウサイズを変更すると、キャプチャー画面がもっと崩れる。
20210808_160914[Greenshot].png

該当するソースコード

    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をキャプチャー前に対象のウィンドウに送信してみたが、変化無し。

0

1Answer

Comments

  1. @Shujis1964

    Questioner

    回答ありがとうございます。
    リンクのページは見ていたのですが、どうにも知識不足で理解が追いついていません。
    取りあえず、GitHubからサンプルをダウンロードしてそのまま走らせてみました。
    このサンプルでは、確かに該当する問題は発生していないようです。
    ただ、実際に自分のプログラムにどうやって実装すれば良いのかが理解できず、と言った状況です。
    あと、もう一つ問題があって、「windows10から新設された新しいウィンドウキャプチャAPI」と言うことで、今回試したのはwindows10マシンでしたが、現在作成中のプログラムが実際に動かそうとしているマシンがwindows8系でして、これ当然動かないですよね。
    100台近くある計測制御用のPCで、使っているソフトの関係で簡単にはOSのバージョンが上げられない的な問題が有るのです。
    新設したAPI以前のものを使うとそういった問題が起きるのが既知であるのならば、この場合改善策はないってこのになるのでしょうか?
  2. 紹介したAPIは確かに8では使えませんね…
    https://docs.microsoft.com/en-us/windows/win32/dwm/thumbnail-ovw
    調べるとDWM ThumbnailというAPIがvistaから提供されているので、これなら使えそうです
    描画先がトップレベルウィンドウでなければいけないなどちょっと使いにくそうですが
  3. @Shujis1964

    Questioner

    DWM(デスクトップ ウィンドウ マネージャー)というのが有るんですね。
    資料を見ましたが、こちらは元々動画用のものなのでしょうか?
    で改めてDWMとキャプチャをキーワードで検索してみると、隠れたウィンドウをキャプチャする 試みをした方の紹介が幾つか出て来ましたので、もう少し調べてみます。
    情報ありがとう御座います。

Your answer might help someone💌