迅雷下载或者旋风下载都有一个很有意思的剪贴板监控功能,当你打开剪贴板监控时,如果你复制了一个下载的URL,这两个程序都会弹出来下载框来让你去下载,显得很智能,昨天发现灵格斯词霸也有个剪贴板取词功能。

上个月写了个智能注册表定位器,输入一段注册表字符串,能为你打开注册表编辑器并定位到相应的注册表位置,有个哥么说想智能点,当我复制注册表字符串时,给我自动拷贝到输入框去啊,后来就研究了一下剪贴板监控,发现还是蛮简单的,原来这一切,微软都给我们想好了,:-)。

 

这是智能注册表定位器的外观

 

android 监听剪切板数据 剪切板监控 xposed插件_api

 

首先你要把一个窗口句柄传到windows的剪贴板监听链中,使用API:SetClipboardViewer,这样在用户复制或者剪切时,Windows会给我们的窗口发送一个WM_DRAWCLIPBOARD消息。而且,当有其他程序加入或者退出这个监听者链表时,Windows会给我们的窗口发送一个WM_CHANGECBCHAIN消息。注意:当自己的窗口接收到这两个消息时,应该使用SendMessage函数来把消息传送给下一个监听者。

m_hNextClipboard = ::SetClipboardViewer(GetSafeHwnd());调用该API会返回下一个监听的窗口句柄,这个要保存下来,等下有用。

我下面的说明都是以MFC为例的,SDK里面怎么做,其实是类似的,我就不加说明了,我也没试过,但是原理是一样的。

然后映射两个消息:ON_WM_CHANGECBCHAIN() 和 ON_WM_DRAWCLIPBOARD()

头文件里面添加如下两个消息函数声明:

afx_msg void OnChangeCbChain(HWND hWndRemove, HWND hWndAfter);
afx_msg void OnDrawClipboard();

WM_CHANGECBCHAIN消息的处理,代码如下:

void CTClipboardMonDlg::OnChangeCbChain(HWND hWndRemove, HWND hWndAfter) 
{
 CDialog::OnChangeCbChain(hWndRemove, hWndAfter);
 if (m_hNextClipboard == hWndRemove)
  m_hNextClipboard = hWndAfter;
 else if (m_hNextClipboard)
  ::SendMessage(m_hNextClipboard, WM_CHANGECBCHAIN, (WPARAM)hWndRemove, (LPARAM)hWndAfter);
}

当有其他程序加入或者退出windows剪贴板监控链时,你会收到此消息,两个参数hWndRemove正要移出剪贴板监听链的窗口句柄,hWndAfter是移出监听链的窗口句柄的下一个监听窗口句柄,此时你要处理两件事情,如果退出的窗口句柄刚好是当前监听窗口的下一个句柄,则把hWndAfter赋值给m_hNextClipboard,保持整个监听链的联通,如果不是的话则给m_hNextClipboard发送一个相同的WM_CHANGECBCHAIN消息,最终目的也是保持整个监听链的联通性。这是属于一个规范的问题,试想如果有一个程序不这样做,则会造成监听链断裂,某些加入了监听链的窗口就会收不到系统发送的剪贴板消息了(原因是因为系统只给剪贴板链的头发送ON_WM_DRAWCLIPBOARD()消息,然后每个窗口负责给后面剪贴板窗口链上的窗口发送ON_WM_DRAWCLIPBOARD()消息,如果你没有正确处理ON_WM_CHANGECBCHAIN消息,会造成某个窗口不能更新自己的下一个监听链窗口句柄,整个链就断了,链在它之后的窗口就收不到剪贴板变化消息了)。

ON_WM_DRAWCLIPBOARD消息处理,代码如下:

void CTClipboardMonDlg::OnDrawClipboard()
{
	CDialog::OnDrawClipboard();
	HGLOBAL hClipboardData;

	// 如果观看链中在当前程序下面存在下一个程序的话
	// 就传递一个WM_DRAWCLIPBOARD 消息给它
	if(m_hNextClipboard)
		::SendMessage(m_hNextClipboard, WM_DRAWCLIPBOARD, 0, 0);
	
	// 打开剪贴板
	::OpenClipboard(GetSafeHwnd());
	
	// 获得剪贴板内容的全局句柄,剪贴板内容格式限制为CF_TEXT 文本格式
	hClipboardData = GetClipboardData(CF_TEXT);
	
	// 锁定全局句柄的地址,并赋给CString 型变量m_strContent
	CString m_strContent = (char *)(GlobalLock(hClipboardData));

	// 将m_strContent 的内容在编辑框里显示出来
	SetDlgItemText(IDC_EDIT_CLIP, m_strContent);
	
	// 取消锁定
	GlobalUnlock(hClipboardData);
	
	// 关闭剪贴板
	::CloseClipboard();
	


	unsigned int anFormats[] = {CF_TEXT, CF_HDROP};

    unsigned int nFormat = GetPriorityClipboardFormat(anFormats, sizeof(anFormats));  

    if(nFormat == CF_TEXT)
    {
        HGLOBAL hMem;

        ::OpenClipboard(GetSafeHwnd());

        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();
    }
	else if (nFormat == CF_HDROP)
	{
		HGLOBAL hMem;
		CString cstrInfo;
		char szFileName[MAX_PATH];

        ::OpenClipboard(GetSafeHwnd());

        if(hMem = ::GetClipboardData(CF_HDROP))
        {

			UINT FileNumber = ::DragQueryFile((HDROP)hMem, -1, NULL, 0); //得到文件数量
			for (int i = 0; i < FileNumber; i++)
			{
				//获得路径及文件名
				::DragQueryFile((HDROP)hMem, i, szFileName, sizeof(szFileName));
				cstrInfo = cstrInfo + szFileName;
				cstrInfo = cstrInfo + "/r/n";
				
			}

			SetDlgItemText(IDC_EDIT_CLIP, cstrInfo);

        }

        ::CloseClipboard();
	}
}

