剪贴板操作

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

若要将数据置于剪贴板上或从剪贴板中检索数据,窗口必须首先使用 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 对象被清空时,应用程序本身必须释放内存对象