Win32编程原理 - 剪切板小程序

Windows界面编程的关键在于捕获和响应各种消息。

最近编写了一个监视剪切板的小程序,总结学到的一些知识。程序的原理很简单,就是当剪切板的内容发生变化时,会向程序发送一个通知,程序每次收到这个通知都做一些处理,完成相应的功能。

具体实现步骤如下。
SetClipboardViewer函数注册加入剪切板的监视窗口列表,这个列表是一个链表结构,链表中记录了所有监视剪切板变化的窗口,我们称每一个监视剪切板的窗口为一个剪切板浏览器。

剪切板内容一旦发生变化,就会向链表中第一个剪切板浏览器发送一个编号为WM_DRAWCLIPBOARD的消息。

第一个窗口拿到这个消息就知道是剪切板发生了变化,于是就做出相应的动作。
同时还要把这个消息传递给链表中第二个剪切板浏览器,因为监视剪切板的程序可能不只有一个,而系统只负责在剪切板变化时发送给链表中的第一个剪切板浏览器。
如果第n个剪切板浏览器在收到WM_DRAWCLIPBOARD消息的时候只顾着自己处理而没有往第n+1个剪切板浏览器传递消息,那从第n+1个剪切板浏览器开始,往后的所有剪切板浏览器都无法收到此消息,也就没法实现监视剪切板的功能。

程序在用SetClipboardViewer函数注册成为剪切板浏览器的时候,SetClipboardViewer函数会返回一个值,就是自己后面的剪切板浏览器句柄,假设是next程序,也就是说,自己在收到剪切板变化消息的时候(这个消息可能是操作系统发来的也可能是其他剪切板浏览器传来的),只需要把消息传递给next程序。

以上就是剪切板浏览器能够监视剪切板变化的原理。

还有个问题就是一旦某一个剪切板浏览器退出了,那因为它不再工作,也就没法往下传送消息了,那它后面的浏览器岂不也无法收到剪切板变化的消息了。

所以程序在想要退出的时候不能“说走就走”,要通知系统自己要离开剪切板浏览器了,其实他只需要通知他前面的一个剪切板浏览器就行,让前面的剪切板浏览器以后再传递剪切板消息的时候不要给自己了,直接给自己的下一个剪切板浏览器就行。

但是自己不知道自己上一个浏览器的句柄,所以我们只管通知操作系统,操作系统再通知剪切板浏览器列表中的第一个剪切板浏览器,然后层层传递消息。
程序用ChangeClipboardChain函数通知系统要离开的剪切板浏览器,这个函数的参数包括将要从剪切板浏览器链表中删除的剪切板浏览器句柄和这个剪切板浏览器的下一个剪切板浏览器的句柄。
操作系统会发送一个编号为WM_CHANGECBCHAIN的消息给剪切板浏览器链表的第一个剪切板浏览器。
所以每一个剪切板监视程序也要在程序里写处理WM_CHANGECBCHAIN消息的代码,并且要负责将这个消息往下传递。

一旦程序收到WM_CHANGECBCHAIN消息,就要检测消息中告知的要离开的剪切板浏览器,它是否正好是自己的下一个通知对象,若是,那这个消息就是为了通知自己的,自己要修改自己以后传递剪切板消息的对象,而且不需要将这个消息继续传递下去了;
假如这个消息告知的将要离开的剪切板浏览器不是自己下一个浏览器,自己不需要做任何处理,只需要往下传递就行。

整个剪切板浏览器链表的加入退出和维护原理就是这样了。

我们的程序需要处理两种消息,并往下传递,而且我们的程序在退出前还要告知操作系统将自己从剪切板浏览器链表中删除。

还有个点就是,如果自己是最后一个加入剪切板浏览器链表的,那自己在剪切板浏览器链表第一个位置,接收的将是操作系统直接发来的消息,如果自己是第一个加入链表的,那没有下一个剪切板浏览器。

另外在编程中还遇到一个麻烦的问题就是编码转换的问题,因为程序中涉及到剪切板数据交换,而剪切板涉及到写入数据和读出数据,写入和输出往往不是在同一个程序中。
比如从程序1写入数据到剪切板,从程序2读出剪切板的数据,而又不能保证每个程序写入和读出总是用同样的编码,所以会出现乱码情况。

