天天看点

12.2 剪贴板的高级用法

摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P457

        如前所述,在数据准备好后向剪贴板传输数据需要四个函数调用:

OopnClipboard (hwnd);
EmptyClipboard();
SetClipboardData(iFormat, hGlobal);
CloseClipboard();
           

        而要取得这些数据则需要三个函数调用:

OpenClipboard(hwnd);
hGlobal = GetClipboardData(iFormat);
[其他程序行]
CloseClipboard();
           

        你可以复制一份剪贴板数据,或在 GetClipboardData 和 CloseClipboard 调用之间以其他方式使用数据。这种方法也许在大多数情况下对你就足够了,但你也可以用更复杂的方式使用剪贴板。

12.2.1  使用多种数据项

        打开剪贴板把数据放入时,必须调用 EmptyClipboard 告诉 Windows 释放或删除剪贴板里的内容。不能在剪贴板现有内容后添加数据。所以在这种意义上,剪贴板每次只保存一个数据项。

        然而,在 EmptyClipboard 和 CloseClipboard 调用之间,你可以多次调用 SetClipboardData,并且每次都使用不同的剪贴板样式。比如,如果想在剪贴板里存储一个短的文本字符串,那么你可以把这段文本写到图元文件或位图里。这样,这个字符串不仅可以被能从剪贴板读文本的程序访问,也能被可以从剪贴板里读位图和图元文件的程序访问。当然,这些程序没法轻易判断出图元文件或位图里其实含有字符串。

        如果想把多个句柄写到剪贴板,则应该对每个句柄都调用 SetClipboardData:

OopnClipboard (hwnd);
EmptyClipboard();
SetClipboardData(CF_TEXT, hGlobalText);
SetClipboardData(CF_BITMAP, hBitmap);
SetClipboardData(CF_METAFILEPIC, hGlobalMFP);
CloseClipboard();
           

当这三种数据类型都在剪贴板里时,如果调用 IsClipboardFormatAvailable,并传入 CF_TEXT、CF_BITMAP 或 CF_METAFILEPIC 参数,结果都将返回 TRUE。要取得这些句柄,程序可以调用

hGlobalText = GetClipboardData(CF_TEXT);
           

或者

hBitmap = GetClipboardData(CF_BITMAP);
           

或者

hGlobalMFP = GetClipboardData (CF_METAFILEPICT);
           

        等程序下次调用 EmptyClipboard 时,Windows 会释放或删除剪贴板里的这三个句柄。

        不要用这个技巧向剪贴板里添加不同的文本、位图或图元文件格式。只应使用一种文本、位图和图元文件格式。就像我提到过的,Windows 会在 CF_TEXT、CF_OEMTEXT 和 CF_UNICODETEXT 之间转换。它也会在 CF_BITMAP 和 CF_DIB,以及在 CF_METAFILEPICT 和 CF_ENHMETAFILE 之间转换。

        程序可以打开剪贴板然后调用 EnumClipboardFormats 来判断剪贴板里存储的数据格式。一开始,先把变量 iFormat 设置成 0:

iFormat = 0;
OpenClipboard(hwnd);
           

现在连续调用 EnumClipboardFormats,从 0 作为参数开始。 对于剪贴板当前的每个数据格式,此函数都会返回一个正的 iFormat 值。当函数返回值为 0 时,枚举就结束了:

while (iFormat = EnumClipboardFormats(iFormat))
{
    [针对每个 iFormat 值的操作]
}
CloseClipboard();
           

        要得到当前剪贴板里不同格式的数目,可以调用:

iCount = CountClipboardFormats();
           

12.2.2  延迟呈现

        把数据放到剪贴板里时,一般应把数据复制一份并且给剪贴板一个句柄,这个句柄指向含有被复制数据的全局内存块。 对于非常大的数据项,这种方法会造成内存浪费。如果用户从来不把数据粘贴到另一个程序,那么这块内存空间就会一直被占用,直到被其他数据代替。

        借助于“延迟呈现”技术,这个问题是可以避免的。这个问题是可以避免的。在这项技术中,程序实际上并不需要提供数据,直到另一个程序需要这些数据。你只需要简单地调用 SetClipboardData 并传入 NULL,而不是给 Windows 传数据句柄:

