精通
英语
和
开源
,
擅长
开发
与
培训
,
胸怀四海
第一信赖
只需几行代码,您就可以轻松地将剪贴板功能添加到应用程序中。
在第一部分的四部分系列文章中,以编程方式将数据传输到Windows剪贴板和从Windows剪贴板传输数据,我将解释基于剪贴板API使用剪贴板的基本步骤。在我完成了基本步骤之后,无论剪贴板上有什么数据格式,都会使用这些步骤,我将介绍一个演示应用程序,说明如何以编程方式将简单(ANSI)文本传输到剪贴板和从剪贴板传输。
实际上有两个不同的机制用于连接到剪贴板。第一个涉及使用Windows剪贴板API,第二个使用OLE。由于剪贴板API是迄今为止最常用的方法,所以这个小系列中的大部分演示将使用这种技术。如果你熟悉Windows API -尤其是内存分配GlobalAlloc和GlobalLock功能 - 使用剪贴板所需的步骤将更容易学习。此时需要实现的另一件事是,无论数据传输到剪贴板的类型如何,当您将数据传输到剪贴板或从剪贴板传输数据时,都会采用相同的基本编程步骤。图1显示了标准UML序列图中的这些步骤,随后的章节将详细介绍每个步骤。
图1:使用Windows剪贴板API将数据传输到剪贴板的标准步骤。
因为剪贴板不是某种全局数据缓冲区,所以将“将数据放在剪贴板”上的说法确实是一个不正确的用法。实际上,剪贴板只是一个数据缓冲区的句柄,该数据缓冲区是由应用程序创建和维护的,该数据缓冲区使得该数据可用于其他应用程序。由于数据必须支持所有进程访问,Windows API函数GlobalAlloc和GlobalLock被使用。
作为一个快速复习,这里是这两个函数的语法及其工作原理:
该GlobalAlloc函数用于分配保留可用数据的全局内存的全局块,这些数据会来自于剪贴板。
HGLOBAL GlobalAlloc(UINT uFlags, SIZE_T dwBytes)
该uFlags参数用于指定Windows如何分配内存。如果指定值为0(或NULL),则GMEM_FIXED假定为值。该参数可以与逻辑或运算符组合。
uFlag值GHND说明 与GMEM_MOVEABLE | GMEM_ZEROINT GMEM_FIXED 默认值相同,这分配了一个固定(即不可移动)存储器的块。GlobalAlloc 指定时的返回值GMEM_FIXED是一个指针。 GMEM_MOVEABLE 在Windows中,内存块从不在物理内存中移动。但是,可以在默认堆中移动。因此,指定时的返回值GMEM_MOVEABLE是内存的句柄。 GPTR 此值结合GMEM_FIXED和GMEM_ZEROINT,稍后将分配的内存初始化为全零。
注意 - 由于GMEM_FIXED为调用的uFlag参数指定一个值GlobalAlloc将返回一个指针,而指定GMEM_MOVEABLE将返回一个句柄,这两个值是互斥的。
该参数dwBytes是一个双字,使您能够指定要分配的缓冲区的大小。
您需要调用为数据分配内存的第二个功能是Windows API函数GlobalLock。
LPVOID GlobalLock(HGLOBAL hMem)
该函数非常简单,并且仅作为GlobalAlloc函数返回的句柄的唯一参数。
一旦您分配了全局内存并获得了一个指针,就可以将所需的数据复制到该全局内存缓冲区中。这样做将取决于您转移的数据类型,因此我将在本文中的三个演示中详细介绍此步骤。
这是一个非常重要但经常被忽视的步骤。根据Windows文档,一旦分配了内存并将数据插入到其他进程中,您不必再次联系此内存。原因很简单。一旦你使剪贴板机制知道了新的数据(下一步),Windows现在可以控制那个内存,并且你的任何进一步的篡改可能会使数据的完整性无效。
要解锁全局内存块,请调用GlobalUnlockAPI函数。这是函数的非常简单的语法,其中hMem参数是从GlobalAlloc函数返回的句柄。
BOOL GlobalUnlock(HGLOBAL hMem)
该OpenClipboard功能使您能够锁定剪贴板,以便其他进程可以在尝试访问该数据时修改其内容。此函数的语法如下:
BOOL OpenClipboard(HWND hWndNewOwner)
在hWndNewOwner简单地把打开的剪贴板和你给定的窗口相关联。如果您不需要这样做(大多数不要),该参数的值只需留为NULL,在这种情况下,Windows将将打开的剪贴板与当前任务相关联。本书中的所有演示将使用后一种技术,因为我个人从未看到太多情况下需要与特定窗口的关联。
这一步需要初始化剪贴板,并通过该EmptyClipboard功能实现。当您调用此功能时,Windows将释放与剪贴板关联的全局内存。如果你记得,早些时候我说Windows将负责这个任务。因此,任何使用剪贴板的应用程序都有责任调用此函数,以防止内存泄漏。
BOOL EmptyClipboard()
请注意,在调用此函数之前,您不需要调用函数来确定是否存在任何数据,因为如果函数失败,此函数将仅返回非零值。如果成功或剪贴板不包含任何数据,该函数返回零(表示成功)。
最后,在此步骤中,您可以设置剪贴板数据。如前所述,剪贴板实际上不包含数据,而只是维护一个由应用程序获取(并填充)的全局句柄。执行此步骤的SetClipboardData功能是该功能。这也是使您能够指定要传输的数据的格式的功能(请注意以下语法)。
HANDLE SetClipboardData(UINT uFormat, HANDLE hMem)
您为uFormat参数指定的值必须是标准剪贴板格式(表2)或注册格式。注册格式是用户定义的格式,可以指定应用程序特定数据的格式。稍后我们将进入这种格式。
表2 - 标准剪贴板格式
uFormat参数值 |
hMem值 |
CF_BITMAP |
处理位图(您将在本文系列的第二个演示中看到如何使用此格式) |
CF_DIB |
处理一个BITMAPINFO结构,然后是构成位图的位 |
CF_DIBV5 |
仅在Windows 2000中使用,这也是BITMAPINFO后续位表示位图颜色信息和位图图像的结构的句柄。 |
CF_DIF |
处理软件艺术数据交换格式(SADIF)缓冲区 |
CF_DSPBITMAP |
处理与特定于应用程序的格式相关联的位图显示格式。指向的数据必须是可以以位图格式显示的数据。 |
CF_DSPENHMETAFILE |
处理与私有格式相关联的增强型图元文件显示格式。在这种情况下,数据必须以增强的图元文件格式显示,而不是私有格式的数据。 |
CF_DSPMETAFILEPICT |
处理与私有格式相关的元文件图片显示格式。指向的数据必须以元文件图片格式显示,而不是私有格式的数据。 |
CF_DSPTEXT |
处理与私有格式相关联的文本显示格式。数据必须以文本格式显示,以代替私有格式的数据。 |
CF_ENHMETAFILE |
处理增强型图元文件(HENHMETAFILE) |
CF_GDIOBJFIRST 通过CF_GDIOBJLAST |
这个整数范围代表应用程序定义的GDI对象剪贴板格式。请注意,GlobalFree当剪贴板被清空时,由这些值表示的句柄不会自动删除。此外,该hMem参数不是GDI对象的句柄,而是由GlobalAlloc具有该GMEM_MOVEABLE标志的函数分配的句柄。 |
CF_HDROP |
键入的句柄HDROP标识通过剪贴板传输的文件列表 - 通常用于拖放操作。该DragQueryFiles函数用于检索有关这些文件的信息。 |
CF_LOCALE |
处理与剪贴板中文本相关联的区域设置标识符 |
CF_METAFILEPICT |
处理图元文件图片格式(METAFILEPICT)结构 |
CF_OEMTEXT |
处理到包含OEM字符集中的字符的文本格式。在这种格式中,每一行都必须以回车/换行符(CR / LF)组合方式终止。空字符表示使用此格式的数据结尾(EOD)。 |
CF_OWNERDISPLAY |
如果应用程序指定了uFormat值CF_OWNERDISPLAY,则该hMem值必须为 NULL。此外,该应用程序是那么对于显示和更新剪贴板查看器窗口和处理以下消息负责:WM_ASKCBFORMATNAME,WM_HSCROLLCLIPBOARD, WM_PAINTCLIPBOARD,WM_SIZECLIPBOARD,WM_VSCROLLCLIPBOARD。 |
CF_PALETTE |
处理到一个调色板。 |
CF_PENDATA |
处理用于Pen Computing的Microsoft Windows的笔扩展的数据。 |
CF_PRIVATEFIRST 通过 CF_PRIVATELAST |
就像使用CF_GDIOBJFIRST- CF_GDIOBJLAST范围,这些整数代表私人剪贴板格式的一系列价值。再次注意,与这些句柄相关联的数据不会自动释放,因此应用程序有责任执行此操作。 |
CF_RIFF |
处理比使用uFormat参数设置为的标准(WAV)格式表示的音频数据格式更复杂CF_WAVE。 |
CF_SYLK |
处理Microsoft Symbolic Link(SYLK)格式的数据。 |
CF_TEXT |
处理标准文本。使用这种格式,每行必须用回车符/换行符(CR-LF)组合终止。空字符表示数据结束(EOD)。这用于ANSI文本,而 CF_UNICODETEXT用于UNICODE文本。 |
CF_WAVE |
以标准波形格式处理音频数据:例如11 kHz或22 kHz脉码调制(PCM)。 |
CF_TIFF |
处理标签图像文件(TIFF)格式的数据 |
CF_UNICODETEXT |
为了与Windows NT / 2000或更高版本一起使用,此格式用于UNICODE数据,而不是ANSI文本(由CF_TEXT值表示)。 |
当应用程序完成检查或修改剪贴板数据时,将CloseClipboard 调用该函数。这具有解锁剪贴板的作用,以便其他应用程序可以访问它。
BOOL CloseClipboard()
我们首先将演示如何将文本传送到剪贴板和从剪贴板传输文本。虽然这一般都是由于Windows 2000 / Me上下文菜单(右键单击任何编辑控件将导致包含标准剪贴板功能的菜单),但仍然有一些非常有效的用途。一个例子是如果你想传递简单的基于文本的数据到另一个应用程序。然而,为了保持尽可能干净的示例,该演示将包含一个具有两个编辑控件的对话框。一个编辑控件将使您能够在其中输入文本并将该文本复制到剪贴板,而第二个编辑控件将是只读控件。一个按钮将使您能够将剪贴板中的文本粘贴到第二个控件中。这演示相对简单,
此时,创建一个名为SimpleTextTransfer的基于对话框的应用程序。完成后,修改默认对话框,使其看起来像图2所示
图2:使用剪贴板测试的简单对话框来传输标准ANSI文本。
修改对话框后,需要对对话框的控件进行以下更改。
完成对话框控件的属性后,为“复制”按钮的BN_CLICKED消息添加事件处理程序, 并对其进行修改,使其如下所示。我在整个代码中有注释,使代码易于理解。
void CSimpleTextTransferDlg::OnBnClickedBtncopy() { if (UpdateData()) { CString strData; m_edtToClipboard.GetWindowText(strData); // test to see if we can open the clipboard first before // wasting any cycles with the memory allocation if (OpenClipboard()) { // Empty the Clipboard. This also has the effect // of allowing Windows to free the memory associated // with any data that is in the Clipboard EmptyClipboard(); // Ok. We have the Clipboard locked and it's empty. // Now let's allocate the global memory for our data. // Here I'm simply using the GlobalAlloc function to // allocate a block of data equal to the text in the // "to clipboard" edit control plus one character for the // terminating null character required when sending // ANSI text to the Clipboard. HGLOBAL hClipboardData; hClipboardData = GlobalAlloc(GMEM_DDESHARE, strData.GetLength()+1); // Calling GlobalLock returns to me a pointer to the // data associated with the handle returned from // GlobalAlloc char * pchData; pchData = (char*)GlobalLock(hClipboardData); // At this point, all I need to do is use the standard // C/C++ strcpy function to copy the data from the local // variable to the global memory. strcpy(pchData, LPCSTR(strData)); // Once done, I unlock the memory - remember you // don't call GlobalFree because Windows will free the // memory automatically when EmptyClipboard is next // called. GlobalUnlock(hClipboardData); // Now, set the Clipboard data by specifying that // ANSI text is being used and passing the handle to // the global memory. SetClipboardData(CF_TEXT,hClipboardData); // Finally, when finished I simply close the Clipboard // which has the effect of unlocking it so that other // applications can examine or modify its contents. CloseClipboard(); } } }
您可能已经猜到,将数据复制到剪贴板并将其剪切到剪贴板之间的唯一区别是,在后一种情况下,在传输发生后,数据将被删除。因此,在这一点上,为剪切按钮的BN_CLICKED消息添加一个事件处理程序。一旦这样做,修改该处理程序,使其看起来如下所示:
void CSimpleTextTransferDlg::OnBnClickedBtncut() { OnBnClickedBtncopy(); m_edtToClipboard.SetWindowText(""); }
在这一点上,应用程序可以将数据放在剪贴板上,但是还没有允许将数据粘贴到对话框中。因此,现在我们会处理好。
与复制和剪切按钮一样,为粘贴按钮的BN_CLICKED消息添加一个事件处理程序。
void CSimpleTextTransferDlg::OnBnClickedBtnpaste() { // Test to see if we can open the clipboard first. if (OpenClipboard()) { // Retrieve the Clipboard data (specifying that // we want ANSI text (via the CF_TEXT value). HANDLE hClipboardData = GetClipboardData(CF_TEXT); // Call GlobalLock so that to retrieve a pointer // to the data associated with the handle returned // from GetClipboardData. char *pchData = (char*)GlobalLock(hClipboardData); // Set a local CString variable to the data // and then update the dialog with the Clipboard data CString strFromClipboard = pchData; m_edtFromClipboard.SetWindowText(strFromClipboard); // Unlock the global memory. GlobalUnlock(hClipboardData); // Finally, when finished I simply close the Clipboard // which has the effect of unlocking it so that other // applications can examine or modify its contents. CloseClipboard(); } }
此时,编译和测试应用程序。你的结果应该与图3中看到的相似。
图3:只需几行代码,就可以轻松地将剪贴板功能添加到应用程序中。
但是,我们的小测试只有一个问题。如果您单击<Alt> <打印屏幕>组合键,则会将当前窗口的位图复制到剪贴板。您可以通过打开PaintBrush应用程序并将图像粘贴到新的位图图像中进行测试。但是,如果将图像复制到剪贴板,然后单击演示应用程序上的“粘贴”按钮,“剪贴板”编辑控件中的文本将被清除,不会出现!这是因为演示正在尝试在剪贴板上使用数据,而无需首先确定数据是否适用于此应用程序。
为了检查剪贴板上数据的格式,只需使用该IsClipboardAvailable功能即可。
BOOL IsClipboardFormatAvailable(UINT format)
该format参数可以是表1中列出的任何值。下面BN_CLICKED是对话框“粘贴”按钮的修改处理程序。变化代码已经黑体指明。
void CSimpleTextTransferDlg::OnBnClickedBtnpaste() { // Test to see if we can open the clipboard first. if (OpenClipboard()) { if (::IsClipboardFormatAvailable(CF_TEXT) || ::IsClipboardFormatAvailable(CF_OEMTEXT)) { // Retrieve the Clipboard data (specifying that // we want ANSI text (via the CF_TEXT value). HANDLE hClipboardData = GetClipboardData(CF_TEXT); // Call GlobalLock so that to retrieve a pointer // to the data associated with the handle returned // from GetClipboardData. char *pchData = (char*)GlobalLock(hClipboardData); // Set a local CString variable to the data // and then update the dialog with the Clipboard data CString strFromClipboard = pchData; m_edtFromClipboard.SetWindowText(strFromClipboard); // Unlock the global memory. GlobalUnlock(hClipboardData); } else { AfxMessageBox("There is no text data (ANSI) on the Clipboard."); } // Finally, when finished I simply close the Clipboard // which has the effect of unlocking it so that other // applications can examine or modify its contents. CloseClipboard(); } }
现在,如果您运行此应用程序,请复制位图(或任何未定义为CF_TEXT或CF_OEMTEXT)的数据,然后按演示的“粘贴”按钮,您将看到如图4所示的消息。
图4:在尝试使用剪贴板之前,应始终检查数据的格式。