`
snzipeng
  • 浏览: 21518 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

[转]循序渐进实现仿QQ界面(五):半透明窗体与不透明控件

阅读更多

本篇演示实现仿QQ界面的中间客户区。QQ是可以调整界面透明度的,但是调整了透明度却连中间客户区也变得半透明了。客户区毕竟是软件最重要的部分,是要与用户交互的,透明了就影响操作了,因此这里的客户区不学QQ,始终不透明。要实现不透明控件,只能创建一个弹出窗口,遮住主界面的客户区域,然后设定与主界面连动,即始终跟着主窗口移动及调整尺寸。这个方法并不是很好,但却几乎是唯一的方法。为什么说几乎是唯一的方法呢?的确存在着另外的解决方案,但是这个方法太麻烦了,在此讨论一下这个方法。

实现半透明窗体,不透明控件,应该有不少朋友碰到过这个问题:为什么设定了窗口透明,窗口上的子窗口及控件也变得透明了?这个是受系统限制的,创建一个窗口,这个窗口区域就相当于一块画布,最终系统要在这块画布上绘制出窗口标题,客户区,控件等等,而窗口和控件绘制时的GetDC,BeginPaint等不过是获取到了跟自己相关的这块画布的一部分,在限定的区域内绘制,最终是画到这块画布上,系统最后显示窗口就是在桌面上显示这块画布,透明度也是认这块画布,因此上面的子窗口及控件也一起变得透明了。另建一个弹出式窗口,相当于创建了另一块画布,就不受主窗口的透明度限制。

知道了原因,现在来讨论如何不创建弹出式窗口,实现不透明控件。透明窗口有两种实现方法,一种是调用SetLayeredWindowAttributes,设定统一的窗口透明度,既整个窗口采用同样的透明度,QQ2009和这里的演示程序采用的就是这个方法,这个方法不创建弹出窗口是无法实现不透明控件的,因为想要控件不透明,就必须在控件区域采用不同的透明度,SetLayeredWindowAttributes无法做到,只能通过调用另一个函数UpdateLayeredWindow来实现可指定不同区域不同透明度的窗口,API代码大致如下:

C/C++ code
HDC hdc,hMemDC; RECT rc; GetWindowRect(hWnd,&rc); POINT ptSrc = {0,0}; POINT ptWinPos = {rc.left,rc.top}; SIZE szWin = {rc.right-rc.left,rc.bottom-rc.top}; BLENDFUNCTION stBlend = { AC_SRC_OVER, 0,255,AC_SRC_ALPHA}; hdc = GetWindowDC(m_hWnd); hMemDC = CreateCompatibleDC(hdc); SelectObject(hMemDC,hbmp); //hbmp为整个窗口贴图 UpdateLayeredWindow(hWnd,hdc,&ptWinPos,&szWin,hMemDC,&ptSrc,0,&stBlend,ULW_ALPHA);


最关键的部分就是SelectObject(hMemDC,hbmp);实现不同区域不同透明度,全在这个选入设备的hbmp的图象数据,Windows的32位色图像的像素数据是COLORREF类型,0x00bbggrr格式,关键就在最高位的字节0x00,UpdateLayeredWindow是认这个字节来设定透明度,0x00为全透明,0xFF为不透明,这个字节的集合有个专门名称叫ALPHA通道。设定hbmp图象每一个像素的这个最高位字节数据,就可以实现像素级别的透明度。网上应该能搜到大把利用PNG图片实现半透明窗口的例子,因为PNG图片是可以带ALPHA通道的,解码PNG图片,自然就有了ALPHA通道,即设定了这个最高位字节,就不需要用代码来一个个像素指定透明度了。实现这样的半透明窗口是这样一个过程:首先要用双缓冲,创建与窗口相同大小的内存图象,然后在这个内存图象上绘制窗口的各个部分,标题栏,背景等等,一般是用PNG图片实现,这样就不用逐个像素指定透明度了,最后把这个内存图象绘制到窗口。想实现不透明控件,就要把控件区域的ALPHA通道值设为0xFF,然而不幸的是,几乎所有的GDI操作,除了TransparentBlt,那些最常用的BitBlt,TextOut,FillRect等等都是忽略ALPHA通道的,绘制过后这些区域的ALPHA值都变成了0,即全透明。因此想要实现不透明控件,就要实现所有控件的自绘,使控件绘制到内存图象上,常规GDI操作过后再设定这些区域的ALPHA值为0xFF。是不是头大了?这个方法太麻烦了,而且仅仅是为了在半透明窗口上实现不透明控件这样一个效果,代价太大,因此并不实用。QQ2009所用的DirectUI应该能很容易实现这个效果,但是并没实现,估计跟执行效率有关,因为这种像素级透明的程序在绘制时很耗时,调整窗口大小时可能会有延迟现象,在速度慢一点的机器上更是明显。

现在来讲模仿QQ的客户区,有很多种方法,这里选用相对比较简单的方法,有更好的解决方案欢迎留言讨论。首先是上部的搜索栏,当然是子类化EDIT控件进行自绘,处理WM_NCCALCSIZE消息加大其非客户区,画个外方内圆的边框。在编辑框输入内容后会有个自绘的下拉列表出来,这个其实跟点了“更改外观”按钮后出现的界面调色对话框是一样的,不过是把那对话框改一下表现形式,然后搬到编辑框下面,就不演示了,还有右边会出现清除和执行按钮,又是贴图,属于非典型编辑框功能,也不演示了,有兴趣的可以自己完成。

然后是侧边栏,看起来是TAB控件的功能,其实用工具栏更简单一些,添加TBSTYLE_BUTTON|TBSTYLE_CHECKGROUP类型的按钮就跟TAB控件的效果差不多。怎么绘制前面一篇已经讲过了,收起和展开只是隐藏和显示而已。最重要的好友列表部分,这个要用到TAB控件了,调整窗口尺寸时3个标签的宽度是跟着变的,这个需要创建TAB控件时指定TCS_FIXEDWIDTH窗口类型,子类化后在WM_SIZE消息里发送TCM_SETITEMSIZE消息调整标签的宽度。标签需要自绘,鼠标点击上面的下拉箭头会弹出菜单,这个需要在WM_LBUTTONDOWN消息里判断一下,子类化后其实想干什么都行,只是麻烦一点罢了:)点击标签后下面列表子窗口的滑动效果切换也很简单,把两个窗口并排然后连续移动就可以,不过因为刷新的关系,可能会有重影,如果想要更好的效果,应该是把子窗口截图,然后用双缓冲绘制出滑动效果,这里就简单一点,不用这个方法了。

好友列表,群列表和最近联系人列表是用ListBox控件实现,本来不需要子类化,不过ListBox不支持鼠标移到选项上的高亮功能,因此还是实现了子类化,处理WM_MOUSEMOVE消息进行判断。好友列表里面是有“我的好友”,“陌生人”,“黑名单”等分类的,这些分类选项的高度与用户项的高度不同,因此创建控件时需要指定LBS_OWNERDRAWVARIABLE类型,还有ListBox控件默认是会计算控件高度并调整尺寸适应列表项的高度,不会在客户区显示不完整的列表选项,这个功能我们不需要,因此还需指定LBS_NOINTEGRALHEIGHT类型,不自动调整高度,再指定一下LBS_HASSTRINGS|LBS_NOTIFY|WS_VSCROLL常规类型,然后就是在父窗口的WM_DRAWITEM消息里进行列表选项的自绘了。加入选项时需要指定选项的高度,为了区分分类选项和用户信息选项,通过发送LB_SETITEMDATA消息绑定了不同的数据,这样就能通过绑定的DATA来确定如何绘制。点击分类选项是可以收起/展开该类下的用户列表的,本来想收起时通过设定其下的用户列表高度为0来实现,结果发现LB_SETITEMHEIGHT消息只能设定选项的高度为1~255之间的值,残念,只能是收起时删掉用户列表,展开时再添加进来。用户列表项高亮状态时会有“发送短信”,“发送邮件”等按钮,这里只演示了“发送邮件”按钮,同样是通过静态文本控件实现,前面一篇文章已经讲过了。信息提示和右键菜单这里就不演示了,那个菜单项实在太多,看着就害怕:)

现在看看程序的截图:



差不多快完成了,还差滚动条和异型菜单,下一篇再说了。

演示程序下载地址:http://download.csdn.net/source/2059841

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics