剪贴板是Windows中系统级的堆空间,系统中任何一个应用程序对剪贴板都有访问权,可以通过剪贴板消息和使用剪贴板API来读写剪贴板内容。因此使用剪贴板不仅可以在同一个应用程序内交互数据,也可以在不通应用程序之间交互数据。尤其是在不通应用程序之间交互时,应用程序往往需要对剪贴板内容的变化做到实时感知,即应用程序要能监视剪贴板内容的变化。
Windows应用是消息驱动的,同理当剪贴板内容发生变化时,Windows提供了剪贴板变化消息,因此要实时感知剪贴板内容的变化,关键是应用程序要能响应和处理Windows触发的剪贴板变化消息。
第一步,要将窗口注册为Clipboard Viewer
需要首先解释两个概念:Clipboard Viewer和Clipboard Viewer Chain。
Clipboard Viewer是一个需要取得并显示剪贴板内容的窗口,通过Clipboard Viewer这个机制,应用程序可以在不影响剪贴板内容的情况下获取剪贴板的变化消息。Clipboard Viewer可以显示系统定义的标准格式的剪贴板内容,也可以显示应用自定义的私有数据格式的内容。通过调用函数SetClipboardViewer将窗口注册为Clipboard Viewer。
Clipboard Viewer Chain是保存Clipboard Viewer窗口以及他们之间的前后向关系的一个Windows系统链表,当一个窗口注册为Clipboard Viewer后,他会被加入Clipboard Viewer Chain,并得到链表中下一个Viewer窗口的句柄,该句柄必须保存以在响应消息时使用,该句柄的作用在下文说明。Windows正是通过Clipboard Viewer Chain保证了所有Clipboard Viewer能接收和响应剪贴板变化消息。
第二步,响应剪贴板变化消息,判断和取出剪贴板内容
在消息响应里必须正确处理两个消息:WM_DRAWCLIPBOARD和WM_CHANGECBCHAIN。
当剪贴板内容发生变化时,Windows将触发WM_DRAWCLIPBOARD消息,并将该消息送给Clipboard Viewer Chain的第一个窗口。每一个Clipboard Viewer窗口,包括第一个窗口在响应和处理该消息后,必须根据其保存的链表中的下一个窗口的句柄将该消息发送给下一个Clipboard Viewer窗口。窗口可以在该消息中取出剪贴板内容,并判断是否是该窗口增在监视的内容,如果是就进行相应的处理。
当某个Clipboard Viewer窗口注销时,系统将触发WM_CHANGECBCHAIN,并将该消息送给Clipboard Viewer Chain的第一个窗口。每一个窗口必须处理该消息。
第三步,将窗口从Clipboard Viewer Chain中注销
当窗口不再需要监视剪贴板变化消息,或窗口要关闭时,必须调用ChangeClipboardChain函数将窗口从Clipboard Viewer Chain中注销。注销后系统会触发WM_CHANGECBCHAIN消息,同WM_DRAWCLIPBOARD消息一样,该消息会给发送给Clipboard Viewer Chain的第一个窗口处理。下面代码示例当窗口被关闭时进行注销。
下面的代码片断给出了监视剪贴板中是否拷贝了URL地址的例子,如果剪贴板中的内容是URL地址,则将其显示在窗口界面上。为使示例代码具有一般性,下面给出了一般Windows程序代码和基于MFC的代代码。其他语言要实现该功能可以参考Windows程序代码。两个DEMO的完成代码请见附件。
Windows程序示例代码
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
unsigned int anFormats[] = {CF_TEXT};
unsigned int nFormat;
switch (message)
{
//----------------------------------------------------------------
case WM_CREATE:
//将本窗口注册到Clipboard Viewer Chain,
//并保存Clipboard Viewer Chain中下一个窗口的句柄
hwndNextViewer = SetClipboardViewer(hWnd);
break;
case WM_CHANGECBCHAIN: //Clipboard Viewer注销
//如果注销的Clipboard Viewer窗口是本窗口的下一个窗口,
//则修改本窗口保存的下一个窗口句柄,
//否则将该消息传递到Clipboard Viewer Chain的下一个窗口
if ((HWND) wParam == hwndNextViewer)
hwndNextViewer = (HWND) lParam;
else if (hwndNextViewer != NULL)
SendMessage(hwndNextViewer, message, wParam, lParam);
break;
case WM_DRAWCLIPBOARD: //剪贴板内容变化
//触发ON_PAINT显示URL内容
InvalidateRect(hWnd, NULL, TRUE);
UpdateWindow(hWnd);
//否则将该消息传递到Clipboard Viewer Chain的下一个窗口
SendMessage(hwndNextViewer, message, wParam, lParam);
break;
case WM_DESTROY:
//从Clipboard Viewer Chain注销本窗口
ChangeClipboardChain(hWnd, hwndNextViewer);
PostQuitMessage(0);
break;
//----------------------------------------------------------------
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX,
hWnd, (DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
//判断剪贴板中的内容是否为URL地址,如是则显示
nFormat = GetPriorityClipboardFormat(anFormats,sizeof(anFormats));
if(nFormat == CF_TEXT)
{
OpenClipboard(hWnd);
HGLOBAL hMem = GetClipboardData(nFormat);
LPTSTR lpstr = (LPTSTR)GlobalLock(hMem);
if(strstr(lpstr,"http://") != NULL ||
strstr(lpstr,"ftp://") != NULL ||
strstr(lpstr,"file://") != NULL)
{
RECT rt;
GetClientRect(hWnd, &rt);
DrawText(hdc, lpstr, -1, &rt, DT_LEFT);
}
GlobalUnlock(hMem);
CloseClipboard();
}
EndPaint(hWnd, &ps);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
MFC程序示例代码
首先要映射以下消息和继承以下函数
afx_msg void OnChangeCbChain(HWND hWndRemove, HWND hWndAfter);
afx_msg void OnDrawClipboard();
afx_msg void OnDestroy();
virtual void OnInitialUpdate();
void CMonitorUrlView::OnInitialUpdate()
{
CListView::OnInitialUpdate();
m_pListCtrl = &GetListCtrl();;
m_pListCtrl->SetExtendedStyle(LVS_EX_FULLROWSELECT |
LVS_EX_GRIDLINES |
LVS_EX_TRACKSELECT |
LVS_EX_TWOCLICKACTIVATE |
LVS_EX_UNDERLINECOLD);
m_pListCtrl->ModifyStyle(LVS_TYPEMASK, LVS_REPORT);
m_pListCtrl->InsertColumn(0, "URL",LVCFMT_LEFT,600,1);
//将本窗口注册到Clipboard Viewer Chain,
//并保存Clipboard Viewer Chain中下一个窗口的句柄
m_hwndNextViewer = SetClipboardViewer();
}
void CMonitorUrlView::OnDestroy()
{
CListView::OnDestroy();
//从Clipboard Viewer Chain注销本窗口
ChangeClipboardChain(m_hwndNextViewer);
}
//Clipboard Viewer注销
void CMonitorUrlView::OnChangeCbChain(HWND hWndRemove, HWND hWndAfter)
{
//如果注销的Clipboard Viewer窗口是本窗口的下一个窗口,
//则修改本窗口保存的下一个窗口句柄,
CView::OnChangeCbChain(hWndRemove,hWndAfter);
if(hWndRemove == m_hwndNextViewer)
m_hwndNextViewer = hWndAfter;
}
//剪贴板内容变化,判断剪贴板中的内容是否为URL地址,如是则显示
void CMonitorUrlView::OnDrawClipboard()
{
CView::OnDrawClipboard();
unsigned int anFormats[] = {CF_TEXT};
unsigned int nFormat =
GetPriorityClipboardFormat(anFormats,sizeof(anFormats));
if(nFormat == CF_TEXT)
{
HGLOBAL hMem;
OpenClipboard();
if(hMem = ::GetClipboardData(CF_TEXT))
{
LPTSTR lpszText = (LPTSTR) GlobalLock(hMem);
CString strURL = lpszText;
strURL = strURL.SpanExcluding("\r\n");
if(strURL.Left(7).CompareNoCase("http://") == 0 ||
strURL.Left(6).CompareNoCase("ftp://") == 0 ||
strURL.Left(7).CompareNoCase("file://") == 0)
{
m_pListCtrl->InsertItem(0,lpszText);
}
GlobalUnlock(hMem);
}
CloseClipboard();
}
}