ビューアチェイン


クリップボードビューアを作る

クリップボードビューアとは、クリップボードの状態を表示するアプリケーションのことで
Windows には Microsoft の CLIPBRD.EXE が標準で付属しています

クリップボードビューワは、私たちも作ることができます
基本的には、GetClipboardData() を定期的に呼び出しそれを表示すればよいのです
問題は、これを実行するタイミングです

定期的にタイマーでこれを取得するというのも方法ですが、これは合理的ではないでしょう
やはり、クリップボードの内容が変更された時というのがもっとも理想的です
実は、Windows はクリップボードに変更があるとビューアに
WM_DRAWCLIPBOARD メッセージを通知します
このメッセージにパラメータや戻り値はありません

ただし、Windows はこのメッセージをクリップボードビューアにしか通知しません
ビューワに登録するには SetClipboardViewer() 関数を使います

HWND SetClipboardViewer(HWND hWndNewViewer);

hWndNewViewer は、ビューアのウィンドウのハンドルを指定します
戻り値は、このビューアの次に登録されているビューアのハンドルです

この戻り値の値が意味するものは、クリップボードビューアの作成に重要です
実は、Windows は1つのビューアにしかメッセージを通知しません
つまり、最後に SetClipboardViewer() を用いて登録したビューアです

しかし、一度に複数のビューアが実行されることも十分ありえます
そのため、プログラムは SetClipboardViewer() が返したウィンドウに
WM_DRAWCLIPBOARD メッセージを送信する必要があるのです

こうして、上から下へ、鎖にようにビューアプログラムがメッセージを流します
このクリップボードビューアの構造をクリップボードビューアチェインと呼びます

クリップボードビューアは、チェインから自分自身を削除することができます
通知の必要がなくなれば ChangeClipboardChain() を使います

BOOL ChangeClipboardChain(HWND hWndRemove , HWND hWndNewNext);

hWndRemove には、チェインから削除するウィンドウのハンドルを
hWndNewNext は、自分の次のチェインのハンドルを指定します
戻り値は通常は FALSE になりますが、チェインにウィンドウが1つしかなかったときは TRUE です

この関数が呼び出されると、Windows はチェインの最上のウィンドウに
(つまり、最初に WM_DRAWCLIPBOARD を受け取るウィンドウ)
WM_CHANGECBCHAIN メッセージが送られます

WPARAM にはチェインから外れるウィンドウのハンドルが
LPARAM には WPARAM のウィンドウの次のウィンドウのハンドルが格納されています
この値は、ChangeClipChain() 関数の引数と同じです。戻り値は 0 を指定します
チェインに参加している全てのウィンドウは、このメッセージを処理する義務があります

もし、WPARAM の値が次のウィンドウと等しければ
これを削除し、LPARAM のハンドルに対して今後メッセージを送るようにします
WPARAM が次のウィンドウでなければ、自分の次のウィンドウにこのメッセージを送ります
#include <windows.h>

LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	static HWND hNextWnd;
	HGLOBAL hg;
	PTSTR strText;
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;

	switch (msg) {
	case WM_DESTROY:
		ChangeClipboardChain(hwnd , hNextWnd);
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		hNextWnd = SetClipboardViewer(hwnd);
		return 0;
	case WM_DRAWCLIPBOARD:
		if (hNextWnd) SendMessage(hNextWnd , msg , wp , lp);
		InvalidateRect(hwnd , NULL , TRUE);
		return 0;
	case WM_CHANGECBCHAIN:
		if ((HWND)wp == hNextWnd) hNextWnd = (HWND)lp;
		else if (hNextWnd) SendMessage(hNextWnd , msg , wp , lp);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hwnd , &ps);
		if (OpenClipboard(hwnd) && (hg = GetClipboardData(CF_TEXT))) {
			strText = (PTSTR)GlobalLock(hg);
			GetClientRect(hwnd , &rect);
			DrawText(hdc , strText , -1 , &rect , DT_LEFT);
			GlobalUnlock(hg);
			CloseClipboard();
		}
		EndPaint(hwnd , &ps);
		return 0;
	}
	return DefWindowProc(hwnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow ) {
	HWND hwnd;
	HACCEL haccel;
	MSG msg;
	WNDCLASS winc;

	winc.style		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= 0;
	winc.cbWndExtra	= DLGWINDOWEXTRA;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return -1;

	hwnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hwnd == NULL) return -1;

	while(GetMessage(&msg , NULL , 0 , 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}
このプログラムをコンパイルし、2つ同時に実行した状態で
何らかの文字列をクリップボードに転送すると、2つのウィンドウとも
即座に変更が通知され、文字列を再表示します

もし、あなたがクリップボードチェインの約束を破り
メッセージをあなたのウィンドウで止めるようなことをした場合
このプログラム以下のウィンドウは、例えチェインに登録していてもメッセージは受け取りません


SetClipboardViewer()

HWND SetClipboardViewer(HWND hWndNewViewer);

クリップボードビューアチェインに、指定されたウィンドウを追加します

hWndNewViewer - クリップボードチェインに追加するウィンドウを指定します

戻り値 - 次に位置するウィンドウのハンドル。失敗、または次がない場合は NULL

ChangeClipboardChain()

BOOL ChangeClipboardChain(HWND hWndRemove , HWND hWndNewNext);

クリップボードビューアのチェインから、指定されたウィンドウを削除します

hWndRemove - 削除するウィンドウのハンドルを指定します
hWndNewNext - hWndRemove の次のウィンドウのハンドルを指定します

戻り値 - 通常は FALSE、ウィンドウが1つしかなかったときは TRUE



前のページへ戻る次のページへ