12.3 实现一个剪贴板查看器

摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P462

        如果某个程序能获知剪贴板内容的改变,这个程序就被称为“剪贴板查看器”。Windows 带有一个剪贴板查看器,但是你也可以编写自己的剪贴板查看器程序。剪贴板查看器通过发送给查看器窗口过程的消息得知剪贴板内容的更改。

12.3.1  剪贴板查看器链

        在 Windows 下可以同时运行任意数目的剪贴板查看器应用程序,并且剪贴板的改变都能通知到它们。但是从 Windows 的角度来看,只有一个剪贴板查看器,我称之为“当前剪贴板查看器”。Windows 只维护一个标识当前剪贴板查看器的窗口句柄,并且只给那个窗口发送消息,通知它剪贴板内容改变了。

        剪贴板查看器应用程序应该加入“剪贴板查看器链”,这样所有运行的剪贴板查看器程序就都能收到 Windows 发给当前剪贴板查看器的消息。当某程序把自己注册为剪贴板查看器时,这个程序就成为当前剪贴板查看器。Windows 把上一个当前剪贴板查看器的窗口句柄交给这个程序,然后这个程序保存此句柄。当它收到剪贴板查看器消息时,它会把消息发送给剪贴板查看器链中下一个程序的窗口过程。

12.3.2  剪贴板查看器函数和消息

        通过调用 SetClipboardViewer 函数,程序就能成为剪贴板查看器链的一部分。如果此程序的主要目的就是作为一个剪贴板查看器,那么它可以在处理 WM_CREATE 消息时调用此函数。这个函数返回上一个当前剪贴板查看器的窗口句柄。程序应该用静态变量来保存这个句柄:

static HWND hwndNextViewer;
[其他程序行]
case WM_CREATE:
    [其他程序行]
    hwndNextViewer = SetClipboardViewer (hwnd);
如果你的程序在 Windows 会话期间是头一个成为剪贴板查看器的程序,那么 hwndNextViewer 将为 NULL。

        一旦剪贴板内容改变了,Windows 就会向当前剪贴板查看器(即最新注册成为为剪贴板查看器的窗口)发送 WM_DRAWCLIPBOARD 消息。剪贴板查看器链的最后一个程序(即第一个注册成为剪贴板查看器的窗口)将会有一个值为 NULL 的 hwndNextViewer。如果 hwndNextViewer 值为 NULL,程序就直接返回,不需要把消息发送给另一个程序。(不要把 WM_DRAWCLIPBOARD 和 WM_PAINTCLIPBOARD 消息混为一谈。WM_PAINTCLIPBOARD 是由剪贴板查看器发给使用 CF_OWNERDISPLAY 剪贴板格式的程序的。WM_DRAWCLIPBOARD 则是由 Windows 发给当前剪贴板查看器的。)

        处理 WM_DRAWCLIPBOARD 消息的最简单的方法是把消息发给下一个剪贴板查看器(除非 hwndNextViewer 为 NULL),并且使窗口的客户区无效:

case WM_DRAWCLIPBOARD:
    if (hwndNextViewer)
        SendMessage (hwndNextViewer, message, wParam, lParam);
    InvalidateRect (hwnd, NULL, TRUE);
    return 0;
处理 WM_PAINT 消息期间,你可以通过普通的 OpenClipboard、GetClipboardData 和 CloseClipboard 调用读取剪贴板内容。

        当某程序想从剪贴板查看器链中退出时,必须调用 ChangeClipboardChain。这个函数需要退出的程序的窗口句柄和它下一个剪贴板查看器的窗口句柄:

