剪贴板操作

在剪切、复制或粘贴数据时,窗口应使用剪贴板。 窗口在剪贴板上放置数据进行剪切和复制操作,并从剪贴板中检索用于粘贴操作的数据。 以下部分介绍这些操作和相关问题。

若要将数据放在剪贴板上或从剪贴板中检索数据,窗口必须首先使用 OpenClipboard 函数打开剪贴板。 一次只能打开一个窗口。 若要找出打开剪贴板的窗口,请调用 GetOpenClipboardWindow 函数。 完成后,窗口必须通过调用 CloseClipboard 函数关闭剪贴板。

本部分将讨论以下主题。

剪切和复制操作

若要在剪贴板上放置信息,窗口首先使用 EmptyClipboard 函数清除以前的任何剪贴板内容。 此函数将 WM_DESTROYCLIPBOARD 消息发送到上一个剪贴板所有者,释放与剪贴板上数据关联的资源,并将剪贴板所有权分配给打开剪贴板的窗口。 若要找出哪个窗口拥有剪贴板,请调用 GetClipboardOwner 函数。

清空剪贴板后,窗口会将剪贴板上的数据置于尽可能多的剪贴板格式中,从最描述性的剪贴板格式排序到最不具有描述性。 对于每个格式,窗口调用 SetClipboardData 函数,并指定格式标识符和全局内存句柄。 内存句柄可以为 NULL,指示窗口在请求时呈现数据。 有关详细信息,请参阅 延迟呈现

粘贴操作

若要从剪贴板检索粘贴信息,窗口首先确定要检索的剪贴板格式。 通常,窗口使用 EnumClipboardFormats 函数枚举可用的剪贴板格式,并使用它识别的第一种格式。 此方法根据在剪贴板上放置数据时设置的优先级集选择最佳可用格式。

或者,窗口可以使用 GetPriorityClipboardFormat 函数。 此函数根据指定的优先级标识最佳可用剪贴板格式。 仅识别一个剪贴板格式的窗口只需使用 IsClipboardFormatAvailable 函数来确定该格式是否可用。

确定要使用的剪贴板格式后,窗口将调用 GetClipboardData 函数。 此函数返回包含指定格式数据的全局内存对象的句柄。 窗口可以短暂锁定内存对象,以便检查或复制数据。 但是,窗口不应释放对象或长时间将其锁定。

剪贴板所有权

剪贴板所有者是与剪贴板上的信息关联的窗口。 当窗口在剪贴板上放置数据时,窗口将成为剪贴板所有者,具体而言,当它调用 EmptyClipboard 函数时。 窗口保持剪贴板所有者,直到它关闭或另一个窗口清空剪贴板。

当剪贴板清空时,剪贴板所有者将收到 WM_DESTROYCLIPBOARD 消息。 以下是窗口可能处理此消息的一些原因:

  • 窗口延迟呈现一个或多个剪贴板格式。 为了响应 WM_DESTROYCLIPBOARD 消息,窗口可能会释放它分配的资源,以便按请求呈现数据。 有关数据呈现的详细信息,请参阅 延迟呈现
  • 窗口以专用剪贴板格式将数据放置在剪贴板上。 当剪贴板被清空时,系统不会释放专用剪贴板格式的数据。 因此,剪贴板所有者在收到 WM_DESTROYCLIPBOARD 消息时应释放数据。 有关专用剪贴板格式的详细信息,请参阅 剪贴板格式
  • 使用 CF_OWNERDISPLAY剪贴板格式 将数据放置在剪贴板上。 为了响应 WM_DESTROYCLIPBOARD 消息,窗口可能会释放它用于在剪贴板查看器窗口中显示信息的资源。 有关此可选格式的详细信息,请参阅 所有者显示格式

延迟呈现

在剪贴板上放置剪贴板格式时,窗口可能会延迟以该格式呈现数据,直到需要数据。 为此,应用程序可以为 SetClipboardData 函数的 hData 参数指定 NULL。 如果应用程序支持多种剪贴板格式(部分或全部格式)呈现,则这非常有用。 通过传递 NULL 句柄,窗口仅在需要时才呈现复杂的剪贴板格式。

如果窗口延迟呈现剪贴板格式,则必须准备好在请求时呈现格式,前提是它是剪贴板所有者。 系统为尚未呈现的特定格式收到请求时,系统会向剪贴板所有者发送 一条WM_RENDERFORMAT 消息。 收到此消息后,窗口应调用 SetClipboardData 函数以请求的格式将全局内存句柄放在剪贴板上。

应用程序在调用 SetClipboardData 以响应 WM_RENDERFORMAT 消息之前,不得打开剪贴板。 打开剪贴板不是必需的,因此任何尝试都会失败,因为应用程序当前正在打开剪贴板,应用程序请求呈现格式。