打开剪贴板,获取数据的方法这里就不说明了,我这里只想强调的是,一定要给监听链上的下一个窗口发送一个相同的ON_WM_DRAWCLIPBOARD消息,否则会怎么样,你可以自己写个程序试试。

在窗口销毁时,应该使这个窗口退出windwos剪贴板监控链,使用API:ChangeClipboardChain,传入调用SetClipboardViewer时的句柄。


附录

源代码下载:


 

我在网上找了一些剪贴板的API,可以去查查MSDN,嘿嘿,你想监控啥类型的数据就自己去编程实现啦。

(一)ChangeClipboardChain
将剪贴的连接从一个句柄转到下一个句柄。

BOOL ChangeClipboardChain( 
HWND hWndRemove, // handle to window to remove 
HWND hWndNewNext // handle to next window 
);


(1)hWndRemove表示第一个窗口的句柄(断开)。
(2)hWndNewNext表示第二个窗口的句柄(连接)。
注意,在使用之前应该使用SetClipboardViewer事先进行窗口句柄的连接。
(二)CloseClipboard
关闭剪贴板。
BOOL CloseClipboard(VOID)//VOID意思是空白。
本函数没有参数,事先应该用OpenClipboard函数打开过剪贴板。
(三)CountClipboardFormats
不管剪贴板是什么格式,全部转化为数据格式。
int CountClipboardFormats(VOID)
本函数没有参数。
(四)EmptyClipboard
清空剪贴板。
BOOL EmptyClipboard(VOID)
本函数没有参数。
(五)EnumClipboardFormats
使剪贴板内的格式转变成指定格式。

UINT EnumClipboardFormats( 
UINT format // specifies a known available clipboard format 
);


其中format表示的是将要转化成的格式。该参数的意义可参照后面。
(六)GetClipboardData
获取剪贴板内的数据。

HANDLE GetClipboardData( 
UINT uFormat // clipboard format 
);


其中format表示的是剪贴板内数据的格式。该参数的意义可参照后面。
(七)GetClipboardFormatName
获取剪贴板内数据格式的名称。

int GetClipboardFormatName( 
UINT format, // clipboard format to retrieve 
LPTSTR lpszFormatName, // address of buffer for name 
int cchMaxCount // length of name string in characters 
);


(1)format表示的意义同前,应该是不事先规定格式;
(2)lpszFormatName表示的是格式名称地址;
(3)cchMaxCount剪贴板内数据的长度。
(八)GetClipboardOwner
获取当前剪贴板是属于哪一个窗口的句柄。
HWND GetClipboardOwner(VOID)
返回那个窗口的句柄。
(九)GetClipboardSequenceNumber
返回剪贴板序号。
DWORD GetClipboardSequenceNumber(VOID)
(十)GetClipboardViewer
返回剪贴板属于窗口的句柄。
HWND GetClipboardViewer(VOID)
(十一)GetOpenClipboardWindow
返回打开剪贴板的那个窗口句柄。

HWND GetOpenClipboardWindow(VOID) 
(十二)GetPriorityClipboardFormat 
int GetPriorityClipboardFormat( 
UINT *paFormatPriorityList, // address of priority list 
int cFormats // number of entries in list 
);


(十三)IsClipboardFormatAvailable
判断剪贴板的格式。

BOOL IsClipboardFormatAvailable( 
UINT format // clipboard format 
);


其中format表示的是剪贴板内数据的格式。该参数的意义可参照后面。
(十四)OpenClipboard
打开剪贴板。

BOOL OpenClipboard( 
HWND hWndNewOwner // handle to window opening clipboard 
);


返回剪贴板的句柄。
(十五)RegisterClipboardFormat
注册新的剪贴板格式。

UINT RegisterClipboardFormat( 
LPCTSTR lpszFormat // address of name string 
);


lpszFormat新的剪贴板格式名称。
(十六)SetClipboardData
设置剪贴板内的数据。

HANDLE SetClipboardData( 
UINT uFormat, // clipboard format 
HANDLE hMem // data handle 
);


uFormat表示的是要放进剪贴板数据的格式;
hMem表示数据的地址指针。
(十七)SetClipboardViewer
将剪贴板内容连接到窗口。

HWND SetClipboardViewer( 
HWND hWndNewViewer // handle to clipboard viewer window 
);


hWndNewViewer表示要连接到的那个窗口句柄。
上文中剪贴板格式Format的可选参数如下:

CF_BITMAP位图格式; 
CF_DIB 
CF_DIBV5 
CF_DIF 
CF_DSPBITMAP 
CF_DSPENHMETAFILE 
CF_DSPMETAFILEPICT 
CF_DSPTEXT 
CF_ENHMETAFILE 
CF_GDIOBJFIRST 
CF_GDIOBJLAST 
CF_HDROP 
CF_LOCALE 
CF_METAFILEPICT 
CF_OEMTEXT 
CF_OWNERDISPLAY 
CF_PALETTE 
CF_PENDATA 
CF_PRIVATEFIRST 
CF_PRIVATELAST 
CF_RIFF 
CF_SYLK 
CF_TEXT文本格式; 
CF_WAVE音乐格式; 
CF_TIFF 
CF_UNICODETEXT

 

【END】