Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9289

【C#】Windows用のスクリーンショットアプリを作成してみた

$
0
0

概要

皆様は、Windowsでスクリーンショット(以下、スクショ)を撮るときに何を使いますか?
デフォルトで入ってる機能として、主に以下のものがあります。

Print Screen
すべてのディスプレイに対して、一枚の画像としてスクショします。
ペイントなどに貼り付けて保存する必要があります。

Alt + Print Screen
アクティブなウィンドウに対してスクショします。
ペイントなどに貼り付けて保存する必要があります。

Win + Print Screen
すべてのディスプレイに対して、一枚の画像としてスクショします。
自動で保存され、デフォルトでは「C:\Users\Username\Pictures\Screenshots」に保存されます。

Win + Alt + Print Screen
アクティブなウィンドウに対してスクショします。
自動で保存され、デフォルトでは「C:\Users\Username\Videos\Captures」に保存されます。

使い勝手のいいのは、下2つでしょうか。
しかし、キーが押しづらく、使い勝手もいまいちだったので、自分で同様以上のことができるアプリ作ろうと思いました。

最後に配布URLを貼っています。気になった方は是非使ってみてください。

仕様

まず、キモとなる技術的な裏付けとして、フォーム外のキー入力を感知できるかを調べました。
できることが分かったので、作れる確信を持ちました。

メインの仕様として、作る前に以下を決めました。

  • キー1つでスクショが撮れる
  • スクショするディスプレイまたはアクティブウィンドウを選択できる
  • 自動で保存する
  • タスクトレイ表示し、常駐アプリとする

サブの仕様として、作りながら以下を決めました。

  • 保存先のフォルダを設定できる
  • スクショした後、ペイントで開くように設定できる
  • スクショをするキーを設定できる
  • 保存する拡張子を設定できる
  • 保存するファイル名のテンプレートを設定できる

シャッターアイコン5 (2).png
アイコンは、こちらのフリー素材を使用しました。

20200615_225814.png
タスクトレイの表示

20200615_225910.png
メニューとツールチップ
デフォルトでは、Pauseキーでスクショができます。
(P)は、PrimaryDisplayです。

20200615_230052.png
設定画面

プログラム

アプリは、C#(.Net)で Windows Form Application として作成しました。
以下、機能を実装するのに調べたことをまとめます。

フォーム外のキーイベントの感知

フォーム外のキーイベントを監視することを、キーロガーというそうです。
メインフォームに、Timerコントロールを追加して監視します。

privatevoidtimer1_Tick(objectsender,EventArgse){varkey_state=KeyStateBackgroundWatcher.IsKeyLocked(Keys.Pause);if(key_state){Console.WriteLine("Keys.Pause");}}// WindowsAPIのインポート[System.Runtime.InteropServices.DllImport("user32.dll",CharSet=System.Runtime.InteropServices.CharSet.Auto,CallingConvention=System.Runtime.InteropServices.CallingConvention.StdCall)]publicstaticexternshortGetKeyState(intnVirtKey);publicstaticboolIsKeyLocked(Keyskey_val){return(GetKeyState((int)key_val)&0x80)!=0;}

参考:https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q12189853917

アプリをタスクトレイのみに表示する

