传统剪贴板操作

2 篇文章 0 订阅
1 篇文章 0 订阅

【转】http://blog.sina.com.cn/s/blog_7d2ec95b0101iz4b.html

剪贴板函数

Function Description
OpenClipboard 打开剪贴板
CloseClipboard 关闭剪切板
EmptyClipboard 删除剪贴板上的内容
GetClipboardData 从剪贴板获取数据
SetClipboardData 向剪贴板传递数据

将数据放入剪贴板需要四个步骤:

调用::OpenClipboard打开剪贴板。
调用::EmptyClipboard清空剪贴板。
调用::SetClipboardData向剪贴板传递全局内存块或其它包含剪贴板数据的对象的句柄。
调用::CloseClipboard关闭剪贴板。

全局内存块是调用::GlobalAlloc分配的内存块,它返回一个HGLOBAL类型的句柄,在Win32应用中可以视为通用句柄。一个名为::GlobalLock的相关函数使用HGLOBAL,并返回指向该内存块的指针。Windows程序员不经常使用::GlobalAlloc,在Win32中被::HeapAlloc取代。但是,它还是非常有用的,因为剪贴板需要的是内存句柄,而不是指针。

下面的代码,先将字符串复制到全局内存块,再将内存块交给剪贴板:

char szText[] = “Hello World!”;
if ( ::OpenClipboard (m_hWnd) {
::EmptyClipboard ();
HANDLE hData = ::GlobalAlloc (GMEM_MOVEABLE, ::lstrlen (szText) + 1);
LPSTR pData = (LPSTR) ::GlobalLock (hData);
::lstrcpy (pData, szText);
::GlobalUnlock (hData);
::SetClipboardData (CF_TEXT, hData);
::CloseClipboard ();
}

全局内存块传一旦交给剪贴板,分配该内存块的应用既不应该使用它也不应该删除它。剪贴板拥有该内存块,下次调用::EmptyClipboard时,释放内存块。

传递给::OpenClipboard的唯一参数是打开剪贴板时,拥有剪贴板的窗口的句柄。在MFC应用中,CWnd的窗口句柄可以通过数据成员m_hWnd获得。如果其它应用已经打开了剪贴板,调用::OpenClipboard就会失败。Windows使用这种先打开再使用的方法,同步对共享资源的访问,并且可以保证使用过程中,内容不会被修改。

从剪贴板获取数据同样简单,步骤是:

调用::OpenClipboard打开剪贴板。
调用::GetClipboardData获取全局内存块或其它包含剪贴板数据的对象的句柄。
将数据从全局内存块复制到局部变量。
调用::CloseClipboard关闭剪贴板。

下面的代码获取前一个例子放在剪贴板上的字符串:

char szText[BUFLEN];
if (::OpenClipboard (m_hWnd)) {
HANDLE hData = ::GetClipboardData (CF_TEXT);
if (hData != NULL) {
LPCSTR pData = (LPCSTR) ::GlobalLock (hData);
if (::lstrlen (pData) < BUFLEN)
::lstrcpy (szText, pData);
::GlobalUnlock (hData);
}
::CloseClipboard ();
}

19.1.1 剪贴板格式

::SetClipboardData和::GetClipboardData都用一个整数值设置剪贴板格式,它是传递过程中数据类型的标识符。前一节例子中使用的是CF_TEXT,表示数据是ANSI文本。Unicode文本使用另一种格式ID。

经常使用的剪贴板格式

Format Data Type
CF_BITMAP Windows位图
CF_DIB 设备无关位图
CF_ENHMETAFILE GDI增强型元文件
CF_METAFILEPICT 旧的GDI元文件,带有大小和映射模式附加信息
CF_HDROP HDROP 格式的文件名列表
CF_PALETTE GDI调色板
CF_TEXT 8位ANSI字符组成的文本
CF_TIFF TIFF格式的位图
CF_UNICODETEXT 16位Unicode字符组成的文本
CF_WAVE WAV格式的音频数据

可以使用预定义的剪贴板格式像传递文本一样传递位图、调色板、增强型元文件等对象。例如,m_bitmap是CBitmap的一个数据成员,它保存一个位图,可以按照下面的方法复制这个位图,并将其放在剪贴板上:

if (::OpenClipboard (m_hWnd)) {
// Make a copy of the bitmap.
BITMAP bm;
CBitmap bitmap;
m_bitmap.GetObject (sizeof (bm), &bm);
bitmap.CreateBitmapIndirect (&bm);
CDC dcMemSrc, dcMemDest;
dcMemSrc.CreateCompatibleDC (NULL);
CBitmap* pOldBitmapSrc = dcMemSrc.SelectObject (&m_bitmap);
dcMemDest.CreateCompatibleDC (NULL);
CBitmap* pOldBitmapDest = dcMemDest.SelectObject (&bitmap);
dcMemDest.BitBlt (0, 0, bm.bmWidth, bm.bmHeight, &dcMemSrc, 0, 0, SRCCOPY);
HBITMAP hBitmap = (HBITMAP) bitmap.Detach ();
dcMemDest.SelectObject (pOldBitmapDest);
dcMemSrc.SelectObject (pOldBitmapSrc);
// Place the copy on the clipboard.
::EmptyClipboard ();
::SetClipboardData (CF_BITMAP, hBitmap);
::CloseClipboard ();
}

从剪贴板获取位图时,调用::GetClipboardData,并传递一个CF_BITMAP参数:

if (::OpenClipboard (m_hWnd)) {
HBITMAP hBitmap = (HBITMAP) ::GetClipboardData (CF_BITMAP);
if (hBitmap != NULL) {
// Make a local copy of the bitmap.
}
::CloseClipboard ();
}

注意这里使用的模式。将数据放入剪贴板时,要告诉Windows数据的类型。获取数据时,要询问该类型的数据是否存在。如果不存在,::GetClipboardData返回NULL。

CF_HDROP剪贴板格式

最有意思的一种格式是CF_HDROP。使用该格式从剪贴板获取数据时,得到的是HDROP,它实际上是一个全局内存块的句柄。内存块内部是一个文件名的列表。读取文件名时,不必解析内存块的内容,可以使用::DragQueryFile。下面的代码从剪贴板获取HDROP,并将文件名写入列表框:

if (::OpenClipboard (m_hWnd)) {
HDROP hDrop = (HDROP) ::GetClipboardData (CF_HDROP);
if (hDrop != NULL) {
// Find out how many file names the HDROP contains.
int nCount = ::DragQueryFile (hDrop, (UINT) - 1, NULL, 0);
// Enumerate the file names.
if (nCount)
TCHAR szFile[MAX_PATH];
for (int i=0; i

ifdef UNICODE

pDropFiles->fWide = TRUE;

else

pDropFiles->fWide = FALSE;

endif

LPBYTE pData = (LPBYTE) pDropFiles sizeof (DROPFILES);
::CopyMemory (pData, szFiles, sizeof (szFiles));
::GlobalUnlock (hData);
::SetClipboardData(CF_HDROP, hData);
::CloseClipboard();
}

传递给::GlobalAlloc的参数GHND是GMEM_MOVEABLE和GMEM_ZEROINIT的组合。GMEM_ZEROINIT告诉::GlobalAlloc将内存块的所有字节初始化为0,这可以将没有被初始化的DROPFILES成员设置为0。在Win32环境中不再需要GMEM_MOVEABLE。

19.1.2 私用剪贴板格式

虽然预定义的剪贴板格式包含了很多种数据类型,但是它们不可能包括应用中所需的全部数据类型。Winodws允许注册私有的剪贴板格式,并使用它们代替标准的剪贴板格式。

假设正在编写一个Widget应用。你想让用户可以剪切或复制饰件到剪贴板,然后粘贴到文档的其它位置。要实现这样的功能,就要调用Win32的API函数::RegisterClipboardForamt为饰件注册一个私有剪贴板格式:

UINT nID = ::RegisterClipboardForamt (_T(“Widget”));

nID是私有剪贴板格式的ID。要复制饰件到剪贴板,就要复制定义饰件所需的全部数据,然后用nID和内存句柄调用::SetClipboardData:

::SetClipboardData (nID, hData);

要从剪贴板获取饰件,可以向::GetClipboardData传递nID:

HANDLE hData = ::GetClipboardData (nID);

然后锁定内存块获取指针,用内存块的数据重建饰件。这里的关键点是,如果10个不同的应用(或同一应用的10个不同实例)用相同的格式名调用::RegisterClipboardFormat,它们会得到相同的剪贴板格式ID。这样,只要应用A和应用B调用::RegisterClipboardForamt时使用相同的格式名,应用B就可以获取应用A复制到剪贴板上的饰件。

19.1.3 以多种格式提供数据

在剪贴板中放置多个项目是完全合法的,只要它们具有不同的格式。应用程序总是这么做。这也是向多个应用提供数据的有效方法,即使有些应用并不理解私有剪贴板格式。

微软的Excel就是一个例子。在Excel中选择一定范围的电子表格单元复制到剪贴板时,Excel最多可以在剪贴板上放置30个项目。其中之一是私有剪贴板格式,表示Excel自己的电子表格数据。另一个是表格单元格的CF_BITMAP译文。Windows的Paint应用并不理解Excel的私有剪贴板格式,但是却可以粘贴Excel电子表格单元到一个位图。至少看上去Paint可以粘贴表格单元。实际上,粘贴的只是这些单元的位图图像,不是真的表格单元。甚至可以粘贴Excel数据到记事本,因为格式中包含CF_TEXT。提供多种格式的表格数据,Excel提高了它的剪贴板数据的可移植性。

为每一种格式调用::SetClipboardData就可以在剪贴板上放置多个项目:

::SetClipboardData (nID, hPrivateData);
::SetClipboardData (CF_BITMAP, hBitmap);
::SetClipboardData (CF_TEXT, hTextData);

19.1.4 查询有效数据格式

查询某种格式的剪贴板数据是否有效,可以调用::GetClipboardData,并查看返回值是否等于NULL。有时需要知道所有有效的格式,并从中选择一个最需要的。可以使用下面的函数:

Function Description
CountClipboardFormats 返回可用格式的个数
EnumClipboardFormats 枚举可用的格式
IsClipboardFromatAvailable 指示某一格式是否有效
GetPriorityClipboardFormat 获取一个具有优先级别的列表,表示哪一个最先有效

::IsClipboardFormatAvailable是最简单的一个。要查看CF_TEXT格式是否有效,可以这样调用:

if (::IsClipboardFormatAvailable (CF_TEXT)) {
// Yes, it’s available
}
else {
// No, it’s not available
}

这个函数常用于实现Edit菜单Paste命令的更新处理函数。

即使剪贴板没有打开,::IsClipboardFormatAvailable也可以工作。但是不要忘了,这时剪贴板很容易被改变。不要这样编写代码:

if (::IsClipboardFormatAvailable (CF_TEXT)) {
if (::OpenClipboard (m_hWnd)) {
HANDLE hData = ::GetClipboardData (CF_TEXT);
LPCSTR pData = (LPCSTR) ::GlobalLock (hData);

::CloseClipboard ();
}
}

这个代码是有问题的。在多任务环境中,::IsClipboardFormatAvailable执行后,调用::GetClipboardData之前,虽然机会很小,但是确实有机会改变剪贴板的数据。因此,要打开剪贴板之后再调用这个函数:

if (::OpenClipboard (m_hWnd)) {
if (::IsClipboardFormatAvailable (CF_TEXT)) {
HANDLE hData = ::GetClipboardData (CF_TEXT);
LPCSTR pData = (LPCSTR) ::GlobalLock (hData);

}
::CloseClipboard ();
}

这段代码就不会出现什么问题,因为只有打开剪贴板的应用才能改变剪贴板的内容。

可以使用::EnumClipboardFormats循环访问有效剪贴板格式的列表。例如:

if (::OpenClipboard (m_hWnd)) {
UINT nFormat = 0; // Must be 0 to start the iteration.
while (nFormat = ::EnumClipboardFormats (nFormat)) {
// Next Clipboard format is in nFormat.
}
::CloseClipboard ();
}

到达表尾时::EnumClipboardFormats返回0,因此获取最后一个有效模式后,循环结束。如果只是想知道有效模式的个数,调用::CountClipboardFormats。

::GetPriorityClipboardFormat简化了查询多个剪贴板格式的过程。假设你的应用可以以私有格式nID、CF_TEXT、CF_BITMAP格式粘贴数据。你希望使用nID,如果无效,用CF_TEXT代替,如果也无效,再用CF_BITMAP代替。不要这样编写代码:

if (::OpenClipboard (m_hWnd)) {
if (::IsClipboardFormatAvailable (nID)) {
// Perfect!
}
else if (::IsClipboardFormatAvailable (CF_TEXT)) {
// Not the best, but I’ll take it.
}
else if (::IsClipboardFormatAvailable (CF_BITMAP)) {
// Better than nothing.
}
::CloseClipboard ();
}

需要这样编写代码:

UINT nFormats[3] = {
nID, // First choice
CF_TEXT, // Second choice
CF_BITMAP // Third choice
};
if (::OpenClipboard (m_hWnd)) {
UINT nFormat = ::GetPriorityClipboardFormat (nFormats, 3);
if (nFormat > 0) {
// nFormat holds nID, CF_TEXT, or CF_BITMAP.
}
::CloseClipboard ();
}

::GetPriorityClipboardFormat的返回值是有效格式列表中第一个格式的ID。如果没有可用的格式,则返回-1;如果剪贴板为空,则返回0。

19.1.5 延迟再现

传统剪贴板有一个局限性,剪贴板上的所有数据都要保存在内存上。对于文本字符串和其它简单数据类型,可以快速有效地传递。但是,假设复制了一个10MB的位图到剪贴板。清空剪贴板之前,位图都要占用10MB的内存。而如果没有人粘贴这个位图,给它分配的内存就毫无用处。

为了避免这种浪费,Windows支持延迟提交。即直到需要的时候才将数据复制到剪贴板。它是怎样工作的?首先,用有效的剪贴板格式和NULL数据句柄调用::SetClipboardData。然后,响应WM_RENDERFORMAT消息,调用::SetClipboardData将数据真正地放入剪贴板。应用调用::GetClipboardData请求获取指定格式的数据时,就会发送WM_RENDERFORMAT消息。如果没有人请求数据,就不会传递这条消息,就无需分配10MB的内存。要注意,该消息的处理函数不应该调用::OpenClipboard和::CloseClipboard,因为接收该消息的窗口,收到消息时就占有了剪贴板。

处理WM_RENDERFORMAT消息的应用还必须处理WM_RENDERALLFORMATS消息。当应用终止而剪贴板拥有应用放置的NULL数据句柄时,就会发送这条消息。该消息处理函数的任务是打开剪贴板、传递应用承诺提供的数据、关闭剪贴板。将数据放入剪贴板,保证使用延迟提交的应用终止后,其它应用可以使用这些数据。

第三个剪贴板消息是WM_DESTROYCLIPBOARD,也在延迟提交中使用。这条消息通知应用不需要再提供延迟提交数据。当其它应用调用::EmptyClipboard时,发送该消息。在WM_RENDERALLFORMATS消息之后也发送该消息。如果你拥有响应WM_RENDERFORMAT和WM_RENDERALLFORMATS所需的资源,可以在收到WM_DESTROYCLIPBOARD消息时安全释放它们。

下面给出一个MFC使用延迟提交复制位图到剪贴板的方法:
// In CMyWindow’s message map
ON_COMMAND (ID_EDIT_COPY, OnEditCopy)
ON_WM_RENDERFORMAT ()
ON_WM_RENDERALLFORMATS ()

// Elsewhere in CMyWindow
void CMyWindow::OnEditCopy ()
{
::SetClipboardData (CF_BITMAP, NULL);
}
void CMyWindow::OnRenderFormat (UINT nFormat)
{
if (nFormat == CF_BITMAP) {
// Make a copy of the bitmap, and store the handle in hBitmap.

::SetClipboardData (CF_BITMAP, hBitmap);
}
}
void CMyWindow::OnRenderAllFormats ()
{
::OpenClipboard (m_hWnd);
OnRenderFormat (CF_BITMAP);
::CloseClipboard ();
}

这个例子不完全实用,因为复制位图到剪贴板和从剪贴板获取位图之间,位图存在改变的可能性,OnEditCopy将被迫复制位图。但是,想一下,如果OnEditCopy复制了位图,延迟提交就失去了意义。延迟提交是节省内存的工具,但是,如果应用被迫复制每一项,为什么不直接复制?

不必如此,可以保存到硬盘上。下面是修改后的程序:

// In CMyWindow’s message map
ON_COMMAND (ID_EDIT_COPY, OnEditCopy)
ON_WM_RENDERFORMAT ()
ON_WM_RENDERALLFORMATS ()
ON_WM_DESTROYCLIPBOARD ()

// Elsewhere in CMyWindow
void CMyWindow::OnEditCopy ()
{
// Save the bitmap to a temporary disk file.

::SetClipboardData (CF_BITMAP, NULL);
}
void CMyWindow::OnRenderFormat (UINT nFormat)
{
if (nFormat == CF_BITMAP) {
// Re-create the bitmap from the data in the temporary file.

::SetClipboardData (CF_BITMAP, hBitmap);
}
}
void CMyWindow::OnRenderAllFormats ()
{
::OpenClipboard (m_hWnd);
OnRenderFormat (CF_BITMAP);
::CloseClipboard ();
}
void CMyWindow::OnDestroyClipboard ()
{
// Delete the temporary file.
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值