如果剪贴板所有者即将销毁并延迟呈现部分或所有剪贴板格式,则会收到 WM_RENDERALLFORMATS 消息。 收到此消息后,该窗口应打开剪贴板,检查它是否仍然是具有 GetClipboardOwner 函数的剪贴板所有者,然后将有效的内存句柄放在剪贴板上,以获取它提供的所有剪贴板格式。 这可确保这些格式在剪贴板所有者被销毁后保持可用。

WM_RENDERFORMAT不同,响应 WM_RENDERALLFORMATS 的应用程序应在调用 SetClipboardData 之前打开剪贴板,以便将任何全局内存句柄放在剪贴板上。

响应 WM_RENDERALLFORMATS 消息时未呈现的任何剪贴板格式不再可供其他应用程序使用,并且剪贴板函数不再枚举。

延迟呈现指南

延迟呈现是一项性能功能,使应用程序能够避免以从未请求的格式呈现剪贴板数据。 但是,使用延迟呈现涉及应考虑的以下权衡:

  • 使用延迟呈现会向应用程序添加一些复杂性,要求它处理两条呈现窗口消息,如前所述。
  • 使用延迟呈现意味着,如果呈现数据需要足够长的时间,应用程序将失去保持 UI 响应的选项,使其对用户明显。 由于呈现延迟,如果最终需要数据,则窗口必须在处理呈现窗口消息时呈现数据,如上所述。 因此,如果数据非常耗时地呈现,应用程序可能会在呈现时明显无响应 (挂起) ,因为处理呈现窗口消息时无法处理其他窗口消息。 不使用延迟呈现的应用程序可能会改为选择在后台线程上呈现数据,以便在呈现时保留 UI 响应,可能提供进度或取消选项,这些选项在使用延迟呈现时不可用。
  • 如果最终需要数据,则使用延迟呈现会增加少量开销。 使用延迟呈现时,窗口最初使用 NULL 句柄调用 SetClipboardData 函数,如果以后需要数据,则该窗口必须响应窗口消息,并使用呈现的数据句柄再次调用 SetClipboardData 函数,如上文所述。 因此,如果最终需要数据,则使用延迟呈现会增加处理窗口消息并再次调用 SetClipboardData 函数的成本。 此成本很小,但不是零。 如果应用程序仅支持单个剪贴板格式,并且如果数据始终是最终请求的,则使用延迟呈现只会增加此少量开销, (成本因硬件而异;估计值为 10 到 100 微秒) 。 但是,如果数据较小,则使用延迟呈现的开销可能超过呈现数据的成本,这可能会使使用延迟呈现来改善性能的目的。 (在测试中,对于已处于最终形式的数据,使用延迟呈现的开销一直超过了将数据复制到剪贴板的成本(如果数据为 100 KiB 或更少)。此测试不包括呈现数据的成本,只需在呈现数据后复制数据。)
  • 延迟呈现是一个净性能优势,如果它节省的时间比增加开销多。 为了确定延迟呈现的开销,测量效果最好,但估计值为 10 到 100 微秒。 若要计算每个剪贴板格式使用延迟呈现的成本,请测量以该格式呈现数据的成本,并确定最终根据上面所述的窗口消息 (请求该格式的频率) 。 将呈现数据的成本乘以在剪贴板清空之前最终不会请求数据 (的时间百分比,或者其内容) 更改,以确定每个剪贴板格式延迟呈现的节省。 如果节省的成本超过开销,则延迟呈现是净性能优势。
  • 作为具体准则,对于仅支持单个剪贴板格式的应用程序(如文本),其中数据不显著昂贵,因此,如果数据大小为 4 KiB 或更少,请考虑将数据直接放置在剪贴板上。

内存和剪贴板

应使用具有GMEM_MOVEABLE标志的 GlobalAlloc 函数来分配要放置在剪贴板上的内存对象。

将内存对象放置在剪贴板上后,该内存句柄的所有权将传输到系统。 当剪贴板被清空并且内存对象具有以下剪贴板格式之一时,系统会通过调用指定的函数释放内存对象:

用于释放对象的函数 剪贴板格式
DeleteMetaFile
CF_DSPENHMETAFILE
CF_DSPMETAFILEPICT
CF_ENHMETAFILE
CF_METAFILEPICT
DeleteObject
CF_BITMAP
CF_DSPBITMAP
CF_PALETTE
GlobalFree
CF_DIB
CF_DIBV5
CF_DSPTEXT
CF_OEMTEXT
CF_TEXT
CF_UNICODETEXT

CF_OWNERDISPLAY
当剪贴板清空 CF_OWNERDISPLAY 对象时,应用程序本身必须释放内存对象。