http://wlbookwl.diy.myrice.com/jck/MSVCer/20020113clipspyhot.htm
附演示源程序 ClipSpy (13.4KB)
剪貼板察看器(Clipboard-vIEwer)是一種讓人感覺很神秘,又很有用的程序。典型的例子就是Windows 98所附帶的“剪貼板察看程序”工具。它可以在任何程序向剪貼板中復制數據或剪貼板被清空時立刻反映這種變化,即使它處於後台運行時,仍具有這種奇妙的能力。那麼它是如何實現這種功能的呢?下面的內容會給您以詳細的解答,並且給出實現這種功能具體的實現步驟。最後通過一個簡單的程序實例來運用這種技術構造一個“Spy”剪貼板中數據格式的小工具。
1 技術說明及實現步驟
剪貼板察看器類程序使用了剪貼板察看器鏈(Clipboard-vIEwer chain)這種技術。一個窗口若想在剪貼板重置時,能夠被剪貼板通知,它必須向剪貼板注冊自己為剪貼板的察看器窗口,剪貼板將若干此類窗口鏈接在一起,組成剪貼板察看器鏈。其運行機制如下,每當剪貼板內容重置或鏈結構改變時,剪貼板便發送消息給鏈中第一個窗口,第一個窗口響應消息後,發送該消息給鏈中的下一個窗口,鏈中每個窗口都重復響應消息、傳遞消息這個動作,直到鏈中最後一個窗口。這樣,每個窗口通過響應剪貼板的重置消息,就可以根據此時剪貼板情況來執行對應的動作。
下面介紹一下具體的編程步驟:
1、將要響應剪貼板重置消息的窗口放入剪貼板察看器鏈中。可視情況在OnInitDialog()或OnInitialUpdate()中調用基類的SetClipboardViewer()來將窗口插入鏈中。將該函數返回值保存在私有變量HWND m_hWndNextCBVIEwer中,該返回值即為鏈中的下一個窗口句柄。
2、添加對WM_DRAWCLIPBOARD和WM_CHANGECBCHAIN消息的處理函數OnDrawClipboard()、OnChangeCbChain()。
其中OnDrawClipboard()在剪貼板重置時被調用,在其中便可完成程序在剪貼板內容重置進想要完成的任何預定任務。OnChangeCbChain()在鏈結構改變時被調用,在此處要判斷參數hWndRemove是否為鏈中本窗口的下一個窗口,若是,表明此窗口已在鏈中清除自己,為保持鏈的完整性,需要將hWndAfter參數傳遞的鏈中此窗口的下一個窗口保存為本窗口的下一個窗口。注意:在這兩個函數中,一定要將接收到的消息傳遞給m_hWndNextCBVIEwer標識的下一個窗口。
3、添加對WM_DESTROY消息的處理函數OnDestroy()。在函數中調用基類的ChangeClipboardChain()函數,以在窗口銷毀時,在察看器鏈中清除本窗口。
2 應用范圍
剪貼板察看器鏈技術,對於那些對自己程序界面要求苛刻或特定方面的編程人員還是很有價值的。如編制Windows窗口應用程序時,一般都會提供“粘貼”功能,為了方便用戶使用,除了在“編輯”菜單中提供“粘貼”項外,還會在工具欄中放上“粘貼”按鈕,這種布置方法事實上已經成為Windows程序普遍采用的標准形式。而界面友好的應用程序,為了讓用戶直觀的知道程序現在可否響應“粘貼”命令,會在程序有能力處理當前剪貼板上數據格式時使能“粘貼”按鈕,而在格式與程序要求不相符時禁止該按鈕,這種處理方式要比按下按鈕時彈出個消息框或干脆一聲不吭要友好的多。但若用MFC的命令狀態更新宏來實現這種處理,會發現當程序處於後台運行時,“粘貼”按鈕變“呆”了,剪貼板與它無關。這是因為MFC對命令狀態更新是當程序處於空閒時(idle-time)才進行的,而後台運行的程序由於消息循環中無消息,當OnIdle()返回FALSE後,消息循環便不再調用它,程序因此也就無法及時反映剪貼板的這種變化。
3 實例說明
對於一項技術,一段具體的代碼最能說明問題了。使用AppWizard建立一個基於對話框的項目ClipSpy。打開對話框模板IDD_CLIPSPY_DIALOG,刪掉OK、Cancel按鈕及靜態文本框,調整對話框邊界,只留下標題欄作顯示之用,再按“技術說明及實現步驟”中的編程步驟將文後所附代碼添加到項目中,好了,一個實用的小工具就做成了,它會如實的列出你都向剪貼板中復制了什麼。
附錄
1、頭文件
// ClipSpyDlg.h : header file
…
class CClipSpyDlg : public CDialog
{
…
private:
HWND m_hWndNextCBVIEwer;
};
2、實現文件
// ClipSpyDlg.cpp : implementation file
…
CClipSpyDlg::CClipSpyDlg(CWnd* pParent /*=NULL*/)
: CDialog(CClipSpyDlg::IDD, pParent)
{
…
m_hWndNextCBVIEwer=NULL;
}
…
BOOL CClipSpyDlg::OnInitDialog()
{
…
//TODO: 將對話框加入剪貼板察看窗口鏈中
m_hWndNextCBViewer=SetClipboardVIEwer();
…
}
…
void CClipSpyDlg::OnDestroy()
{
//TODO: 從察看鏈中刪除本察看窗口 ChangeClipboardChain( m_hWndNextCBVIEwer );
CDialog::OnDestroy();
}
void CClipSpyDlg::OnDrawClipboard()
{
//TODO: 剪貼板內容發生變化
CString sText("ClipSpy-");
UINT CBFormat[]={
CF_BITMAP,
CF_DIB,
CF_DIF,
CF_DSPBITMAP,
CF_DSPENHMETAFILE,
CF_DSPMETAFILEPICT,
CF_DSPTEXT,
CF_ENHMETAFILE,
CF_HDROP,
CF_LOCALE,
CF_METAFILEPICT,
CF_OEMTEXT,
CF_OWNERDISPLAY,
CF_PALETTE,
CF_PENDATA,
CF_RIFF,
CF_SYLK,
CF_TEXT,
CF_WAVE,
CF_TIFF
};
char *FormatName[]={
"CF_BITMAP",
"CF_DIB",
"CF_DIF",
"CF_DSPBITMAP",
"CF_DSPENHMETAFILE",
"CF_DSPMETAFILEPICT",
"CF_DSPTEXT",
"CF_ENHMETAFILE",
"CF_HDROP",
"CF_LOCALE",
"CF_METAFILEPICT",
"CF_OEMTEXT",
; "CF_OWNERDISPLAY", "CF_PALETTE",
"CF_PENDATA",
"CF_RIFF",
"CF_SYLK",
"CF_TEXT",
"CF_WAVE",
"CF_TIFF",
"未知格式或剪貼板中無數據"
};
//打開剪貼板查詢格式類型
if( OpenClipboard() )
{
CString sCBAvailableFormat("");
int nFormatNum=sizeof(CBFormat)/sizeof(UINT);
//枚舉類型
for(int i=0;i<nFormatNum;i++)
{
if( ::IsClipboardFormatAvailable( CBFormat[i] ) )
{
//此處if-else修飾輸出格式
if( sCBAvailableFormat.IsEmpty() )
sCBAvailableFormat+=FormatName[i];
else
sCBAvailableFormat=sCBAvailableFormat+" & "+FormatName[i];
}
}
if( !sCBAvailableFormat.IsEmpty() )
sText+=sCBAvailableFormat;//列出有效格式
else
sText+=FormatName[i];//該格式未知
::CloseClipboard();
}
//在標題欄中顯示格式
SetWindowText( sText );
//傳WM_DRAWCLIPBOARD給下一個察看窗口
::SendMessage(m_hWndNextCBVIEwer,WM_DRAWCLIPBOARD,0,0);
}
void CClipSpyDlg::OnChangeCbChain(HWND hWndRemove, HWND hWndAfter)
{
// TODO: 察看鏈中窗口發生變動
if( m_hWndNextCBVIEwer==hWndRemove )
m_hWndNextCBVIEwer=hWndAfter;
//傳WM_CHANGECBCHAIN給下一個察看窗口
::SendMessage(m_hWndNextCBVIEwer,WM_CHANGECBCHAIN,
(WPARAM)hWndRemove,(LPARAM)hWndAfter);
}