質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.35%

  • C#

    4765questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

  • Windows

    1011questions

    Windowsは、マイクロソフト社が開発したオペレーティングシステムです。当初は、MS-DOSに変わるOSとして開発されました。 GUIを採用し、主にインテル系のCPUを搭載したコンピューターで動作します。Windows系OSのシェアは、90%を超えるといわれています。 パソコン用以外に、POSシステムやスマートフォンなどの携帯端末用、サーバ用のOSもあります。

  • Win32 API

    151questions

    Win32 APIはMicrosoft Windowsの32bitプロセッサのOSで動作するAPIです。

アクティブウィンドウが正しくキャプチャされない

解決済

回答 1

投稿 2017/12/03 14:31

  • 評価
  • クリップ 0
  • VIEW 145

ab03

score 1

前提・実現したいこと

Visual Studio 2017を用いてC#でWindowsフォームアプリケーションを作成しています。

以下のサイトの「Win32 APIを使用する方法」を参考にしながらアクティブウィンドウのスクリーンショットを撮り保存する機能を実装しました。

http://dobon.net/vb/dotnet/graphics/screencapture.html

私の理想はこの通りです。
理想SS

しかし保存された画像は私の想像していた画像とは少し違いました。

発生している問題・エラーメッセージ

保存された画像は以下になります。
テストSS

ウィンドウの左右と下の部分にアクティブウィンドウの後ろ側の壁紙(紫色の部分)が表示されてしまいます。
私はこれを解決する方法を調べたのですがお恥ずかしながら私では解決できませんでした。

該当のソースコード

private void screenshot()
{ 
    Bitmap bmp;
    bmp = CaptureActiveWindow();   
    string outputPath = "./" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".png";
    bmp.Save(outputPath, System.Drawing.Imaging.ImageFormat.Png);
    bmp.Dispose();
}

private const int SRCCOPY = 13369376;
private const int CAPTUREBLT = 1073741824;

[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hwnd);

[DllImport("gdi32.dll")]
private 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")]
private static extern IntPtr ReleaseDC(IntPtr hwnd, IntPtr hdc);
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

[DllImport("user32.dll")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);