ChangeClipboardChain(hwnd, hwndNextViewer);
当程序调用 ChangeClipboardChain 时,Windows 会向当前剪贴板查看器发送一个 WM_CHANGECBCHAIN 消息。该消息的 wParam 参数是要从链里退出的窗口的句柄(即 ChangeClipboardChain 的第一个参数),lParam 是将要退出的窗口的下一个剪贴板查看器的窗口句柄(即 ChangeClipboardChain 的第二个参数)。

        当你的程序收到了 WM_CHANGECBCHAIN 消息,则必须检查 wParam 是否等于你保存的 hwndNextViewer 值。如果相等,你的程序必须把 hwndNextViewer 设为 lParam。这个操作确保将来你得到的任何 WM_CHANGECBCHAIN 消息不会被发送给从链里退出的那个窗口。如果 wParam 不等于 hwndNextViewer,并且 hwndNextViewer 不为 NULL,就把消息发送给下一个剪贴板查看器:

case WM_CHANGECBCHAIN:
    if ((HWND) wParam == hwndNextViewer)
        hwndNextViewer = (HWND)lParam);

    else if (hwndNextViewer)
        SendMessage (hwndNextViewer, message, wParam, lParam);
    return 0;
并不一定要加上 else if 语句,它只是检查 hwndNextViewer 是否为非 NULL。hwndNextViewer 值为 NULL 意味着执行这段代码的程序是链里的最后一个剪贴板查看器,在这种情况下,消息不应该再传递。

        如果你的程序在要终止时仍然在剪贴板查看器链里,则必须把它从链里删除。可以在处理 WM_DESTROY 消息时调用 ChangeClipboardChain 来完成删除:

case WM_DESTROY:
    ChangeClipboardChain (hwnd, hwndNextViewer);
    PostQuitMessage (0);
    return 0;

        Windows 也有一个让程序获得第一个剪贴板查看器的窗口句柄的函数:

hwndViewer = GetClipboardViewer();
这个函数通常没有什么用。如果当前剪贴板查看器不存在,则返回值为 NULL。

        这里有一个例子说明剪贴板查看器链是怎样工作的。当 Windows 刚开始启动时,当前剪贴板查看器为 NULL:

        当前剪贴板查看器:                                                      NULL

        拥有窗口句柄 hwnd1 的程序调用了 SetClipboardViewer。该函数会返回 NULL,这个值成为此程序里的 hwndNextViewer 值:

        当前剪贴板查看器:                                                      hwnd1

        hwnd1 的下一个查看器:                                             NULL

        拥有窗口句柄 hwnd2 的第二个程序现在调用 SetClipboardViewer,并且取得 hwnd1:

        当前剪贴板查看器:                                                      hwnd2

        hwnd2 的下一个查看器:                                            hwnd1

        hwnd1 的下一个查看器:                                            NULL

        第三个程序(hwnd3)和第四个程序(hwnd4)也调用了 SetClipboardViewer,并且取得 hwnd2 和 hwnd3:

        当前剪贴板查看器:                                                      hwnd4

        hwnd4 的下一个查看器:                                            hwnd3

        hwnd3 的下一个查看器:                                            hwnd2

        hwnd2 的下一个查看器:                                            hwnd1

        hwnd1 的下一个查看器:                                            NULL

        当剪贴板内容发生变化时,Windows 向 hwnd4 发送 WM_DRAWCLIPBOARD 消息,hwnd4 把消息发给 hwnd3,hwnd3 再发给 hwnd2,hwnd2 发给 hwnd1,hwnd1 返回。

        现在 hwnd2 决定从链中退出,它调用以下函数:

ChangeClipboardChain (hwnd2, hwnd1);

        Windows 会向 hwnd4 发送 WM_CHANGECBCHAIN 消息,相应的 wParam 等于 hwnd2,lParam 等于 hwnd1。由于 hwnd4 的下一个查看器时 hwnd3,因此 hwnd4 把这个消息发给 hwnd3。现在 hwnd3 注意到 wParam 等于它的下一个查看器(hwnd2),所以它把它的下一个查看器设置成等于 lParam(hwnd1)并且返回。这个使命就完成了。剪贴板查看器现在看起来如下:

        当前剪贴板查看器:                                                      hwnd4

        hwnd4 的下一个查看器:                                            hwnd3

        hwnd3 的下一个查看器:                                            hwnd1

        hwnd1 的下一个查看器:                                            NULL