Windows从剪切板中往外读数据的GetClipboardData函数中,实现了一个格式转换的功能,正是对这个函数转换的不了解,造成了乱码。

下面对这个情况做一下简单说明。

我们大部分时候利用剪切板都是在操作系统shell下,而这时候不管是复制还是粘贴,往剪切板里读写数据都是用的UTF-8编码,而程序中字符串存储方式默认并非UTF-8,所以要避免出现乱码,就要注意程序往剪切板里写入数据或者读取从系统shell中复制到剪切板的数据的时候,最好是指定GetClipboardDataGetClipboardData函数的格式为CF_UNICODETEXT

写入剪切板的时候,要把程序中的字符串先用MultiByteToWideChar函数转为宽字符格式,在以CF_UNICODETEXT格式写入;

读取剪切板数据的时候,从剪切板里以CF_UNICODETEXT格式读出数据后,要先把读出的数据用WideCharToMultiByte函数转为多字符格式再进行字符串的操作处理。

编码问题是比较复杂的问题,涉及很多内容,本篇不做详细总结。

另外还有个比较有趣的问题是程序右下角小图标的处理,我在程序中加入了最小化时候可隐藏的功能,于是要处理隐藏程序后要在桌面任务栏右下角显示小图标,并对图标的相关操作进行处理。
处理过程中我发现,这一个小小的功能几乎隐含了windows消息处理机制的多方面知识,可以说对小图标的处理对于理解消息机制原理很有帮助。

下面总结说下细节。

首先在任务栏显示或者取消显示小图标是靠Shell_NotifyIcon函数完成的, 这个函数接收的参数中有一个最重要的,是一个指向NOTIFYICONDATAA结构体的指针,这个结构体中有一个成员变量是uID,是这个小图标的序号标识(每个小图标一个,都不一样);
还有个是窗口句柄hWnd,表示将来操作小图标产生的消息发往哪一个窗口的回调函数;
还有个uCallbackMessage,这个变量的作用是,它被设为几,将来小图标产生的消息的消息序号就是几。
其他成员都是相对次要的,比如小图标的图片、样式和提示信息等。

举个例子,假如想要鼠标右键点击小图标后,弹出一个菜单,要实现这个功能,需要这样:
鼠标右键点击小图标后,系统会发送一个消息,这个消息的编号就是NOTIFYICONDATAA结构体内成员变量uCallbackMessage的值,消息会发往结构体的另一个成员变量hWnd所表示窗口的窗口回调函数,所以我们要在回调函数中实现对uCallbackMessage定义值的消息的处理。

假如有好几个小图标,定义的uCallbackMessage是一样的怎么区分是哪个图标发来的呢,我们根据消息的另一个参数,就是NOTIFYICONDATAA结构体的另一个成员变量:小图标的标识uID来区分。

我们怎么确定是什么操作呢,消息中还有一个参数就是操作类型标识,如双击就是WM_RBUTTONDOWN

这样我们通过在窗口回调函数下建立三层分支就能对每一个小图标的每一种操作定义不同的功能,功能当然是我们写代码来实现了。

第一次分支判断消息是不是小图标传来的,第二层分支判断是哪个小图标传来的,第三次分支判断传来的是哪一种具体操作。

这里我们获得了是右键点击操作,于是我们用代码来进行处理,我们在代码中实现一个菜单的创建(创建的菜单要绑定在一个窗口句柄上),并添加几项菜单内容,如还原窗口、退出程序等。
每一个菜单项又对应一个标识菜单项的ID。这样我们在点击某一个菜单项的时候,操作系统会发送一个WM_COMMAND号消息给所绑定窗口的窗口回调函数。
回调函数中用菜单项ID来判断是哪一个菜单被点击,然后进入相应的分支运行相关功能代码。

剪切板的本质是一个内存块,在复制的时候需要分配内存,要考虑分配内存的尺寸,否则可能会造成内存浪费、性能下降或者内存不足数据丢失,甚至程序崩溃。这里面涉及内存分配的相关知识。

一个剪切板小程序涉及这些方面的关键知识,麻雀虽小五脏俱全。

另外:
一切信息传递的关键是编码(或者说的更高级点叫协议);
一切编写程序语句的关键是内存分配;
一切编程思路的关键是算法;
一切程序处理的关键是数据结构。

posted @ 2019-01-02 16:07  xuejianbest  阅读(470)  评论(0编辑  收藏  举报