[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();

[DllImport("user32.dll")]
private static extern int GetWindowRect(IntPtr hwnd,
    ref RECT lpRect);

/// <summary>
/// アクティブなウィンドウの画像を取得する
/// </summary>
/// <returns>アクティブなウィンドウの画像</returns>
public static Bitmap CaptureActiveWindow()
{
    //アクティブなウィンドウのデバイスコンテキストを取得
    IntPtr hWnd = GetForegroundWindow();
    IntPtr winDC = GetWindowDC(hWnd);
    //ウィンドウの大きさを取得
    RECT winRect = new RECT();
    GetWindowRect(hWnd, ref winRect);
    //Bitmapの作成
    Bitmap bmp = new Bitmap(winRect.right - winRect.left,
        winRect.bottom - winRect.top);
    //Graphicsの作成
    Graphics g = Graphics.FromImage(bmp);
    //Graphicsのデバイスコンテキストを取得
    IntPtr hDC = g.GetHdc();
    //Bitmapに画像をコピーする
    int bmpWidth = bmp.Width;
    int bmpHeight = bmp.Height;
    BitBlt(hDC, 0, 0, bmpWidth, bmpHeight,
        winDC, 0, 0, SRCCOPY);
    //解放
    g.ReleaseHdc(hDC);
    g.Dispose();
    ReleaseDC(hWnd, winDC);

    return bmp;
}

試したこと

BitBlt(hDC, 0, 0, bmpWidth, bmpHeight,winDC, 0, 0, SRCCOPY);
で第2、第3引数の値を増加させると画像の左上が、bmpWidthとbmpHeightの値に対して減算すると画像の右下がトリミングされることが最初わかりました。

しかしアクティブウィンドウのサイズによって先に述べた紫色の部分の領域のサイズが変わることに気付き、固定値の加減算では上手く動作しないことがわかりました。

他の方法として最初に上げたリンク先の「.NET Framework 2.0以降で、Graphics.CopyFromScreenメソッドを使用する方法」はアクティブウィンドウのみでは無いため断念。

「Print Screenキーストロークを送信する方法」は画像を取得することが出来ず断念しました。

何卒よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+2

画像を見るに、Getting a window rectangle without the drop shadow - Articles and information on C# and .NET development topics • Cyotekのスクリーンショット結果に似ているように見えますね。
サイトの方によると、OSのバージョンを調べて、Vista以降ならDwmGetWindowAttributeを使ってDWMWA_EXTENDED_FRAME_BOUNDSを取得することでドロップシャドウなしの矩形領域を得られるそうですが、それを試してみるのはいかがでしょう。

[追記]
残念ですね、うまくいきませんでしたか...
2012年の投稿ですが、Bug in DwmGetWindowAttribute DWMWA_EXTENDED_FRAME_BOUNDS に寄せられた回答に、Windows 8ではDWMWA_EXTENDED_FRAME_BOUNDSとGetWindowRectが同じになるという報告がありました。バージョンによって挙動がころころ変わるんでしょうかね?

ご参考までに、私の環境の場合(Windows 10 バージョン1703)ですと、下記のようにしたところ、いい感じにドロップシャドウなしの画像が得られました。
ネイティブメソッド関連を別クラスにまとめたためメソッド等の頭にNativeMethodsが付いていますが、お気になさらないでください。
winDCの原点はGetWindowRectで得られる左上座標っぽかったので、DWMWA_EXTENDED_FRAME_BOUNDSで得た左上座標との差をBitBltのソース座標としましたが、それ以外は特にご質問者さんと違ったことはしていないかと思います。

        public static Bitmap CaptureActiveWindow()
        {
            //アクティブなウィンドウのデバイスコンテキストを取得
            IntPtr hWnd = NativeMethods.GetForegroundWindow();
            IntPtr winDC = NativeMethods.GetWindowDC(hWnd);
            //ウィンドウの大きさを取得
            NativeMethods.RECT winRect = new NativeMethods.RECT();
            NativeMethods.DwmGetWindowAttribute(
                hWnd,
                NativeMethods.DWMWA_EXTENDED_FRAME_BOUNDS,
                out var bounds,
                Marshal.SizeOf(typeof(NativeMethods.RECT)));
            NativeMethods.GetWindowRect(hWnd, ref winRect);
            //Bitmapの作成
            var offsetX = bounds.left - winRect.left;
            var offsetY = bounds.top - winRect.top;
            Bitmap bmp = new Bitmap(bounds.right - bounds.left,
                bounds.bottom - bounds.top);
            //Graphicsの作成
            Graphics g = Graphics.FromImage(bmp);
            //Graphicsのデバイスコンテキストを取得
            IntPtr hDC = g.GetHdc();
            //Bitmapに画像をコピーする
            int bmpWidth = bmp.Width;
            int bmpHeight = bmp.Height;
            Console.WriteLine(winRect);
            NativeMethods.BitBlt(hDC, 0, 0, bmpWidth, bmpHeight,
                winDC, offsetX, offsetY, NativeMethods.SRCCOPY);
            //解放
            g.ReleaseHdc(hDC);
            g.Dispose();
            NativeMethods.ReleaseDC(hWnd, winDC);

            return bmp;
        }

投稿 2017/12/03 15:58

編集 2017/12/04 07:38

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/03 19:33

    ご回答ありがとうございます。
    1時間ほど試してみてるのですが上手く短径領域を得ることが出来ません。

    もう少し頑張らせて頂きます。

    キャンセル

  • 2017/12/03 22:46 編集

    大変申し訳ないです。どうしても出来ません。
    環境はWindows10です。
    IntPtr hWnd = GetForegroundWindow();
    RECT winRect = new RECT();
    DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, out winRect, Marshal.SizeOf(winRect));
    Bitmap bmp = new Bitmap(winRect.right - winRect.left, winRect.bottom - winRect.top);
    で生成しているはずなのですが値が変わらず…

    今日は一旦諦めてみます。
    解答ありがとうございました。

    キャンセル

  • 2017/12/05 23:11

    追記のご回答ありがとうございます!

    Bongo様のメソッドをコピペしたところ動作致しました…
    今から原因を究明したいと思います。
    取り急ぎ動作した旨をご報告させていただきます。ありがとうございました!

    キャンセル

  • 2017/12/05 23:31

    原因がわかりました。
    DwmGetWindowAttribute(
    hWnd,
    DWMWA_EXTENDED_FRAME_BOUNDS,
    out winRect, // ←ここが原因!
    Marshal.SizeOf(winRect)
    );
    DwmGetWindowAttributeでせっかく影無しの座標を取得したにも関わらずその後
    GetWindowRect(hWnd, ref winRect);
    でwinRectを上書きしていたというとても恥ずかしいミスでした。

    プログラム自体は初めてでは無いのですがC#(.NET)は初めてだったのでずっと「私がC#についてまだ理解しきれていないんだ!」と思い込んでいてこのような単純なミスを見逃しておりました…

    煮詰めすぎず頭を一度リフレッシュする必要性を知り、大変良い勉強になりました。
    重ねてですがお付き合いくださり本当にありがとうございました!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

ただいまの回答率

91.35%

関連した質問

同じタグがついた質問を見る

  • C#

    4765questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

  • Windows

    1011questions

    Windowsは、マイクロソフト社が開発したオペレーティングシステムです。当初は、MS-DOSに変わるOSとして開発されました。 GUIを採用し、主にインテル系のCPUを搭載したコンピューターで動作します。Windows系OSのシェアは、90%を超えるといわれています。 パソコン用以外に、POSシステムやスマートフォンなどの携帯端末用、サーバ用のOSもあります。

  • Win32 API

    151questions

    Win32 APIはMicrosoft Windowsの32bitプロセッサのOSで動作するAPIです。