WindowsでPrintScreenを押すとスクリーンショットがクリップボードへコピーされますね。
Altキーを押したままPrintScreenを押すと、現在のウィンドウのみのスクリーンショットが取れます。
※ノートPCなんかではFnキーを押さないとPrintScreenとして動作しないものがあります。
このPrintScreenを押した時の動作をプログラミングにより自分で色々と変更したいと思った時にやった事をちょっと(ちょっとじゃないかも)書いてみたいと思います。
VC++でWinAPIを使ったやり方になります。
まず最初に思いついた事は、グローバルフックでPrintScreenキーのメッセージを横取りする事。
ただ、今回の場合はホットキーで十分事足りるので扱いが簡単なホットキーを使う事にしました。
ホットキーの登録はこんな感じ・・・
// PrintScreenを押した時
::RegisterHotKey(hWnd, 10, 0, VK_SNAPSHOT);
// Alt+PrintScreenを押した時
::RegisterHotKey(hWnd, 11, MOD_ALT, VK_SNAPSHOT);
※RegisterHotKeyで登録したホットキーは使わなくなったら必ずUnregisterHotKeyで解除を行います。
こんな感じでね・・・
::UnregisterHotKey(hWnd, 10);
::UnregisterHotKey(hWnd, 11);
こうする事で指定したウィンドウにそのPrintScreen(Alt+PrintScreen)が押されたらWM_HOTKEYという
ウィンドウメッセージが飛んできますのでそいつを捕まえてコードを書きます。
WM_HOTKEYでは、RegisterHotKeyで指定したホットキーの識別子がWPARAMに入ってきます。
上記の例でいくと、PrintScreenでは10、Alt+PrintScreenでは11を指定しています。
まぁ、この値は#defineしといた方がいいですが例なので直接書いてます。
なので判定としては
if(wParam == 10){
// PrintScreenが押された時の処理
}else if(wParam == 11){
// Alt+PrintScreenが押された時の処理
}
みたいに書けます。
じゃあPrintScreenによるスクリーンショットを無効化するか!
と思った場合、クリップボードをクリアしてあげればいいと思ってましたが、
どうやらホットキーとしてキーのイベントを奪ってしまうとそもそもスクリーンショットが
クリップボードへコピーされなくなっていました。
つまり、PrintScreenキーをホットキーとして登録しただけで、PrintScreenキーによる
スクリーンショットが無効化されてしまいました・・・
じゃあPrintScreenキーを押した時ログを残してスクリーンショットは通常通りやりたい!
・・・となった時、どうすればよいか?
スクリーンショットの処理を自分で書くしかないですね。
コードがアレですが、こんな感じで関数化してみました。
// スクリーンショットを取る
bool ScreenShot(HWND hWnd)
{
// スクリーンサイズ
int x = ::GetSystemMetrics(SM_CXSCREEN);
int y = ::GetSystemMetrics(SM_CYSCREEN);
int x1 = 0, y1 = 0;
if(hWnd != NULL){
// Alt+PrintScreenだったら作業中のウィンドウのみを取る
WINDOWINFO wi;
::ZeroMemory(&wi, sizeof(wi)); // ねんのため
wi.cbSize = sizeof(WINDOWINFO);
if(!::GetWindowInfo(hWnd, &wi)) return false;
x = wi.rcWindow.right - wi.rcWindow.left;
y = wi.rcWindow.bottom - wi.rcWindow.top;
x1 = wi.rcWindow.left;
y1 = wi.rcWindow.top;
}
HDC hdc = ::GetDC(NULL);
if(hdc == NULL) return false;
HBITMAP hBitmap = ::CreateCompatibleBitmap(hdc, x, y);
// エラー判定用
bool bSuccess = (hBitmap != NULL);
// ここから先は成功したかを判定しながら進む。
// メモリデバイスコンテキストを作成
HDC hdcMem;
if(bSuccess){
hdcMem = ::CreateCompatibleDC(hdc);
bSuccess = (hdcMem != NULL);
}
// メモリデバイスコンテキストを選択
HGDIOBJ hgdiobj;
if(bSuccess){
bSuccess = ((hgdiobj=::SelectObject(hdcMem, hBitmap)) != NULL);
}
// メモリデバイスコンテキストへビットマップを転送
if(bSuccess){
bSuccess = (::BitBlt(hdcMem,0,0,x,y,hdc,x1,y1,SRCCOPY) != 0);
}
// クリップボードへコピー
if(bSuccess && ::OpenClipboard(NULL)){
bSuccess = (::EmptyClipboard() != 0);
if(bSuccess) bSuccess = (::SetClipboardData(CF_BITMAP, hBitmap) != NULL);
::CloseClipboard();
}else bSuccess = false;
// 後片付け
if(hgdiobj != NULL) ::SelectObject(hdcMem, hgdiobj);
if(hdcMem != NULL) ::DeleteDC(hdcMem);
if(hBitmap != NULL) ::DeleteObject(hBitmap);
if(hdc != NULL) ::ReleaseDC(hWnd, hdc);
return bSuccess;
}
引数のhWndは見てのとおりスクリーンショットを取りたいウィンドウを指定します。
NULLを指定すると画面全体となります。
現在のウィンドウを取得するのにGetForegroundWindow()で取得したウィンドウを
使う事を想定してますが、問題ないすかね?
あと、戻り値はtrueが成功、falseが失敗です。
WinAPIと言っておきながらC言語ではなくC++の文法で書いてます。
C言語で書く場合は、::を外してboolをBOOL、trueをTRUE、falseをFALSEにして変数の定義をとりあえず関数の先頭に持ってくればいいんじゃないすかね。(適当)
実際に動かしたものからちょっと変更してますので
コンパイルが通らない時やうまく動かない時は自分で治してくださいw
間違いを指摘してもらえればありがたく修正しますw
また、これはマルチモニタの事は考慮してません。
マルチモニタの場合、おそらくプライマリディスプレイしか情報が取れません。。。
対応させるならEnumDisplayDevicesあたりを使う必要があるのかな?
今回も天草とはまったく関係ない話題でしたが、まぁ書くだけいいか、と割り切って書きましたw