Program.cs
staticclassProgram{/// <summary>/// アプリケーションのメイン エントリ ポイントです。/// </summary>[STAThread]staticvoidMain(){Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);// 画面を表示せず、アプリケーションを実行します。//Application.Run(new MainForm());varform=newMainForm();Application.Run();}}

NotifyIconコントロールを追加します。メニューはこのNotifyIconに付属させます。
以下は、メニューのExitを押したときのイベントです。
アプリを終了する前に、notifyIcon1.Dispose()をすることで、タスクトレイ上にアイコンが残らないようにします。

privatevoidtsmi_exit_Click(objectsender,EventArgse){notifyIcon1.Dispose();Application.Exit();}

アクティブウィンドウのスクショを撮る

以下は、ウィンドウの影を含めないで、キャプチャする方法です。
※アクティブウィンドウは、立体感を出すためかウィンドウ周りに影が入っていて、通常はそれを含めてキャプチャしてしまいます。

privatestaticBitmapCaptureActiveWindow(){//アクティブなウィンドウのデバイスコンテキストを取得IntPtrhWnd=NativeMethods.GetForegroundWindow();IntPtrwinDC=NativeMethods.GetWindowDC(hWnd);//ウィンドウの大きさを取得NativeMethods.RECTwinRect=newNativeMethods.RECT();NativeMethods.DwmGetWindowAttribute(hWnd,NativeMethods.DWMWA_EXTENDED_FRAME_BOUNDS,outvarbounds,Marshal.SizeOf(typeof(NativeMethods.RECT)));NativeMethods.GetWindowRect(hWnd,refwinRect);//Bitmapの作成varoffsetX=bounds.left-winRect.left;varoffsetY=bounds.top-winRect.top;Bitmapbmp=newBitmap(bounds.right-bounds.left,bounds.bottom-bounds.top);//Graphicsの作成using(varg=Graphics.FromImage(bmp)){//Graphicsのデバイスコンテキストを取得IntPtrhDC=g.GetHdc();//Bitmapに画像をコピーするConsole.WriteLine(winRect);NativeMethods.BitBlt(hDC,0,0,bmp.Width,bmp.Height,winDC,offsetX,offsetY,NativeMethods.SRCCOPY);//解放g.ReleaseHdc(hDC);}NativeMethods.ReleaseDC(hWnd,winDC);returnbmp;}privatestaticvoidscreenShot_Active(){using(varbmp=CaptureActiveWindow()){bmp.Save(@"hoge\image.png",ImageFormat.Png);}}
NativeMethods.cs
classNativeMethods{publicconstintSRCCOPY=13369376;publicconstintDWMWA_EXTENDED_FRAME_BOUNDS=9;[DllImport("user32.dll")]publicstaticexternIntPtrGetDC(IntPtrhwnd);[DllImport("gdi32.dll")]publicstaticexternintBitBlt(IntPtrhDestDC,intx,inty,intnWidth,intnHeight,IntPtrhSrcDC,intxSrc,intySrc,intdwRop);[DllImport("user32.dll")]publicstaticexternIntPtrReleaseDC(IntPtrhwnd,IntPtrhdc);[StructLayout(LayoutKind.Sequential)]publicstructRECT{publicintleft;publicinttop;publicintright;publicintbottom;}[DllImport("user32.dll")]publicstaticexternIntPtrGetWindowDC(IntPtrhwnd);[DllImport("user32.dll")]publicstaticexternIntPtrGetForegroundWindow();[DllImport("user32.dll")]publicstaticexternintGetWindowRect(IntPtrhwnd,refRECTlpRect);[DllImport("dwmapi.dll")]publicstaticexternintDwmGetWindowAttribute(IntPtrhwnd,intdwAttribute,outRECTpvAttribute,intcbAttribute);}

参考:https://teratail.com/questions/103093

すべてのディスプレイを1枚スクショとして撮る

privatestaticstringscreenShot_All(){intleft=SystemInformation.VirtualScreen.Left;inttop=SystemInformation.VirtualScreen.Top;intwidth=SystemInformation.VirtualScreen.Width;inthight=SystemInformation.VirtualScreen.Height;Rectanglerect=newRectangle(left,top,width,hight);using(varbmp=newBitmap(rect.Width,rect.Height,PixelFormat.Format32bppArgb)){using(varg=Graphics.FromImage(bmp)){g.CopyFromScreen(rect.X,rect.Y,0,0,rect.Size,CopyPixelOperation.SourceCopy);}bmp.Save(@"hoge\image.png",ImageFormat.Png);}}

あとは、設定の読み込み処理や保存処理などをちょこちょこ書けば終わりです。

バグ

アクティブウィンドウを取得する場合、対象がブラウザだとうまく取得できません。
以下に、(推測みたいですが)理由がありました。
Unity×Windowsの画面キャプチャで四苦八苦してる話(1)

user32.dllを使用している場合、うまく取得できないウィンドウがあるみたいですね...:cold_sweat:

配布

以下で配布しています。(配布は、予告なく終了する可能性があります)
zipを解凍したら、フォルダごとどこかに置いて、ScreenShotApp.exeを起動してください。

https://github.com/nemutas/ScreenShotApp/releases


Viewing all articles
Browse latest Browse all 9289

Trending Articles