OopnClipboard (hwnd);
EmptyClipboard();
SetClipboardData(iFormat, NULL);
CloseClipboard();
           

        可以用 iFormat 的不同值多次调用 SetClipboardData。对于其中一些调用,可以用 NULL 当参数;而对另外一些,可以用实际的句柄。

        以上操作再简单不过,但是从现在开始,过程变得有些复杂。当另一个程序调用 GetClipboardData 时,Windows 将检查那个格式的句柄是否为 NULL。如果是,Windows 就会给“剪贴板所有者”(即你的程序)发送一条消息,请求一个实际的数据句柄。你的程序这时必须给出句柄。

        更具体来说,“剪贴板所有者”是把数据放到剪贴板里的最后一个窗口。当某个程序调用了 OpenClipboard 函数,Windows 就把此函数需要的窗口句柄存储下来。这个句柄标识了打开剪贴板的窗口。在收到 EmptyClipboard 调用时,Windows 会把这个窗口定为新的剪贴板所有者。

        使用延迟呈现技术的程序必须在它的窗口过程中处理三个消息:WM_RENDERFORMAT、WM_RENDERALLFORMATS 和 WM_DESTROYCLIPBOARD。当另一个程序调用 GetClipboardData 时,Windows 给你的窗口过程发送WM_RENDRFORMAT 消息。该消息的 wParam 值是 Windows 需要的格式。你在处理 WM_RENDERFORMAT 消息时,不要打开并清空剪贴板。只需简单地按照 wParam 给出的格式创建一个全局内存块,把数据传给它,并且用正确的格式和全局句柄调用 SetClipboardData 即可。显然,在处理 WM_RENDERFORMAT 消息时,需要在程序里保留信息以正确地建立数据。当另一个程序调用 EmptyClipboard 时,Windows 给你的程序发送一个 WM_DESTROYCLIPBOARD 消息。这个消息指出不再需要用于建立剪贴板数据的信息了。你的程序不再是剪贴板所有者。

        如果你的程序在自己仍然是剪贴板所有者时终止,并且剪贴板仍然拥有程序用 SetClipboardData 设置的 NULL 数据句柄,那么你将收到 WM_RENDERALLFORMATS 消息。此时你应该打开剪贴板,清空它,把数据放入全局内存块,然后对每个格式调用 SetClipboardData。最后关闭剪贴板。WM_RENDERALLFORMATS 消息是你的窗口过程最后接收到的消息之一。它的后面会跟有一个 WM_DESTROYCLIPBOARD 消息——因为你已经呈现了所有数据——再之后是普通的 WM_DESTROY 消息。

        如果你的程序只能把一种数据格式传给剪贴板(比如文本),那么你可以一块处理 WM_RENDERALLFORMATS 和 WM_RENDERFORMAT 消息。代码如下所示:

case WM_RENDERALLFORMATS:
    OpenClipboard(hwnd);
    EmptyClipboard();
                                    // fall through
case WM_RENDERFORMAT:
    [把文本放入全局内存块]
    SetClipboardData (CF_TEXT, hGlobal);

    if (message == WM_RENDERALLFORMATS)
        CloseClipboard();
    return 0;
           

        如果你的程序使用了若干个剪贴板格式,你也许应该只针对 wParam 需求的格式处理 WM_RENDERFORMAT 消息。你无需处理 WM_DESTROYCLIPBOARD 消息,除非对于你的程序来说保留创建数据的信息时一种负担。

12.2.3  私有数据类型

        到目前为止,我们只处理了 Windows 定义的标准剪贴板类型。然而,你也许想用剪贴板存储“私有数据类型”。许多字处理程序使用这种技术来存储含有字体和格式信息的文本。

        乍一看,这个概念也许没有意义。如果使用剪贴板的目的是在应用程序之间传输数据,为什么剪贴板应该含有只能被一个应用程序理解的数据呢?答案很简单:剪贴板存在的原因之一也是为了把数据从程序自身传入和传出(或者是在同一个程序的不同实例之间传输),显然,这些实例能理解同样的私有格式。

        使用私有数据格式有几种方法。最简单的方法涉及从表面上看符合标准剪贴板格式的数据(即文本、位图或图元文件),但是该数据包含只有你的程序才能理解的含义。这种情况下,在调用 SetClipboardData 和 GetClipboardData 时,应使用以下 iFormat 值之一:CF_DSPTEXT、CF_DSPBITMAP、CF_DSPMETAFILEPICT 或 CF_DSPENHMETAFILE。(字母 DSP 代表 “display”)。这些格式允许 Windows 剪贴板查看器把数据显示成文本、位图或图元文件。然而,使用普通的 CF_TEXT、CF_BITMAP、CF_DIB、CF_METAFILEPICT 或者 CF_ENHMETAFILE 格式来调用 GetClipboardData 的其他程序将不会得到这些数据。

        如果使用这些格式之一把数据放入剪贴板,则必须用同样的格式把数据取出。但是如何得知数据时来自于你的程序的另一个实例,或是来自于另一个使用这些格式之一的程序呢?以下是一种方法:首先取得剪贴板所有者,你可以调用以下函数:

hwndClipOwner = GetClipboardOwner();
           

然后取得这个窗口句柄的窗口类的名称。

TCHAR szClassName[32];
[其他程序行]
GetClassName(hwndClipOwner, szClassName, 32);
           

