概要
皆様は、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つでスクショが撮れる
- スクショするディスプレイまたはアクティブウィンドウを選択できる
- 自動で保存する
- タスクトレイ表示し、常駐アプリとする
サブの仕様として、作りながら以下を決めました。
- 保存先のフォルダを設定できる
- スクショした後、ペイントで開くように設定できる
- スクショをするキーを設定できる
- 保存する拡張子を設定できる
- 保存するファイル名のテンプレートを設定できる
アイコンは、こちらのフリー素材を使用しました。
メニューとツールチップ
デフォルトでは、Pause
キーでスクショができます。(P)
は、PrimaryDisplayです。
プログラム
アプリは、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
アプリをタスクトレイのみに表示する
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);}}
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
を使用している場合、うまく取得できないウィンドウがあるみたいですね...
配布
以下で配布しています。(配布は、予告なく終了する可能性があります)
zipを解凍したら、フォルダごとどこかに置いて、ScreenShotApp.exe
を起動してください。