[MFC]クリップボードを監視する

MFC
この記事は約9分で読めます。

さて今回はクリップボードを監視する方法についてまとめたいと思います。

以前書いた「[MFC]常駐型のアプリケーションを作る(設計編)」で作ったSnipExの肝の部分です。いよいよこのアプリも完成に近づいてきました。

クリップボードの変更を知る方法

正しい図となっている自信はないですが、大体のイメージを書きました。

まずクリップボードはWindowsの管理するアプリケーションであり、クリップボードへの内容の保存や情報の取得などはWindowsから行っています。そのため各アプリケーションからはクリップボードの変更を直接知ることはできません。

そこでクリップボードの変更を知りたい場合は「クリップボードに変更があったら教えてね」ということをWindowsへ登録しておく必要があります。するとWindowsではクリップボードに変更があったら、予め登録してあったアプリケーションにメッセージを投げて更新を通知します。

今回行うのは、このクリップボードの監視をしたいアプリ群の中に、SnipExを追加することです。するとクリップボードの更新がイベントメッセージとしてWindowsから通知されるので、SnipExからクリップボードの変更を知ることができるようになります。

クリップボードの監視を行う

クリップボードの監視を行うアプリ群に登録する

まずはじめにSnipExがクリップボードの変更通知を受け取りたいことをWindowsに登録しましょう。

//---------------------------------------------------------
// HotKeyが押されたときのイベントハンドラ
//---------------------------------------------------------
void CSnipExWnd::OnHotKey(UINT nHotKeyId, UINT nKey1, UINT nKey2)
{
	// TODO: ここにメッセージ ハンドラー コードを追加するか、既定の処理を呼び出します。

	CTaskTrayApp::OnHotKey(nHotKeyId, nKey1, nKey2);

	// 押下したホットキーが「SnippingStart」だった場合
	if (nHotKeyId == SNIP_START_KEY)
	{
		// 現在の状態を切り替える。
		if (m_bIsClipboardChk == TRUE)
		{
			EndClipBoardChk();
		}
		else
		{
			StartClipBoardChk();
		}
	}
}

//---------------------------------------------------------
// クリップボードの監視を開始する
//---------------------------------------------------------
void CSnipExWnd::StartClipBoardChk()
{
	// クリップボードのビューアチェインに自信を登録する。
	m_hNextViewr = SetClipboardViewer();

	// クリップボード監視中にする
	m_bIsClipboardChk = TRUE;
}

Hotleyが押された時のイベントハンドラから呼ばれるStartClipBoardChk()の中でSetClipboardViewer()でクリップボードのビューアチェインにアプリケーションを登録します。ビューアチェインに登録されたアプリケーションには、クリップボードが変更されるとWM_DRAWCLIPBOARDメッセージが発行され、クリップボードの変更を知ることができます。

そしてクリップボードの更新は、チェインになっています。Windowsはビューアチェインの先頭のウィンドウにイベントメッセージを投げ、その後他のアプリへはリレーのようにメッセージを流していきイベントを消費したところでリレーを打ち切るようにします。

そのため SetClipboardViewer()の戻り値で次にメッセージを送るアプリのハンドルを受け取り、メンバ変数に保存しておきます。

クリップボードの内容を取得する

クリップボードの内容を取得するにはオープン→リード→クローズの三段階の処理が必要となります。

//---------------------------------------------------------
// クリップボード更新時のイベントハンドラ
//---------------------------------------------------------
void CSnipExWnd::OnDrawClipboard()
{
	HANDLE hClipData = NULL;
	
	CTaskTrayApp::OnDrawClipboard();

	// TODO: ここにメッセージ ハンドラー コードを追加します。

	OpenClipboard();

	// クリップボードの更新値がイメージだったら取得する。
	hClipData = GetClipboardData(CF_BITMAP);
	
	if (hClipData != NULL)
	{
		// 取得した場合はクリップボードの内容を画像として保存する。
		SaveBitmap((HBITMAP)hClipData);
	}

	// クリップボードを閉じる
	CloseClipboard();
}

まずはOpenClipboard()でクリップボードをオープンし、GetClipboardData()でクリップボードにあるデータを取得します。この時、引数に取得するデータのタイプを指定します。

取得したデータを処理したあと、CloseClipboard()をコールしてクリップボードを閉じて処理を終了します。

またクリップボードの処理としてコールしているSaveBitmap()ですが、自作関数です。クリップボードと直接関係はしないですが、処理を紹介しておきます。

//---------------------------------------------------------
// jpgファイル保存処理
//---------------------------------------------------------
void CSnipExWnd::SaveBitmap(HBITMAP hBitMap)
{
	// イメージを保存させる

	CImage image;
	image.Attach(hBitMap);

	CTime cTime = CTime::GetCurrentTime();
	CString time = cTime.Format("%Y-%m-%d_%H-%M-%S");

	CString path = m_strSaveDir;
	path.Append(time);
	path.Append(TEXT(".jpg"));

	image.Save(path);
	image.Detach();
}

イメージのハンドルを受け取ってファイル名をつけて保存しているだけですね。保存時の日時をファイル名にすることで、ファイル名かぶりを防いでいます。

クリップボードの監視を終了する

クリップボードの監視を終了する場合は、ChangeClipboardChain()をコールしてWindowsに通知します。

//---------------------------------------------------------
// クリップボードの監視を終了する
//---------------------------------------------------------
void CSnipExWnd::EndClipBoardChk()
{
	// クリップボードのビューアチェインから削除する。
	ChangeClipboardChain(m_hNextViewr);

	// クリップボード非監視状態にする。
	m_bIsClipboardChk = FALSE;
}

自分、もしくは他のアプリケーションがクリップボードの監視を終了する場合、ビューアチェインから適切に削除する必要があります。

ビューアチェインから削除されるアプリケーションがあると、チェインの参加者にWindowsが
WM_CHANGECBCHAIN メッセージを発行するので、キャッチするハンドラでチェインの適切な位置で削除する必要があります。

//---------------------------------------------------------
// クリップボードビューア削除時のイベントハンドラ
//---------------------------------------------------------
void CSnipExWnd::OnChangeCbChain(HWND hWndRemove, HWND hWndAfter)
{
	CTaskTrayApp::OnChangeCbChain(hWndRemove, hWndAfter);

	// TODO: ここにメッセージ ハンドラー コードを追加します。
	// 次にチェインから抜けるウィンドウが、このアプリの次の位置に存在するウィンドウだった場合
	if (m_hNextViewr == hWndRemove)
	{
		// イベントの渡し先を更新する。
		m_hNextViewr = hWndAfter;
	}
	else
	{
		// そのまま次のチェーンに流す
		if (m_hNextViewr != NULL)
		{
			CWnd::FromHandle(m_hNextViewr)->SendMessage(WM_CHANGECBCHAIN, (WPARAM)hWndRemove, (LPARAM)hWndAfter);
		}
	}

	return;
}

OnChangeCbChain()では、ビューワチェインから削除されるウィンドウのハンドルと、その次のウィンドウのハンドルが引数として渡されます。

ビューワチェインの参加者は、自分の次にクリップボードのイベント通知を受け取るウィンドウを覚えているので、ビューワチェインから削除されるウィドウがそれだった場合、イベントを通知する相手を削除されるウィンドウの次のウィンドウに繋ぎ変えます。

それ以外の参加者は何もせずにこのイベントをスルーします。

まとめ

以上でクリップボードの監視を実装する方法は終わりです。

クリップボードチェインの考え方が少し難しいかもしれませんが、順を追って調べていけばわかると思うのでがんばってください。

コメント

タイトルとURLをコピーしました