如果类名和你的程序名一样,那么数据是被你的程序的另一个实例放入剪贴板的。

        使用私有格式的第二种方法涉及 CF_OWNERDISPLAY 标志。SetClipboardData 的全局内存句柄是 NULL:

SetClipboardData (CF_OWNERDISPLAY, NULL);
           

这种方法被一些字处理程序用来在 Windows 自带的剪贴板查看其的客户区内显示格式化文本。 很明显, 剪贴板查看其并不知道怎样显示这种格式化文本。 当字处理程序指明了 CF_OWNERDISPLAY 格式时,将由它来负责绘制剪贴板查看器的客户区。

        由于全局内存句柄是 NULL,所以调用 SetClipboardData 并传入 CF_OWNERDISPLAY 格式的程序(剪贴板所有者)必须处理由 Windows 发送给它的延迟呈现消息,此外还要处理另外 5 个消息。下面的 5 个消息是由剪贴板查看器发送给剪贴板所有者的。

  • WM_ASKCBFORMATNAME    剪贴板查看器把这个消息发送给剪贴板所有者以得到数据格式的名称。lParam 参数是一个指向缓冲区的指针,wParam 是这个缓冲区能容纳的字符的最大数据。剪贴板所有者必须把剪贴板格式的名称复制到这个缓冲区。
  • WM_SIZECLIPBOARD    这个消息告诉剪贴板所有者,剪贴板查看器客户区的大小改变了。wParam 参数是一个指向剪贴板查看器的句柄,lParam 是一个指向含有新的大小的 RECT 结构的指针。如果 RECT 结构包含的全是 0,则说明剪贴板查看器正在被销毁或最小化。还有,尽管 Windows 剪贴板查看器只允许它自己的一个实例在运行,但其他剪贴板查看器也可以向剪贴板所有者发送这个消息。对于剪贴板所有者来说,处理多个剪贴板查看器消息虽非不可能(鉴于 wParam 标识了特定的查看器),但是也绝非轻而易举。
  • WM_PAINTCLIPBOARD    这个消息告诉剪贴板所有者要更新剪贴板查看器的客户区。同样,wParam 是指向剪贴板查看器窗口的句柄。lParam 参数是指向 PAINTSTRUCT 结构的全局句柄。剪贴板所有者可以锁定此句柄,并从此结构的 hdc 字段取得一个指向剪贴板查看器设备环境的句柄。
  • WM_HSCROLLCLIPBOARD 和 WM_VSCROLLCLIPBOARD    这两个消息告诉剪贴板所有者用户移动了剪贴板查看器的滚动条。wParam 参数是指向剪贴板查看器窗口的句柄,lParam 的低位字是滚动请求;如果该低位字是 SB_THUMBPOSITION,那么 lParam 的高位字就是滚动条滑块的位置。

        处理这些消息也许看起来得不偿失。然而,这个过程给用户提供了一个好处:当把文本从字处理程序中复制到剪贴板时,用户会欣慰地发现在剪贴板查看器的客户区显示文本仍然保持其原有的格式。

        使用私有剪贴板格式的第三种方法是注册你自己的剪贴板格式名。你向 Windows 提供这个格式的名称,Windows 会给你的程序一个数值,在 SetClipboardData 和 GetClipboardData 里,可以把这个数值用作格式参数。使用这种方法的程序通常也是按照标准格式之一把数据复制到剪贴板。这种方法允许剪贴板查看器在其客户区里显示数据(无需像 CF_OWNERDISPLAY 那么费力)并且允许其他程序从剪贴板里复制数据。

        举个例子,假设我们写了一个绘制矢量的程序,它把数据以位图格式、图元文件格式和它自己注册的剪贴板格式复制到剪贴板里。剪贴板查看器将会显示图元文件或位图。其他可以从剪贴板里读取位图或图元文件的程序会相应获取这些格式。然而,当绘制矢量的程序本身需要从剪贴板中读数据时,它将以它自己注册的格式复制数据,因为该格式可能含有比位图或图元文件更多的信息。

        想要注册新的剪贴板格式,程序可以调用以下函数:

iFormat = RegisterClipboardFormat (szFormatName);
           

iFormat 值介于 0xC000 和 0xFFFF 之间。剪贴板查看器(或者通过调用 EnumClipboardFormats 取得当前剪贴板所有格式的程序)要获得此格式的 ASCII 名称,可以调用以下函数:

GetClipboardFormatName (iFormat, psBuffer, iMaxCount);
           

Windows 最多复制 iMaxCount 个字符到 psBuffer 中。

        用这种方法把数据复制到剪贴板中的程序员也许会公布格式名和数据的实际格式。如果程序变得受欢迎,其他程序就可以从剪贴板中以这种格式复制数据。