12.3.3  一个简单的剪贴板查看器

        剪贴板查看器不一定要和 Windows 提供的查看器一样复杂。比如,剪贴板查看器可以只显示一种剪贴板格式。图 12-2 所示的 CLIPVIEW 程序就是一个只显示 CF_TEXT 格式的剪贴板查看器。

/*---------------------------------------------
	CLIPVIEW.C -- Simple Clipboard Viewer
				(c) Charles Petzold, 1998
----------------------------------------------*/

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	PSTR szCmdLine, int iCmdShow)
{
	static TCHAR szAppName[] = TEXT(".ClipView.");
	HWND		 hwnd;
	MSG			 msg;
	WNDCLASS	 wndclass;

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

	if (!RegisterClass(&wndclass))
	{
		MessageBox(NULL, TEXT("This program requires Windows NT!"),
			szAppName, MB_ICONERROR);
		return 0;
	}

	hwnd = CreateWindow(szAppName, 
		TEXT("Simple Clipboard Viewer (Text Only)"),
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		NULL, NULL, hInstance, NULL);

	ShowWindow(hwnd, iCmdShow);
	UpdateWindow(hwnd);

	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HWND  hwndNextViewer;
	HGLOBAL		 hGlobal;
	HDC			 hdc;
	PTSTR		 pGlobal;
	PAINTSTRUCT  ps;
	RECT		 rect;

	switch (message)
	{
	case WM_CREATE:
		hwndNextViewer = SetClipboardViewer(hwnd);
		return 0;

	case WM_CHANGECBCHAIN:
		if ((HWND)wParam == hwndNextViewer)
			hwndNextViewer = (HWND)lParam;

		else if (hwndNextViewer)
			SendMessage(hwndNextViewer, message, wParam, lParam);

		return 0;
	
	case WM_DRAWCLIPBOARD:
		if (hwndNextViewer)
			SendMessage(hwndNextViewer, message, wParam, lParam);
		InvalidateRect(hwnd, NULL, TRUE);
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		GetClientRect(hwnd, &rect);
		OpenClipboard(hwnd);
		
#ifdef UNICODE
		hGlobal = GetClilpboardData(CF_UNICODETEXT);
#else
		hGlobal = GetClipboardData(CF_TEXT);
#endif
		
		if (hGlobal != NULL)
		{
			pGlobal = (PTSTR)GlobalLock(hGlobal);
			DrawText(hdc, pGlobal, -1, &rect, DT_EXPANDTABS);
			GlobalUnlock(hGlobal);
		}

		CloseClipboard();
		EndPaint(hwnd, &ps);
		return 0;

	case WM_DESTROY:
		ChangeClipboardChain(hwnd, hwndNextViewer);
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}

        按照前面讨论的,CLIPVIEW 处理 WM_CREATE、WM_CHANGECBCHAIN、WM_DRAWCLIPBOARD 和 WM_DESTROY 消息。WM_PAINT 消息打开剪贴板并用 CF_TEXT 格式调用 GetClipboardData。如果该函数返回一个全局内存句柄,CLIPVIEW 就锁定它并用 DrawText 在客户区显示文本。

        能处理标准格式之外的其他格式的剪贴板查看器(比如 Windows 提供的剪贴板查看器)还需要作一些额外的工作,比如显示当前剪贴板里所有格式的名称。你可以调用 EnumClipboardFormats 得到格式名,还可以调用 GetClipboardFormatName 来取得非标准格式的名称。使用 CF_OWNERDISPLAY 格式的剪贴板查看器必须向剪贴板所有者发送以下四个消息才能显示数据:

        WM_PAINTCLIPBOARD                                             WM_VSCROLLCLIPBOARD

        WM_SIZECLIPBOARD                                                WM_HSCROLLCLIPBOARD

        如果想实现这种剪贴板查看器,你得通过 GetClipboardOwner 取得剪贴板所有者的窗口句柄。而且,在需要更新剪贴板查看器的客户区时,还需要向剪贴板所有者的窗口发送上述这些信息。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值