循序渐进实现仿QQ 界面


原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    1 / 50    目录  1.  循序渐进实现仿 QQ 界面(一):园角矩形与双缓冲贴图窗口  ............................... 2  2.  循序渐进实现仿 QQ 界面(二):贴图按钮的三态模拟  .......................................... 7  3.  循序渐进实现仿 QQ 界面(三):界面调色与控件自绘  ........................................ 14  4.  循序渐进实现仿 QQ 界面(四):圆形按钮与工具栏自绘  ..................................... 24  5.  循序渐进实现仿 QQ 界面演示程序编译问题及 MFC 调用 RingSDK 图象库示例  .... 31  6.  循序渐进实现仿 QQ 界面(五):半透明窗体与不透明控件  ................................. 36  7.  循序渐进实现仿 QQ 界面(六):异型菜单与内建滚动条自绘  ............................. 40  8.  RingSDK 界面库已改为 LGPL 协议 ............................................................................ 49                      原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    2 / 50    1. 循序渐进实现仿 QQ 界面(一):园角矩形与双缓冲贴图窗口  2010-01-08 02:01 13917 人阅读 评论(48) 收藏 举报 印象里仿 QQ 界面的程序应该有很多,搜了一下,虽然出来一大堆,排除了重复的,却只有两三个, 没我想象的好。 经常看到 CSDN 上有人问, QQ 这个功能怎么实现, 那个界面怎么实现, 归纳了一下, 决定写这么一个仿 QQ 界面程序,实用功能一律不实现,仅仿界面: 异型窗口 贴图界面 界面可调色,换底纹 仿 QQ 界面上的各种自绘控件 QQ2009 界面仔细研究起来,其实还是很复杂的,完全模拟做到一模一样还是很花工夫的,用 API 实现是个噩梦,因此这里是用 RingSDK 实现。关于 RingSDK,请到这个链接 http://blog.csdn.net/ringphone/archive/2008/09/11/2911244.aspx,最新版本请用 SVN 到 svn://svnhost.cn/RingSDK 下载。RingSDK 的图象库与界面库结合,可以实现一些比较酷的界面。这 里就是演示图象库与界面库结合实现 QQ2009 的界面。OK,Let's go!不过先声明一下,这里只是模 仿界面,实际 QQ 的界面不是这么做的。 QQ2009 是园角矩形窗口,不是复杂形状,因此实现起来很简单, CreateRoundRectRgn 然后 SetWindowRgn 就行了。不过窗口可以拖动调整大小,因此不是简单的在开始设定就可以,需要在 WM_SIZE 消息里随时改变这个 RGN: view plain 1. RECT rc;        2. GetWindowRect(m_hWnd,&rc);    3. OffsetRect(&rc,‐rc.left,‐rc.top);    4.         5. if(m_hrgn)    6.     DeleteObject(m_hrgn);    7. m_hrgn = CreateRoundRectRgn(rc.left,rc.top,rc.right,rc.bottom,NCB_CORNERSIZE,NCB_CORNE RSIZE);    8. SetWindowRgn(m_hWnd,m_hrgn,TRUE);    9. InvalidateRect(m_hWnd,NULL,TRUE);    原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    3 / 50    然后需要在 WM_NCPAINT 的时候描边,创建 QQ 边框颜色的画笔,用 RoundRect 就可以画个圆角 边框。 QQ 的标题栏和系统按钮是自绘的,本来应该在 WM_NCPAINT 消息里绘制,但是发现点击 “编辑个 性签名”会出现一个编辑框供输入, 而标题栏是不能放置控件的, 因此主窗口创建时干脆不要标题栏, 全部客户区,在客户区上方模拟一个标题栏区域出来。由于要支持调整窗口尺寸,因此创建窗口时窗 口类型是 WS_POPUPWINDOW|WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX,如 此默认的窗口边框并不符合我们的要求,需要在 WM_NCCALCSIZE 消息里设置一下边框尺寸: view plain 1. RINGMAINMSG(WM_NCCALCSIZE)    2. {    3.  LPRECT lprc = (LPRECT)param.lParam;    4.      5.  lprc‐>top += WND_BORDER;    6.  lprc‐>left += WND_BORDER;    7.  lprc‐>right ‐= (WND_BORDER + 1);   //右边框需要留出画边框线的位置, 1    8.  lprc‐>bottom ‐= (WND_BORDER + 1);  //同理    9.      10.  return 0;    11. }    设置边框尺寸为 2。这里有一个有趣的现象:即使不响应 WM_NCCALCSIZE 消息,创建的窗口也是 没有标题栏的,没有最小 /大化,关闭按钮,但是在任务栏上的程序按钮上按鼠标右键,最小化,移 动,大小,关闭的选项一个不少,并且窗口也响应这些命令。如果窗口加上 WS_EX_TOOLWINDOW 的扩展类型,则程序不会出现在任务栏上,就跟 QQ 的表现一样了,不过目前我们的程序暂时仅实现 标题栏的贴图,不支持实际功能,因此暂时不加这个扩展类型,靠在任务栏上的程序按钮上按鼠标右 键实现窗口的最小 /大化和关闭操作。不过这也太那个了一点,因此还是在系统栏加个图标为上,怎 么在系统栏加图标这个应该不成问题了,这里就不解释了, RingSDK 对此进行了封装,一句 AddInTaskBar(m_hWnd,LoadIcon(GetInstance(),MAKEINTRESOURCE(IDI_QQ)),"仿 QQ2009 界 面");搞定。响应图标的点击弹出菜单这里也不解释了,看代码就知道了。 接下来就是界面贴图,实现 QQ 的上下两个区域的绘制,这里就要用到界面库了。首先就是要准备图 片,QQ 资源里没找到,懒得研究,就自己截图搞了,弄了个宽度为 1,高 95 和 55 的图片平铺做上 下两部分的背景, VISTA 风格的系统按钮图案,因为是圆角,需要有透明色,就做成了 GIF 格式。 底纹,右边的沙滩海鸥图案抠图没这个本事,另找了一个相似的,做成 PNG 格式, PS 里面加个蒙 板就有了 ALPHA 通道。图象库支持 PNG 的半透明绘制,只要调用 AlphaTo 就行。还有用户头像, 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    4 / 50    随便截了一个,把这些素材加入资源,算准坐标进行双缓冲绘制,依次绘制到内存图象就可以了。这 里说一下内存图象的创建,是一开始就创建了一个跟屏幕等宽,高 95+55 的内存图象,这样就不需 要在 WM_SIZE 里频繁释放再创建了。代码如下: view plain 1. 先把各素材绘制到内存图象 m_dibBanner    2. //背景    3. m_dibBkg.StretchTo(&m_dibBanner,0,0,rc.right,m_dibBanner.Height(),0,0,m_dibBkg.Width() ,m_dibBkg.Height(),FALSE);    4. //标题( “QQ2009”)    5. m_dibCaption.AlphaTo(&m_dibBanner,8,5,0,0);    6. //装饰图案,即 QQ 所谓的 “底纹”    7. m_dibTatoo.AlphaTo(&m_dibBanner,rc.right‐m_dibTatoo.Width(),0,0,0);    8. //用户头像     9. m_dibUser.DrawTo(&m_dibBanner,7,25,0,0,m_dibUser.Width(),m_dibUser.Height());    10. //用户名及签名     11. m_dibUserBanner.DrawTo(&m_dibBanner,54,25,0,0,m_dibUserBanner.Width(),m_dibUserBanner. Height());    12. //系统按钮     13. //最小化     14. m_dibBtnRc.DrawTo(&m_dibBanner,rc.right ‐ NCB_ENTIREWIDTH ‐ NCB_SPACE,yPos,gRcBtn[0].x ,gRcBtn[0].y, NCB_MINWIDTH,NCB_HEIGHT);    15. //关闭    16. m_dibBtnRc.DrawTo(&m_dibBanner,rc.right ‐ NCB_CLOSEWIDTH ‐ NCB_SPACE,yPos,                       gRcBtn[NCB_CLOSENORMAL].x,gRcBtn[NCB_CLOSENORMAL].y,NCB_CLOSEWIDTH,NCB_HEIGH T);    17. //最大化 /还原    18. if(IsZoomed())    19. {    20.     m_dibBtnRc.DrawTo(&m_dibBanner,rc.right ‐ NCB_ENTIREWIDTH ‐ NCB_SPACE + NCB_MINWID TH,yPos,    21.     gRcBtn[NCB_RESTORENORMAL].x,gRcBtn[NCB_RESTORENORMAL].y,NCB_MAXWIDTH,NCB_HEIGHT);     22. }    23. else    24. {    25.     m_dibBtnRc.DrawTo(&m_dibBanner,rc.right ‐ NCB_ENTIREWIDTH ‐ NCB_SPACE + NCB_MINWID TH,yPos,    26.         gRcBtn[NCB_MAXNORMAL].x,gRcBtn[NCB_MAXNORMAL].y,NCB_MAXWIDTH,NCB_HEIGHT);    27. }    原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    5 / 50    28.     29. //绘制到窗口     30. //标题栏区域     31. m_dibBanner.Draw(hdc,0,0,0,0,rc.right,NCB_TITLEHEIGHT,rc.right,NCB_TITLEHEIGHT);    32. //底栏区域     33. m_dibBanner.Draw(hdc,0,rc.bottom ‐ NCB_BOTTOMHEIGHT,0,NCB_TITLEHEIGHT,rc.right,    34.     NCB_BOTTOMHEIGHT,rc.right,NCB_BOTTOMHEIGHT);    至此贴图窗口完成,看一下程序截图: 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    6 / 50    目前的这个程序可拖动,可调整大小,双击可最大化,系统栏图标按右键可弹出仿 QQ 的菜单,不过 仅“打开主面板 ”和“关闭”命令有效。界面上的按钮只是贴图,并不会响应鼠标动作。下一步会实现界 面按钮的三态和功能响应,添加其他功能,下一篇再说了。 补充说明:界面库是支持 MFC 的,程序中的贴图代码基本可以照搬到 MFC 的程序,只需要包含 "ringdib.h"即可。不过需要先编译 libsrc/freelib 下的 zlib,png 库,想支持 JPG 还需编译 JPG 库。 程序下载地址: http://d.download.csdn.net/down/1974639/ringphone                                   原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    7 / 50    2. 循序渐进实现仿 QQ 界面(二):贴图按钮的三态模拟  2010-01-10 23:23 7323 人阅读 评论(25) 收藏 举报 开始之前先说一下 RingSDK 的编译问题,这里演示的程序需要用到最新版本的 RingSDK,请务必用 SVN 到 svn://svnhost.cn/RingSDK 更新到最新版本,推荐用 TortoiseSVN。 如果是 VC2008,编译应该没有问题, 只是警告多了一点。 VC6 编译 RingSDK 之前需要安装 Platform SDK,并且选择 VC 菜单 Tools->Options...,在弹出的对话框中选择 Directories 页,Show directories for:下拉框里选择 include files,然后在下面列表中确认 Platform SDK 的 include 目录是排在第一位。 同时把 RingSDK 的 include 目录加入列表,如下图: 然后 Show directories for:下拉框里选择 Library files,确认 Platform SDK 的 lib 目录排在第一位,把 RingSDK的lib目录加入列表。如果你想调试程序时跟进 RingSDK源代码,应该把 RingSDK下libsrc 下的目录加入到 Source files 列表里面。 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    8 / 50    VC 设置完毕,请先打开 RingSDK/libsrc/freelib 目录,先编译该目录下 3 个第 3 方库,然后打开 ringsdk.dsw 进行编译。编译通过,就可以编译 example 里面的演示程序了。 现在进入正题,讲解如何实现贴图按钮的三态模拟。所谓三态,就是按钮的正常状态,鼠标移上去的 高亮状态以及按下状态。实际应该还有一个 Disable 状态,不过这个程序没有不可用的按钮,因此就 不实现这个效果了。由于是贴图,不是实际的按钮,因此必须自己处理鼠标消息并进行三种状态的绘 制。首先当然要有一个鼠标位置检测函数 HitTest,检测鼠标在哪个按钮上面,由于程序的非客户区 仅仅是一个尺寸为 2 的园角矩形框,标题栏是模拟在客户区实现,因此这个 HitTest 函数是在 WM_MOUSEMOVE 消息里调用判断,无非是 PtInRect 进行各个按钮坐标的检测,因此这个 HitTest 函数的实现这里就不讲解了,看代码就明白了。如果鼠标在按钮区域,该函数返回按钮 ID,在系统 按钮区域,返回 HTMINBUTTON,HTMAXBUTTON,HTCLOSE,方便直接发送系统命令,不在任 何按钮,返回 HTCAPTION,可以拖动窗口。 如果 HitTest 返回了按钮 ID,用户没按下鼠标,应该绘制按钮的高亮状态。这里有个问题,绘制完了 以后,用户继续移动鼠标,但是没移出这个按钮, WM_MOUSEMOVE 消息又会检测到需要绘制高亮 状态,这样不断绘制就会造成鼠标闪烁,因此需要定义一个 m_nCurWhere 的成员变量,记录鼠标上 一次的 HitTest 检测值: view plain 1. int nWhere = HitTest(...);    2. if(nWhere != m_nCurWhere)    3. {    4.  //需要绘制按钮,高亮或恢复原状     5.  if(nWhere != HTCAPTION)    6.   CheckAndDrawButtons(nWhere,4);  //绘制 nWhere 按钮的高亮状态     7.  else    8.   CheckAndDrawButtons(m_nCurWhere,0); //恢复高亮按钮的原始状态     9. }     10. m_nCurWhere = nWhere;    这样就保证了各种状态变化,只需要绘制一次。 实现按钮按下状态,需要在 WM_LBUTTONDOWN 消息里处理,首先当然是进行 HitTest 检测,这 里也要定义一个成员变量 m_nCurSysCmd,标记当前是按下了哪个按钮, 然后绘制按钮的按下状态。 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    9 / 50    由于用户可能按下了按钮,不松开鼠标进行移动,因此还需要定义一个成员变量 m_bInCapture,在 WM_LBUTTONDOWN 消息里置这个变量为 TRUE,SetCapture 捕获鼠标,在 WM_LBUTTONUP 消息里 ReleaseCapture 释放鼠标,置这个变量为 FALSE,这样在 WM_MOUSEMOVE 里调用的按 钮绘制函数就可以根据 m_bInCapture 判断是该绘制按下状态还是高亮状态。 WM_LBUTTONUP 消 息也可以根据这个标志判断是否该执行按钮动作,否则在别的地方按下鼠标键,移到一个按钮上松开 鼠标,执行该按钮功能有点不大对头, 应该判断 m_bInCapture 为 TRUE 且 m_nCurSysCmd 与 HitTest 检测到的按钮相等才执行该按钮的功能。鼠标键按下时的移动,只会对 m_nCurSysCmd 标记的按钮 进行按下状态和正常状态的绘制,其余按钮一概不理。发现 QQ2009 在这方面没有实现,按下按钮 后不松开鼠标键移出按钮,按钮状态不会变化。 按此机制, WM_MOUSEMOVE 里的判断应该修改一下了,增加对按钮按下状态的判断: view plain 1. RINGMAINMSG(WM_MOUSEMOVE)    2. {    3.  int nWhere = HitTest(param);    4.      5.  if(m_bInCapture)    6.  {    7.   //鼠标键按下状态的移动     8.   if(m_nCurSysCmd == nWhere && m_nCurWhere != nWhere)    9.   {    10.    //鼠标从按钮外移入按钮,绘制按下状态按钮     11.    CheckAndDrawButtons(nWhere,8);    12.   }    13.   else if(m_nCurSysCmd != nWhere && m_nCurSysCmd == m_nCurWhere)    14.   {    15.    //鼠标移出按钮,绘制正常状态     16.    CheckAndDrawButtons(m_nCurSysCmd,0);    17.   }    18.   m_nCurWhere = nWhere;    19.   return 0;    20.  }    21.  else    22.  {    23.   LRESULT res;    24.   if(nWhere != m_nCurWhere)    25.   {    26.    //需要绘制按钮,高亮或恢复原状     原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    10 / 50    27.    if(nWhere != HTCAPTION)    28.     CheckAndDrawButtons(nWhere,4); //高亮状态    29.    else    30.     CheckAndDrawButtons(m_nCurWhere,0); //恢复原状    31.   }    32.   res = DefaultProc(param);    33.   m_nCurWhere = nWhere;    34.   return res;    35.  }    36. }    至此,鼠标对按钮的检测功能已完成,剩下的就是按钮的绘制工作了。按钮的绘制工作分为 3 块:系 统按钮的绘制,用户头像旁边一个可弹出菜单的按钮和编辑个性签名按钮([我在线上]也是按钮,不 过这个与个性签名按钮实现是一样的,这里就省点事,不实现了),以及用户头像下方的工具栏上那 一排按钮。系统按钮的绘制采用的是最常见的方法,这里把系统按钮资源图象贴出来大家就知道了, 根据需要绘制的状态,只要把图象上相应区域绘制到目标就可以了。如图: 不过这里还是要有个技巧,最下面一排是正常状态按钮,为将来界面调色的需要,处理成了透明的, 仅边框和线条是不透明的,这样绘制正常状态按钮的时候就需要恢复背景色,然后透明绘制正常状态 的按钮,有点麻烦,因此程序初始化的时候就初始化了系统按钮区域大小的一个内存图片,在 WM_PAINT 消息里面贴图的时候,顺便就把该正常系统按钮区域的图象绘制到了这个内存图象,这 样恢复系统按钮状态只要绘制一遍这个内存图象就可以了。 接下来是绘制工具栏那一排按钮,来看看所需要的资源图片,一共是两幅: 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    11 / 50    实现的按钮三态效果,第二个是高亮状态,第三个是按下状态: 恩?这个是怎么实现的?关键在第 2 张资源图片,分为左右两部分,左边是高亮状态,右边是按下状 态,每一部分图象是 5 个像素宽, 左右各 2 像素分别是按钮左右边框, 中间 1 像素是按钮的中间图案, 绘制时需要横向拉伸至按钮除去左右边框的宽度。先恢复背景图案,然后绘制高亮状态按钮,最后把 整个工具栏图象透明绘制上去就可以了,不会影响已经绘制好的背景。按下状态的绘制稍稍有点不一 样,因为需要把按下的按钮图象向右下方偏移 1 个像素,显得按钮是按下去了,这样就不能一下把整 个工具栏图象画上去,需要绘制按下的按钮图象,然后把这个按钮左右边的按钮绘制上去。 用户头像旁边的按钮与工具栏的绘制类似,就不讲解了,看源代码就知道了,代码里面图象的源区域 坐标,绘制目的坐标有点绕,需要头脑清楚才不会搞糊涂。需要搞清楚图象库的 DrawTo,StretchTo 几个函数的参数定义,具体看 RingSDK 的帮助文件吧。 顺便说一下,图象库的双缓冲绘制操作是直接操作的图象数据,说白了只是一些 COLORREF 数组数 据的搬移,除了绘制文字,根本不需要 HDC 的参与,因此速度和效率是很高的。 至此贴图按钮的三态模拟完成,还需要实现其他一些功能,工具提示,把工具栏按钮的工具提示加了 上去,不过最右边两个 “打开消息盒子”和“更改外观”的按钮的工具提示需要额外处理,因为窗口可以 调整大小,一改变尺寸原来的坐标就不对了, 因此需要动态改变坐标, 在哪里改呢, WM_SIZE 消息? 用户改变窗口尺寸的时候其实是没办法把鼠标移到这两个按钮上的,而且 WM_SIZE 消息太频繁了, 我们只需要在尺寸调整完毕的时候更新一下坐标就行了,这个消息就是 WM_EXITSIZEMOVE: view plain 1. TOOLINFO ti;    2. CopyRect(&ti.rect,&rc);  //rc 为更新的坐标     3. m_tip‐>SetToolInfo(&ti);    原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    12 / 50    实际程序里的代码要复杂一些。系统按钮的工具提示没加,有兴趣的可以自己完成,需要跟这两个工 具栏按钮一样的处理,更新坐标。 还有是按下用户头像旁按钮弹出菜单, QQ2009 这个菜单的最后两项与系统栏图标上弹出的菜单是不 一样的,这里就不麻烦了,直接弹出了系统栏图标上弹出的菜单。 个性签名按钮,按下后会显示一个编辑框允许编辑签名,这个不难,麻烦的是这个编辑框需要自动隐 藏,处理其 EN_KILLFOCUS 消息并不能完全实现效果,因为界面上没有别的控件可以抢它的焦点, 只有程序失去焦点才会有这个消息,因此需要在 WM_LBUTTONDOWN 里面再加个判断处理,既然 收到 WM_LBUTTONDOWN 消息,说明是在编辑框外按了鼠标,可以隐藏了。可惜这样做还不够, 调整窗口尺寸的时候是点击了非客户区,没有 WM_LBUTTONDOWN 消息,因此需要在 WM_ENTERSIZEMOVE 消息里再加个判断处理,这样编辑个性签名的功能才算完善。 至此,这个程序已经实现了所有按钮的三态模拟,系统按钮的功能响应,工具栏按钮的工具提示,可 编辑个性签名, 可弹出菜单。 加了 WS_EX_TOOLWINDOW 的扩展类型, 程序不会出现在任务栏上, 没加限制只执行一次程序的功能。有个小小 BUG,鼠标移到系统按钮区域,系统按钮显示高亮状态, 这时把鼠标快速向上移出窗口,按钮状态不会恢复,因为没有了 WM_MOUSEMOVE 消息,解决这 个问题需要 TrackMouseEvent,然后在 WM_MOUSELEAVE 消息里恢复按钮状态,有兴趣的可以自 己解决。 最后看看程序截图: 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    13 / 50    界面下方的工具栏以后会用不同的方法实现,下一篇,将会讲解如何实现一个激动人心的功能:界面 调色。大家打开程序资源, 可以看到里面已经有更改外观的设置对话框了, 以后会讲解如何自绘控件, 实现 QQ 的这个更改外观对话框。 演示程序下载地址: http://download.csdn.net/source/1982937             原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    14 / 50    3. 循序渐进实现仿 QQ 界面(三):界面调色与控件自绘  2010-01-15 00:39 12093 人阅读 评论(34) 收藏 举报 本篇讲述如何进行界面调色。界面调色一般有两种方法,调色板和 HSL 色彩变换。调色板局限于 256 色,这里不采用,因此用 HSL 色彩变换实现。首先要了解一下什么是 HSL 色彩空间,完整且详尽的 知识请到维基百科去看,链接地址: http://zh.wikipedia.org/wiki/HSL%E5%92%8CHSV%E8%89%B2%E5%BD%A9%E7%A9%BA%E9 %97%B4,这里简单讲一下(摘自维基百科): HSL 和 HSV(也叫做 HSB)是对 RGB 色彩空间中点的两种有关系的表示,它们尝试描述比 RGB 更准确的感知颜色联系,并仍保持在计算上简单。 HSL 表示 hue(色相)、saturation(饱和度)、 lightness(亮度), HSV 表示 hue、 saturation、value 而 HSB 表示 hue、saturation、brightness (明度)。如下图: HSL 和 HSV 二者都把颜色描述在圆柱体内的点,这个圆柱的中心轴取值为自底部的黑色到顶部的 白色而在它们中间是的灰色,绕这个轴的角度对应于 “色相”,到这个轴的距离对应于 “饱和度”,而沿 着这个轴的距离对应于 “亮度”,“value”或“明度”。 把 RGB 颜色转换到 HSL 色系,进行调色就很简单了,改变色彩只要转个角度,改变亮度就是沿轴升 降进行“切片”,饱和度就是改变到中心轴的距离, 3 点定位到一个颜色,再转回 RGB 颜色就完成了界 面调色。对需要调色的贴图进行这么一个变换,再重新贴图刷新一下界面就完成了调色功能。 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    15 / 50    并不是所有图片都需要调色, 象标题栏右边的沙滩图案, 如果调色就很难看了, 会象照片的负片效果, 因此用户头像,底纹图案,文字,图标都不需要调色,只需要把背景图案,按钮的高亮和按下状态图 片,菜单背景和高亮图案进行色彩变换就行了。系统按钮需要局部调色,把最小化和最大化,还原按 钮的高亮和按下状态的背景进行调色,因此准备了另外一张图片,一共 5 张图片需要调色: 最后一张图片是调色后透明绘制到系统按钮源图象上,实现了局部调色。 HSL 色彩变换的实现是通过挂接图象库的滤镜插件类实现,实现原理请看 RingSDK 的帮助文档,调 用代码极其简单, m_dibBkg.GETFILTER(dibFilterEFFECT)->AdjustHSL(h,s,l);就完成了背景的调 色。图象库实现了两个滤镜插件类, dibFilterALPHA 和 dibFilterEFFECT,实现图象的各种 ALPHA 混合效果和亮度对比度,色度调整等。这里把关键的调色代码贴出来, 其中 m_rdib->Data()为已加载 的 32 位色图象数据: view plain 1. //调整色调,参数范围: ‐180~180(度) ,=0 不作调整    2. //调整饱和度,参数范围 0~200(建议,最大值可 >200),=100 不作调整     3. //调整亮度,亮度参数范围 0~200(建议,最大值可 >200),=100 不作调整    4. BOOL dibFilterEFFECT::AdjustHSL(int degHue,int perSaturation,int perLuminosity)    5. {    6.  if(!m_rdib‐>Data())    7.     return FALSE;    8.     9.  if(perSaturation < 0 || perLuminosity < 0)    10.   return FALSE;    原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    16 / 50    11.     12.  if(degHue == 0 && perSaturation == 100 && perLuminosity == 100)    13.   return TRUE; //未作调整,直接返回     14.       15.    LPBYTE pRed, pGrn, pBlu,pBuf=(LPBYTE)m_rdib‐>Data();    16.  UINT loop = m_rdib‐>Width() * m_rdib‐>Height();    17.  COLORREF *cr = (COLORREF*)m_rdib‐>Data();    18.        19.    pRed=pBuf++;pGrn=pBuf++;pBlu=pBuf;     20.  float H,S,L;    21.     22.  for (UINT i=0;i 360.0f)     69.    *H ‐= 360.0f;    70.    }    71. }    72.     73. COLORREF dibFilterEFFECT::HSLtoRGB(float H,float S,float L)    74. {    75.  BYTE r,g,b;    76.      77.  L = min(1,L);    78.  S = min(1,S);    79.     80.  if(S == 0.0)    81.  {    82.   r = g = b = (BYTE)(255 * L);    83.  }     84.  else     85.  {    86.   float rm1, rm2;    87.              88.       if (L <= 0.5f)     89.    rm2 = L + L * S;    90.       else    91.    rm2 = L + S ‐ L * S;    92.       rm1 = 2.0f * L ‐ rm2;       93.           94.   r = HueToRGB(rm1, rm2, H + 120.0f);    95.       g = HueToRGB(rm1, rm2, H);    96.       b = HueToRGB(rm1, rm2, H ‐ 120.0f);    原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    18 / 50    97.    }    98.    return RGB(r,g,b);    99. }    100.     101. BYTE dibFilterEFFECT::HueToRGB(float rm1,float rm2,float rh)    102. {    103.  while(rh > 360.0f)    104.   rh ‐= 360.0f;    105.  while(rh < 0.0f)    106.   rh += 360.f;    107.      108.  if(rh < 60.0f)    109.   rm1 = rm1 + (rm2 ‐ rm1) * rh / 60.0f;    110.  else if(rh < 180.0f)    111.   rm1 = rm2;    112.  else if(rh < 240.0f)    113.   rm1 = rm1 + (rm2 ‐ rm1) * (240.0f ‐ rh) / 60.0f;    114.        115.  float n = rm1*255;    116.  int m = min((int)n,255);    117.  m = max(0,m);    118.  return (BYTE)m;    119. }    实现了界面调色功能,接下来就得实现调色的设置界面, QQ2009 的调色设置是预置了 8 种基色,可 以选择一种基色,也可以对 HSL 分别调色,但是这 8 种基色是随着底纹和皮肤的选择变动的,皮肤 的选择影响可选择的底纹和基色,一开始没怎么搞懂,后来下了一个 QQ2009 的主题包,结果是一 个自定义格式的打包文件,没办法解开看里面图片,就没研究了,再下了一个 2008 版的主题包,可 以看到所用的图片,大致清楚其为什么这么做了,里面的底纹图片是 BMP 格式,用紫红做透明色, 由于没有 ALPHA 混合通道,这样的图片透明绘制到背景,边缘是有锯齿的,除非背景颜色跟图象边 缘颜色相近才看不出来, 因此选择的皮肤会限定底纹图片和背景颜色, 选择底纹会同时变动背景颜色, 再调色则对背景和底纹一起调色, 底纹图案尽量采用 RGB 色差不大的图案, 使调色效果不会太明显, 这样整个界面调色底纹图案就没有了锯齿,变化也不怎么看得出来,是一种比较巧妙的做法。当然这 是 QQ 以前版本的做法, 2009 应该已经改进了。这里演示的界面调色功能由于底纹图案是采用了带 ALPHA 通道的 PNG 格式图片,因此不存在这样的限制,同时为了简单起见,代码易于理解,就不学 QQ 的做法了,取消皮肤的设定,调色和底纹的选择互不影响,不做联动。在资源里预置了 5 张底纹 图片可供选择,有兴趣的可以自己加。这里只是为简单起见而将底纹加到了资源里面,实际为了实现 换肤并可扩展应该是自己实现一个换肤的配置文件和资源包,这里就不演示了。 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    19 / 50    现在实现调色的设置界面,首先响应 “更改外观 ”按钮的弹起事件( WM_LBUTTONUP 消息里面), 弹出设置窗口,这个窗口是资源里面调色和底纹对话框的大小,不可拖动和调整大小,需要在失去焦 点时自动隐藏,因此需要在其 WM_ACTIVATE 消息里面判断一下,如果是 WA_INACTIVE 状态就发 送一个退出消息把自己关闭。加载调色和底纹两个对话框,显示一个,把另一个隐藏,调色和底纹的 选择看起来应该是 TAB 控件, 不过得自绘, 这里用图片控件模拟 TAB 反而简单,反正只有两个选择, 判断一下鼠标位置,换个图片,切换一下两个对话框的显示状态,看代码就知道了。 这里主要讲解一下调色对话框上 4 个 Slider 控件的自绘。控件自绘需要先子类化, RingSDK 界面库 已经封装好了,继承相应的控件类,子类化是自动的,可以重载其 RingdowProc 函数,即控件的窗 口过程,想做什么都可以,给了你最大的自由度,未处理的消息返回 DefaultProc 或基类的 RingdowProc 就可以。自绘的 Slider 控件是继承自 RingTrackBar,只要响应其 WM_PRINTCLIENT 和 WM_PAINT 消息,把背景图案和滑块图案画上去就 OK 了,代码很简单: view plain 1. LRESULT RingTrackBarEx::RingdowProc(HWND hWnd,RINGPARAMS param)    2. {    3.  switch(param.uMsg)    4.  {    5.   case WM_PRINTCLIENT:    6.   case WM_PAINT:    7.   {    8.    RECT rc,rcc;    9.    HDC hdc,hMemDC;    10.    PAINTSTRUCT ps;    11.    GetWindowRect(m_hWnd,&rc);    12.    OffsetRect(&rc,‐rc.left,‐rc.top);    13.    hdc = param.wParam?(HDC)param.wParam:BeginPaint(hWnd,&ps);    14.    FillRect(hdc,&rc,m_brush);    15.     16.    hMemDC = CreateCompatibleDC(hdc);    17.    SelectObject(hMemDC,m_hbmLine);    18.    GetChannelRect(&rcc);    19.    StretchBlt(hdc,rcc.left,(rc.bottom ‐ m_sizeLine.cy)/2,rcc.right‐rcc.left,m_sizeLine .cy,    20.        hMemDC,0,0,m_sizeLine.cx,m_sizeLine.cy,SRCCOPY);    原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    20 / 50    21.    SelectObject(hMemDC,m_hbmThumb);    22.    GetThumbRect(&rc);    23.    BitBlt(hdc,rc.left,rc.top,rc.right‐rc.left,rc.bottom‐rc.top,hMemDC,0,0,SRCCOPY);    24.     25.    DeleteDC(hMemDC);    26.    if(param.wParam == 0)    27.     EndPaint(hWnd, &ps);    28.    return 0;    29.   }    30.  }    31.  return DefaultProc(param);    32. }    因为调色函数 AdjustHSL 的参数关系,设置调整色调的控件调整范围值为 -180 ~ 180,初始值为 0, 饱和度和亮度的范围都是 0 ~ 200,初始值为 100。关于透明度的调整,怎么使窗口半透明网上介绍 文章有很多,这里就不罗嗦了,界面库对此做了封装,调用 SetLayeredAlpha 就可以了,不过范围值 是设成了 25 ~ 255,不能 0 ~ 255,全透明的话窗口就不能操作了。 调整色调的图片是展开的 HSL 色环,如下图: 注意因为默认的背景是兰色,因此兰色是在图案的中间,如果默认背景是别的颜色,就需要循环平移 这些色彩, 使背景色处在中间位置, 否则拖动滑块调色, 实际的效果跟滑块所在的颜色就对不上号了。 底纹图案的选择很简单,重新加载选择的图案就 OK,看代码就知道了。 至此完成了界面调色功能,界面中间部分因为以后会有控件遮盖,因此不需要调色。现在看看程序的 截图: 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    21 / 50    最后说一下怎么做带 ALPHA 通道的 PNG 图片和加到程序里面: 首先找一张自己满意的图片,调整到高度 95,QQ 标题栏的高度,宽度随意,只要图案不怎么变形就 可以,然后加一个蒙板,如图,按一下图中的按纽: 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    22 / 50    要使图片透明,需要去除背景,如果是新建图片并复制了图象过来,把背景层删掉,如果是直接打开 的图片,需要复制一层然后把背景层删掉。加了蒙板后选择渐变工具,从右往左一拉,然后把图片保 存为 PNG 格式就可以了,如图: 做完 PNG 图片还要做一张 31*31 的预览 BMP 图片,按资源里的格式做就可以了,把 PNG 加入为 “PNG”类型的资源, ID 值必须与 resource.h 里 IDP_SEA 等 5 张预置图片的 ID 值连续,预览图片的 ID 值必须与 IDB_TATOO1~IDB_TATOO5 的值连续,在 wnduioption.cpp 里面找到 gszTatooInfo 的 初始化代码,把图片说明加到里面,编译一下程序就 OK 了。 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    23 / 50    现在这个仿 QQ 界面的程序已经完成了标题栏部分的功能,接下 来要完成客户区和底栏部分的功能, 留待下一篇再讲了。 程序代码下载地址: http://download.csdn.net/source/1995651                                   原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    24 / 50    4. 循序渐进实现仿 QQ 界面(四):圆形按钮与工具栏自绘  2010-01-26 23:47 4656 人阅读 评论(19) 收藏 举报 这一篇本来应该演示如何实现仿 QQ 界面的中间客户区与底部工具栏, 不过在实现底部工具栏的时候 发现圆形按钮与工具栏自绘有不少取巧的方法,因此加插这么一篇,讲解一下如何实现圆形按钮和工 具栏自绘。 前面几篇都是在讲解如何实现 QQ 顶部的标题栏,是用窗口贴图实现,也讲到底部区域会用不同的方 法实现,因此这里底部的 QQ 按钮和工具栏不是在主窗口上画图了,而是用控件实现。并且这里讲解 的方法不局限于使用 RingSDK 界面库及实现这个仿 QQ 界面程序,类似的效果用 MFC 或 API 都可 以轻易实现。 讲到圆形按钮,大家一定会想到要实现按钮的自绘,然而有取巧的方法,就是用静态文本控件进行模 拟,完全不需要自绘。先截个 QQ 的图作为资源,如下图: 这张图包含了两个按钮, 一个收起/展开侧边栏的按钮和弹出主菜单的 QQ 按钮,两个都是圆形按钮。 我们先建一个跟这张图一样大小的无边框的子窗口,用这张图作为窗口背景,想来这个大家都会,用 RingSDK 界面库只要调用一下 SetBkgBitmap 就一切搞定。然后建两个静态文本控件,一个 13*13 大小,一个 36*36 大小,位置正好覆盖那两个按钮。 静态文本控件本来就是透明的, 只要不设置文字, 两个控件就跟不存在一样,不影响背景,但是占据了位置却正好可以模拟出按钮的动作。当然,这两 个控件还要先 CreateEllipticRgn,再 SetWindowRgn 一下,使其成为圆形,这样鼠标就必须进入到 这个圆形按钮区域才会有响应动作,这下不用费劲在主窗口判断鼠标位置了 :) 首先是模拟鼠标移上去的高亮状态,鼠标移到按钮上,父窗口是没有 WM_MOUSEMOVE 消息的, 因此这里必须要由控件处理这个消息,需要子类化这两个静态文本控件, MFC 有个第 3 方的静态文 本控件,类名不记得了,是实现超文本链接的,如果有这个类的话,可以不用自己进行子类化,用这 个类就可以了, RingSDK 界面库则已经封装了这个功能,只要调用 RingStatic::SetHyperlink 就可以 实现。实现这个超文本链接功能的原理是调用 TrackMouseEvent,然后处理 WM_MOUSEHOVER 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    25 / 50    和 WM_MOUSELEAVE 消息,这两个消息会在鼠标移入控件和离开控件各发送一次,因此不需要象 前面的贴图按钮一样用个标志记录是否绘制过按钮的各种状态, 只需要在 WM_MOUSEHOVER 消息 里绘制按钮的高亮状态,在 WM_MOUSELEAVE 消息里恢复按钮的原始状态就行了。界面库的 RingStatic 类因为在这两个消息里有自己的处理,没有把这两个消息转发给父窗口,而是发送了 WM_LINKHOVER 和 WM_LINKLEAVE 两个自定义消息,因此演示程序的绘制是在这两个消息里处 理,实际是跟 WM_MOUSEHOVER 和 WM_MOUSELEAVE 消息一样的。绘制代码很简单,把两张 用到的资源图片贴出来,大家一看就知道了,就是把图片的不同区域绘制到窗口。第 2 张图片采用连 续绘制实现了动画显示,因为需要调色的关系,这张图片是实时生成的。 view plain 1. RINGMSG(WndQQButton,WM_LINKHOVER)    2. {    3.  if((HWND)param.wParam == m_btn‐>Handle())    4.  {    5.   int sx = BTN_PICWIDTH;    6.   if(m_bIsSideToolHide)    7.    sx += m_dibArrBtn.Width()/2;    8.   m_dibArrBtn.Draw((BTN_WIDTH ‐ BTN_PICWIDTH)/2,0,sx,0,BTN_PICWIDTH,BTN_PICHEIGHT,BTN_ PICWIDTH,BTN_PICHEIGHT);    9.  }    10.  else if((HWND)param.wParam == m_btnQQ‐>Handle())    11.  {    12.   m_dibQQBtn.Draw(0,0,0,0,QQBTN_WIDTH,QQBTN_HEIGHT,QQBTN_WIDTH,QQBTN_HEIGHT);    13.   Sleep(80);    14.   m_dibQQBtn.Draw(0,0,QQBTN_WIDTH,0,QQBTN_WIDTH,QQBTN_HEIGHT,QQBTN_WIDTH,QQBTN_HEIGHT) ;    15.   Sleep(80);    16.   m_dibQQBtn.Draw(0,0,QQBTN_WIDTH*2,0,QQBTN_WIDTH,QQBTN_HEIGHT,QQBTN_WIDTH,QQBTN_HEIGH T);    17.  }    原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    26 / 50    18.  return TRUE;    19. }    20.     21. RINGMSG(WndQQButton,WM_LINKLEAVE)    22. {    23.  if((HWND)param.wParam == m_btn‐>Handle())    24.  {    25.   int sx = 0;    26.   if(m_bIsSideToolHide)    27.    sx = m_dibArrBtn.Width()/2;    28.   m_dibArrBtn.Draw((BTN_WIDTH ‐ BTN_PICWIDTH)/2,0,sx,0,BTN_PICWIDTH,BTN_PICHEIGHT,BTN_ PICWIDTH,BTN_PICHEIGHT);    29.  }    30.  else if((HWND)param.wParam == m_btnQQ‐>Handle())    31.  {    32.   m_dibQQBtn.Draw(0,0,QQBTN_WIDTH,0,QQBTN_WIDTH,QQBTN_HEIGHT,QQBTN_WIDTH,QQBTN_HEIGHT) ;    33.   Sleep(80);    34.   m_dibQQBtn.Draw(0,0,0,0,QQBTN_WIDTH,QQBTN_HEIGHT,QQBTN_WIDTH,QQBTN_HEIGHT);    35.   Sleep(80);    36.   m_dibBkg.Draw(0,0,QQBTN_X,QQBTN_Y,QQBTN_WIDTH,QQBTN_HEIGHT,QQBTN_WIDTH,QQBTN_HEIGHT) ;    37.  }    38.  return TRUE;    39. }    代码里面的 m_dibXXX 几个对象在初始化的时候设定了绘制目标窗口是两个静态文本控件,因此这 里看不到 GetDC 之类的代码,封装起来了,相应的因为目标 DC 是两个静态文本控件,坐标也比较 好计算。这些代码改成 GDI 操作也比较容易。静态文本控件已经设置成圆形,DC 以外的图象会绘制 到父窗口,这样正好满足我们的要求,因为那个小按钮鼠标移上去会有一圈光晕(不是很明显),而 这个光晕是在按钮范围以外。 除去初始化创建静态文本控件,图片调色和子类化静态文本控件代码以外,只要响应上面两个消息就 实现了 QQ2009 左下角两个按钮的模拟,是不是很简单?子类化静态文本控件的代码可以去看 RingSDK 界面库的 ringstatic.cpp 或者是扩展的 MFC 超文本链接控件类的代码。QQ2009 的这两个 按钮没有按下的状态,但是很容易添加实现,演示程序里的相关代码也很容易就可以改成 MFC 或 API 的,可以实现自己的比较酷的按钮效果。顺便说一下,创建的静态文本控件必须带上 SS_NOTIFY 窗 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    27 / 50    口类型,否则点击后父窗口是收不到消息的。父窗口的窗口类型不需要带上 WS_CLIPCHILDREN 和 WS_CLIPSIBLINGS 类型,否则不会绘制静态文本控件占据的区域背景。 接下来说一下工具栏的自绘。 实现自绘工具栏一种方法是子类化, 自己处理其 WM_PAINT 消息。但是工具栏按钮的状态实在太多, 按钮形态,扁平形态,高亮,按下,不可用等等状态,是否显示文字,是否显示下拉箭头等等,要做 出一个通用的自绘工具栏实在是一件很麻烦的事。这里教你一个取巧的办法,以 QQ2009 的工具栏 为例,子类化工具栏是逃不掉的,但是只要处理 WM_ERASEBKGND 消息,刷上你要的背景就可以 了,不需要处理 WM_PAINT 消息。先创建工具栏,带上 TBSTYLE_FLAT 类型,扁平按钮,然后准 备好如下两张图片: 接下来,下面所列出的代码是调用的 API: view plain 1. HBITMAP = LoadBitmap(...);    2. HIMAGELIST himg = ImageList_Create(20,20,ILC_COLOR32|ILC_MASK,9,4);  //这里的参数请根据 图片自行调整     3. ImageList_AddMasked(himg,hbm,0x00FF00FF);                            //指定紫红为透明 色    4. SendMessage(hWndToolbar,TB_SETIMAGELIST,0,(LPARAM)himg);    另一张图片如法炮制, SendMessage(m_hWndToolbar,TB_SETHOTIMAGELIST,0,(LPARAM)himg);设置为高亮图案。然 后发送 TB_INSERTBUTTON 消息给工具栏添加按钮, TBBUTTON 结构的 iBitmap 设置为图片中相 应按钮图案的序号(从 0 开始),这样生成的工具栏按钮图案是不局限于 256 色的。鼠标移到按钮 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    28 / 50    上,就显示为第 2 张图相应的高亮图案,但是按钮周围还是有一个边框,这是系统帮你画的,高亮图 案上已经画了个漂亮的边框,我们不需要这个边框,怎么去掉呢,父窗口响应 NM_CUSTOMDRAW 的 WM_NOTIFY 消息: view plain 1. case WM_NOTIFY:    2. {    3.  LPNMHDR lpnm = (LPNMHDR)lParam;    4.  if(lpnm‐>code == NM_CUSTOMDRAW)    5.  {    6.   LPNMCUSTOMDRAW lpnmdraw = (LPNMCUSTOMDRAW)lParam;    7.   if(lpnmdraw‐>dwDrawStage == CDDS_PREPAINT)    8.    return CDRF_NOTIFYITEMDRAW; //指定通知按钮自绘     9.   else if(lpnmdraw‐>dwDrawStage == CDDS_ITEMPREPAINT)    10.    return TBCDRF_NOEDGES;   //其余交给系统绘制,指定不需要绘制按钮外边框     11.  }    12.  break;    13. }    要求自绘按钮时返回 TBCDRF_NOEDGES,系统就不绘制按钮边框了,这样这个工具栏乍看就跟 QQ2009 的工具栏一样了。但是按钮按下去的状态就露馅了,因为高亮图案上画的边框也跟着图标一 起按下去了,这个边框毕竟不是真的边框,只是高亮图案罢了。当然如果这个效果你能接受,那也可 以了,至此打住,可以省不少事。 现在要使按钮按下去仅按下图标,边框位置不动。可是工具栏只能设置两个 IMAGELIST,没有按下 状态的 IMAGELIST 可以设置,因此只能自绘,就需要判断 lpnmdraw->uItemState 标志,如果是 CDIS_SELECTED,就自绘按钮的按下状态,可是要自绘按钮,又很麻烦了,画边框,画图标,画 文字...,嘿嘿,这里又有取巧的方法,我们只要画个按下状态的边框就可以了,还是返回 TBCDRF_NOEDGES,其他的系统会帮我们画的。不过系统帮我们画上去的是高亮图案,又有一个 按下去的边框,所以我们要取消高亮图案,创建工具栏的时候只需要发送 TB_SETIMAGELIST 消息 设置按钮图标就可以了,高亮和按下的边框我们自己画,准备好下面一张图: 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    29 / 50    左边是高亮边框,右边是按下的边框。自绘时根据 lpnmdraw->uItemState 标志把这张图的相应边框 画上去: view plain 1. case WM_NOTIFY:    2. {    3.  LPNMHDR lpnm = (LPNMHDR)lParam;    4.  if(lpnm‐>code == NM_CUSTOMDRAW)    5.  {    6.   LPNMCUSTOMDRAW lpnmdraw = (LPNMCUSTOMDRAW)lParam;    7.   if(lpnmdraw‐>dwDrawStage == CDDS_PREPAINT)    8.    return CDRF_NOTIFYITEMDRAW; //指定通知按钮自绘     9.   else if(lpnmdraw‐>dwDrawStage == CDDS_ITEMPREPAINT)    10.   {    11.      int sx;    12.      int off = (lpnmdraw‐>rc.bottom ‐ lpnmdraw‐>rc.top ‐ 20)/2;    13.      if((lpnmdraw‐>uItemState & CDIS_SELECTED))    14.     sx = 20;    15.    else if((lpnmdraw‐>uItemState,CDIS_HOT))    16.     sx = 0;    17.    else    18.     return TBCDRF_NOEDGES;    19.         20.    HDC hMemDC = CreateCompatibleDC(lpnmdraw‐>hdc);    21.    SelectObject(hMemDC,hbm);  //hbm 为事先加载的边框图案的 HBITMAP    22.    BitBlt(lpnmdraw‐>hdc,lpnmdraw‐>rc.left + off,lpnmdraw‐>rc.top + off,20,20,hMemDC,sx ,0,SRCCOPY);    23.    DeleteDC(hMemDC);    24.    return TBCDRF_NOEDGES;   //其余交给系统绘制,指定不需要绘制按钮外边框     25.   }    26.  }    27.  break;    28. }    OK,就这么简单,现在这个工具栏就跟 QQ2009 的工具栏的效果一样了。 现在说说演示程序里的工具栏实现,代码差不多,但是 实际复杂得多,因为界面库对此进行了封装。 界面库实现了任意子窗口和控件都可以停靠, 因此 QQ 按钮窗口和工具栏是作为停靠窗口停靠在主界 面的下方,只是设置了不能拖动。因为是停靠窗口,所以你在主窗口的 WM_SIZE 消息里看不到移动 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    30 / 50    QQ 按钮窗口和工具栏的代码,封装起来了。这个不是本篇重点,有兴趣的可以去看界面库里 ringdocksite.cpp 的代码。 现在看看程序的截图: 变化不是很大,本篇旨在教大家一个自绘按钮和自绘工 具栏的比较简单的方法,不知道说清楚没有, 有问题或是不同意见欢迎留言讨论。下一篇会实现 QQ2009 的客户区域和左下角按钮弹出的异型菜 单。 演示程序下载地址: http://d.download.csdn.net/down/2025754/ringphone 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    31 / 50    5. 循序渐进实现仿 QQ 界面演示程序编译问题及 MFC 调用 RingSDK 图象库示例  2010-01-29 22:46 6748 人阅读 评论(51) 收藏 举报 有不少朋友反映《循序渐进实现仿 QQ 界面》里面的示例程序无法编译或编译有问题,以及希望能有 MFC 的工程,在此集中解答一下。 演示程序编译问题: 1、演示程序需要最新版本的 RingSDK,请先用 SVN 到 svn://svnhost.cn/RingSDK 更新到最新版本, 推荐用 TortoiseSVN。TortoiseSVN 的下载地址: http://tortoisesvn.net/downloads,安装后在资源管 理器新建 RingSDK 目录,比如 c:/ringsdk,在该文件夹上按鼠标右键,选择 “SVN 检出”,输入地址 svn://svnhost.cn/RingSDK,按确定就获取到了最新版本的 RingSDK。 2、安装 platform sdk,可以到微软网站去下或是到如下地址: ftp://platformsdk:platformsdk@61.132.59.166/platform_sdk.zip,如果 setup 不能安装请用 setup 目 录里的 psdk_x86.msi 安装。安装后选择 VC 菜单 Tools->Options...,在弹出的对话框中选择 Directories 页,Show directories for:下拉框里选择 include files,然后在下面列表中确认 Platform SDK 的 include 目录是排在第一位。同时把 RingSDK 的 include 目录加入列表,如下图: 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    32 / 50    然后 Show directories for:下拉框里选择 Library files,确认 Platform SDK 的 lib 目录排在第一位,把 RingSDK的lib目录加入列表。如果你想调试程序时跟进 RingSDK源代码,应该把 RingSDK下libsrc 下的目录加入到 Source files 列表里面。 3、编译 RingSDK,先打开 ringsdk/libsrc/freelib 目录下 3 个子目录里的工程,这是 3 个第 3 方库, zlib,png 和 jpeg 库,编译一下。然后打开 ringsdk/ringsdk.dsw,编译 RingSDK 库 4、至此环境已经搭建完成,可以编译 ringsdk/example 下的示例程序和仿 QQ 界面的演示程序了。 MFC 如何调用 RingSDK 图象库: 这里有个演示程序, MFC 工程,下载地址: http://download.csdn.net/source/2028551 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    33 / 50    先看看程序截图: 是一个动画异性型窗口,按左键可拖动,按右键弹出菜单可选择退出。在此说明一下步骤,按此步骤 可以不用去下这个演示程序,自己就能做出来。 1、新建一 MFC 对话框工程 mfcdibdemo,选择使用 MFC 静态库。 2、选择 VC 菜单 Project->setting...,General 标签页,确认 Microsoft Foundation Classes:的选项 是 Use MFC in a Static Library,然后选择 C/C++标签页, Catgory:选择 Code Generation,然后在 下面的 Use run-time library 里面选择 Debug Multithreaded 或 Multithreaded,设置完成。 3、把上面的程序截图图片存到本地,加入到资源,资源类型输入 "GIF",连引号一起输入,资源 ID 为 IDG_BKG 4、新建菜单资源,如下图: 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    34 / 50    映射两个菜单项命令函数为 OnHelp 和 OnExit 5、CMfcdibdemoDlg 增加 WM_LBUTTONDOWN,WM_RBUTTONDOWN,WM_MEARUSEITEM,WM_DRAWITEM,消息的处 理 6、代码修改: mfcdibdemoDlg.h 的开始包含图象库和界面库的头文件: #include "ringdib.h" #include "ringdows.h" CMfcdibdemoDlg 类增加两个成员变量: RingDIB m_dib; RingCoolMenu *m_rm; 修改 mfcdibdemoDlg.cpp 文件: 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    35 / 50    CMfcdibdemoDlg::OnInitDialog 的最后,//TODO 下面添加如下代码: view plain 1. //MFC 调用 RingSDK 库必须执行此初始化,第一个参数应该是主窗口句柄,可为 NULL,第 2 个参数必须正确 传递    2. InitRingLib(m_hWnd,AfxGetResourceHandle());    3. DIBREGFORMAT(GIF);    4. m_dib.Load(MAKEINTRESOURCE(IDG_BKG),"GIF",GIF);    5. m_dib.SetTarget(m_hWnd);    6. m_dib.CreateCoolWindow();    7.     8. m_rm = new RingCoolMenu;    9. m_rm‐>LoadPopup(MAKEINTRESOURCE(IDR_POPMENU));    10. m_rm‐>SetXPStyle();    新增的消息代码: + expand sourceview plain 注意 OnMeasureItem 和 OnDrawItem 里面不能调用 CDialog::OnMeasureItem 和 CDialog::OnDrawItem,因为 MFC 会试图获取 MFC 的菜单对象但是获取不到,会非法操作。 7、完成,编译程序。 说明:用 MFC 的话其实不应该用 RingSDK 界面库,因为都是对窗口,控件做了封装,想要把仿 QQ 界面程序移植到 MFC,应该自己创建 MFC 窗口和控件,贴图代码可以照搬。     原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    36 / 50    6. 循序渐进实现仿 QQ 界面(五):半透明窗体与不透明控件  2010-02-11 11:41 5960 人阅读 评论(26) 收藏 举报 本篇演示实现仿 QQ 界面的中间客户区。 QQ 是可以调整界面透明度的,但是调整了透明度却连中间 客户区也变得半透明了。 客户区毕竟是软件最重要的部分, 是要与用户交互的, 透明了就影响操作了, 因此这里的客户区不学 QQ,始终不透明。要实现不透明控件,只能创建一个弹出窗口,遮住主界面 的客户区域,然后设定与主界面连动,即始终跟着主窗口移动及调整尺寸。这个方法并不是很好,但 却几乎是唯一的方法。为什么说几乎是唯一的方法呢?的确存在着另外的解决方案,但是这个方法太 麻烦了,在此讨论一下这个方法。 实现半透明窗体,不透明控件,应该有不少朋友碰到过这个问题:为什么设定了窗口透明,窗口上的 子窗口及控件也变得透明了?这个是受系统限制的, 创建一个窗口, 这个窗口区域就相当于一块画布, 最终系统要在这块画布上绘制出窗口标题,客户区,控件等等,而窗口和控件绘制时的 GetDC, BeginPaint 等不过是获取到了跟自己相关的这块画布的一部分,在限定的区域内绘制,最终是画到这 块画布上,系统最后显示窗口就是在桌面上显示这块画布,透明度也是认这块画布,因此上面的子窗 口及控件也一起变得透明了。另建一个弹出式窗口,相当于创建了另一块画布,就不受主窗口的透明 度限制。 知道了原因,现在来讨论如何不创建弹出式窗口,实现不透明控件。透明窗口有两种实现方法,一种 是调用 SetLayeredWindowAttributes,设定统一的窗口透明度,既整个窗口采用同样的透明度, QQ2009 和这里的演示程序采用的就是这个方法, 这个方法不创建弹出窗口是无法实现不透明控件的, 因为想要控件不透明, 就必须在控件区域采用不同的透明度, SetLayeredWindowAttributes 无法做到, 只能通过调用另一个函数 UpdateLayeredWindow 来实现可指定不同区域不同透明度的窗口, API 代 码大致如下: view plain 1. HDC hdc,hMemDC;    2. RECT rc;    3.       4. GetWindowRect(hWnd,&rc);    5.       6. POINT ptSrc = {0,0};    7. POINT ptWinPos = {rc.left,rc.top};    原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    37 / 50    8. SIZE szWin = {rc.right‐rc.left,rc.bottom‐rc.top};    9. BLENDFUNCTION stBlend = { AC_SRC_OVER, 0,255,AC_SRC_ALPHA};    10.        11. hdc = GetWindowDC(m_hWnd);    12. hMemDC = CreateCompatibleDC(hdc);    13. SelectObject(hMemDC,hbmp);       //hbmp 为整个窗口贴图     14. 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 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    38 / 50    控件了,调整窗口尺寸时 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://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    39 / 50    差不多快完成了,还差滚动条和异型菜单,下一篇再说了。 演示程序下载地址: http://download.csdn.net/source/2059841         原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    40 / 50    7. 循序渐进实现仿 QQ 界面(六):异型菜单与内建滚动条自绘  2010-03-13 21:51 7337 人阅读 评论(49) 收藏 举报 本篇演示实现仿 QQ 界面的异型菜单与滚动条自绘。 先讲解一下如何实现 QQ 的圆角菜单,这个要用到 HOOK 了,因为菜单是一种特殊的窗口,无法用 FindWindow 或通过 HMENU 来获取到窗口句柄,也就无法子类化。必须下钩子,这里下的是 WH_CALLWNDPROC 的钩子: view plain 1. BOOL QQMenu::InstallHook()    2. {    3.  if(m_hMenuHook == NULL)    4.   m_hMenuHook = SetWindowsHookEx(WH_CALLWNDPROC,MenuHook,GetInstance(),GetCurrentThrea dId());    5.  return (BOOL)m_hMenuHook;    6. }    7.     8. LRESULT CALLBACK QQMenu::MenuHook(int code,WPARAM wParam,LPARAM lParam)    9. {    10.  PCWPSTRUCT lpCwp = (PCWPSTRUCT)lParam;    11.      12.  if(code == HC_ACTION && lpCwp‐>message == WM_CREATE)    13.  {    14.   //检测是否是菜单     15.   TCHAR szClassName[16];    16.   int nCount = GetClassName(lpCwp‐>hwnd,szClassName,12);    17.   if(nCount == 6 && lstrcmpi(szClassName,_T("#32768")) == 0)    18.   {    19.    QQMenu* pMenu = (QQMenu*)GetWindowPtr(lpCwp‐>hwnd);    20.    if(pMenu == NULL)    21.    {    22.     //未子类化     23.     pMenu = window‐>NewRingObj(pMenu,TRUE);    24.     pMenu‐>Attach(lpCwp‐>hwnd);    25.    }    26.   }    原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    41 / 50    27.  }    28.  return CallNextHookEx(m_hMenuHook,code,wParam,lParam);    29. }    查 MSDN 知道菜单的窗口类名是 “#32768”,找到菜单窗口就好办了,子类化后就可以做手脚了,做 的手脚不多,只需要实现圆角矩形外形就够了,菜单的自绘早已实现,是交由主窗口处理的,就不必 费手脚了。 首先是 WM_NCCALCSIZE 消息,需要加大其非客户区,为画圆角腾出空间来,但是注意腾出了空间 画圆角,客户区的面积就缩小了,有可能会使菜单项显示不全,因此需要在 WM_WINDOWPOSCHANGING 消息里把吃掉的客户区空间再吐出来,就是加大菜单窗口的尺寸, 这样才不影响菜单项的显示。怎么实现及绘制圆角矩形窗口这个系列文章的第一篇就讲过了,这里就 不多费口舌了。 回答一下有位朋友在我博客前面文章的留言,为什么子类化菜单后截不到 WM_MOUSEMOVE 消息, 试了一下的确没有,其实菜单的 WM_MOUSEMOVE 消息被替换掉了,变成了 MN_MOUSEMOVE 消息,值为 0x01EE,定义看这个链接: http://topic.csdn.net/t/20050713/18/4142641.html 接下来讲解怎么实现 QQ 左下角按钮上弹出来的异型菜单, 一般做法是自己实现一个异型窗口然后加 载菜单,不过既然是 HOOK 了菜单,还是直接在菜单上实现比较好。首先一个问题是怎么区分这个 主菜单和其他圆角菜单,才好实现不同的处理。方法是主菜单创建的时候调用 SetMenuInfo 为其设置 一个值,以后可以通过 GetMenuInfo 来检测这个值以鉴别是主菜单: view plain 1. //设置数据标识 QQ 主菜单     2. MENUINFO mf;    3. mf.cbSize = sizeof(MENUINFO);    4. mf.fMask = MIM_MENUDATA;    5. mf.dwMenuData = ID_QQMENU;    6. m_rmQQ‐>SetMenuInfo(&mf);    原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    42 / 50    鉴别出主菜单后就可以实现不同的外形处理了,这个菜单的外形可以有两种方案,一个是带上 QQ 圆 形按钮的外形,弹出时盖住 QQ 按钮。不过考虑到 QQ 图案不能调色,绘制时要多道手脚专门画那个 企鹅,还是省点事采用挖掉那个圆形的外形,如下图(左边的图像): 由于菜单项是可增减的,因此这个外形必须动态生成,几何学得好的话可以用线条绘制出来,我是属 于几何学得不好的,早忘光了 :P,因此采用圆角矩形和图片合成的方式来构造这个外形。看上图中的 右图,兰色边框勾勒出了圆角矩形,可以用 CreateRoundRectRgn 生成,红色虚线勾出了需要在这 个圆角矩形区域挖掉和添加的部分,黄色是这两个 HRGN 相交叠的部分,这块区域是固定尺寸不会 变动的,因此可以用图片实现,根据这个图来生成 HRGN。这张图片如下: 形状有点怪异,黑色部分就是需要生成的 HRGN 形状,生成 HRGN 后可以调用 CombineRgn 来与 圆角矩形的 HRGN 合并,参数采用 RGN_XOR: view plain 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    43 / 50    1. CombineRgn(m_hrgn,m_hrgn,hrgn,RGN_XOR);    m_hrgn 是圆角矩形,hrgn 是图中黑色部分形状的 HRGN,采用 RGN_XOR 参数的效果是:合并两 个形状,去掉重叠的部分,结果就是圆角矩形在左下角挖掉了图中半圆的形状,添加了下面的圆弧三 角,恰好符合 QQ 主菜单的外形。 下面讲解一下如何根据图片来生成 HRGN,原理是逐行扫描像素,发现是白色(透明色)不处理,发 现是黑色(非透明色)就根据连续的像素数量调用 CreateRectRgn 来生成高度为 1 的 HRGN,每一 扫描行可能会有一些不连续的点或线,可以通过 CombineRgn,采用 RGN_OR 参数把这些不连续的 HRGN 合并起来,然后把整个图象所有扫描行生成的这些 HRGN 再合并,最终就得到了图片中指定 形状的 HRGN,代码有点复杂,这里就不贴了,可以去看 RingSDK 图象库 libsrc/ringdib/rdib.cpp 里 面的 RingDIB::CreateRgn 函数代码。 得到最终的 HRGN 后就可以通过 SetWindowRgn 实现异型菜单了,当然还要准备好窗口非客户区的 贴图,左下角的贴图如下: 怎么贴图绘制就不说了,前面的文章都讲解过了。 接下来就是菜单的弹出问题,计算好位置弹出,貌似可以了,其实还是有工作要做,把窗口移到桌面 上方,再点按钮弹出菜单,晕,菜单是往下弹出了,缺口跑到主界面下方去了,必须限制菜单的弹出 方向,这时要用到 TrackPopupMenuEx 函数的最后一个参数 LPTPMPARAMS 了,这个参数一般调 用时是指定为 NULL,其实是可以用这个参数限制菜单的弹出方向的,这个参数说明如下: view plain 1. typedef struct tagTPMPARAMS {     2.   UINT cbSize;     3.   RECT rcExclude;     4. } TPMPARAMS, *LPTPMPARAMS;     5.     6. cbSize :Size of structure, in bytes.     7. rcExclude :Rectangle to exclude when positioning the window, in screen coordinates    原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    44 / 50    指定 rcExclude 的坐标,菜单弹出时就会避开这个区域,我们把主窗口下方的空间坐标赋给这个 rcExclude,主菜单弹出时为了避免覆盖这个坐标,就只能往上弹出,这下终于达到我们期望的效果 了。不过且慢,这个 rcExclude 只是个矩形,我们拦住了菜单不往下弹,却拦不住左右,把窗口移到 屏幕最右边,再弹出菜单,继续晕,菜单往左弹出了!缺口跑到了左边,又露馅了。这下没有取巧的 办法,只能实现菜单外形的左右翻转,同时因为左右翻转,还需要调整菜单的弹出位置以使缺口套准 QQ 圆形按钮。弹出菜单的代码如下: view plain 1. RINGCMD(WndQQButton,OnQQMenuPopup)    2. {    3.  if(event == STN_CLICKED)    4.  {    5.   RECT rc,rcEx;    6.   int xoff = 0;    7.   GetWindowRect(m_hWnd,&rc);    8.   rc.left += QQBTN_X;    9.   rc.bottom ‐= (window‐>m_dibQQCorner.Height() + QQBTN_QQ_HEIGHT + 3);    10.   //菜单如果靠屏幕右边弹出,需要横向翻转其不规则外形,则需要调整其弹出位置的 X 坐标    11.   if(rc.left + QQMENU_WIDTH > WINVAR(SM_CXSCREEN))    12.    xoff = QQMENU_OFFSET;    13.   //限制菜单弹出位置,使其始终向上弹出。     14.   rcEx.left = 0;    15.   rcEx.right = rc.right;    16.   rcEx.top = rc.bottom + 1;    17.   rcEx.bottom = WINVAR(SM_CYSCREEN);    18.   //先置标记通知刷新,绘制 QQ 按钮上的企鹅     19.   m_bMenuPopup = TRUE;    20.   InvalidateRect(m_hWnd,NULL,TRUE);    21.   //弹出菜单,设定避开 rcEx 指定的坐标     22.   window‐>m_rmQQ‐>PopupEx(m_hWnd,rc.left+xoff,rc.bottom,FALSE,&rcEx);    23.  }    24. }    翻转菜单不规则外形,其实是先把左下角 HRGN 图象横向翻转,再生成 HRGN。图象横向翻转其实 就是把图象的每一行数据数组进行倒序,属于 C 语言的入门功课了,这里就不说了。 现在终于实现了 QQ 的异型主菜单,剩下最后好友列表区的滚动条了。 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    45 / 50    自绘滚动条的实现: 自绘滚动条其实很简单,看过这个系列前面的文章大家应该会了,子类化后贴图不是很难。但是 QQ 好友列表区的滚动条自绘却不是那么简单,因为好友列表是用 ListBox 控件实现,而 ListBox 的滚动 条是内建的,是画出来的,根本不是窗口!没办法子类化,这下完蛋,只能子类化 ListBox,自己实 现绘制滚动条的代码。然而有个要命的地方, ListBox 绘制滚动条既没提供接口,也没提供消息,光 在 WM_NCPAINT 里面绘制没用,有很多消息都是直接进行了滚动条的绘制,这下子就需要围追堵 截,把可能绘制滚动条的消息全部截过来自己处理。除了 WM_NCPAINT,需要拦截的消息如下: WM_STYLECHANGED WM_LBUTTONDBLCLK WM_NCLBUTTONDBLCLK WM_TIMER WM_LBUTTONDOWN WM_NCLBUTTONDOWN WM_LBUTTONUP WM_MOUSEWHEEL WM_SIZE WM_VSCROLL 那个晕哪!有这工夫还不如自己写个窗口实现了。事实也确实是,研究这些消息的时间够自己写个窗 口实现了。不过实现 ListBox 的滚动条自绘还是有意义的,一个个消息来吧。 首先当然要实现 WM_NCPAINT 消息,把滚动条画出来,这个不难。实现之后窗口被其他程序覆盖, 再切换到前台,滚动条象样子了,不过滚动条一动就露馅了。 然后是 WM_VSCROLL,ListBox 有个窗口类型是 LBS_DISABLENOSCROLL,说明没有滚动条, 窗口还是能滚动的,因此在这个消息里面,先把窗口的 WS_VSCROLL 类型去掉,然后调用默认的 窗口过程,再把 WS_VSCROLL 类型设置回来。现在效果是按翻页滚动不会露馅,发送 WM_VSCROLL 消息设定滚动条位置不会露馅。 view plain 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    46 / 50    1. case WM_VSCROLL:    2. {    3.     ReplaceStyle(WS_VSCROLL,0);    4.     LRESULT res = RingListBox::RingdowProc(m_hWnd,param);    5.     ReplaceStyle(0,WS_VSCROLL);    6.     return res;    7. }    WM_SIZE 消息做同样处理,这下调整窗口大小也不会露馅了。 WM_STYLECHANGED,WM_LBUTTONDBLCLK,WM_NCLBUTTONDBLCLK 三个消息拦截掉, 直接 return 0;这下在窗口上双击不会露馅了。 WM_LBUTTONDOWN,因为设定了 ListBox 的 LBS_NOINTEGRALHEIGHT 类型,因此 ListBox 的 最下面一个可视的选项有可能是显示不完全的,如果选择了这个选项, ListBox 会滚动窗口让这个选 项完全显示,这时会重绘滚动条,因此需要在这个消息里检测如果选中的是没有显示完整的选项, 就发送 wParam 为 SB_LINEDOWN 的 WM_VSCROLL 消息让其显示完全,然后选中该选项, return 0;其余情况交给默认的窗口过程处理。 WM_MOUSEWHEEL:计算好要滚动的位置,然后连着发送下面两个消息就 OK 了。 view plain 1. SendMessage(m_hWnd,WM_VSCROLL,MAKELONG(SB_THUMBPOSITION,nPos),0);    2. SendMessage(m_hWnd,WM_VSCROLL,SB_ENDSCROLL,0);    接下来就来到最困难的部分,点击滚动条上的上下箭头和拖动滑块。这个需要先处理 WM_NCLBUTTONDOWN 和 WM_LBUTTONUP 消息, WM_NCLBUTTONDOWN 消息里面先检测 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    47 / 50    鼠标是否点在了滚动条上,是就要 SetCapture 捕获鼠标,如果是按在上下箭头或不在滑块上,就设 定一下定时器,在 WM_TIMER 消息里发送 wParam 为 SB_LINEUP,SB_LINEDOWN,SB_PAGEUP,SB_PAGEDOWN 的 WM_VSCROLL 消息,可以连续 滚动。WM_LBUTTONUP 消息里 ReleaseCapture,清除定时器, 再发送 wParam 为 SB_ENDSCROLL 的 WM_VSCROLL 消息终止滚动。 最难的就是拖动滑块的处理了,必须自己绘制滑块位置,定时器检测并绘制滑块位置,当检测到滑块 移动的距离足够需要滚动窗口的时候,就需要计算出滚动条的滑块位置并发送 wParam 为 SB_THUMBPOSITION 的 WM_VSCROLL 消息,ListBox 的滚动条滚动范围( GetScrollRange 返回 的值)是选项总数 -1,绘制时是按像素,因此计算时难免会有误差,会造成滑块拖动时的偶尔抖动, 不过总算是实现了 ListBox 内建的滚动条自绘,演示程序就先这样了,要想达到商用级别尚需进行代 码优化和调整。 现在看看程序的截图: 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    48 / 50    终于完成了,长吁一口气,在此谢谢大家一直以来的支持和鼓励。 演示程序下载地址: http://download.csdn.net/source/2125267 说明:在编写这个演示程序的时候发现和修正了 RingSDK 的几个 BUG,因此编译这个演示程序需要 更新到最新版本的 RingSDK,不更新的话主菜单和部分调色功能可能会不正常,不过不影响效果演 示。这个演示程序的源代码也已经提交到 SVN,更新 RingSDK 的时候会自动下载下来,可以不用去 下演示程序。 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    49 / 50    8. RingSDK 界面库已改为 LGPL 协议  2010-01-05 02:12 3253 人阅读 评论(13) 收藏 举报 RingSDK 界面库现已改为 LGPL 协议,不再有使用限制了。 同时修正了一些 BUG,增加了一些功能, 大家可以到 svn://svnhost.cn/RingSDK 用 SVN 更新,文件打包太麻烦,因此不再提供文件下载方式 更新。 这次更新主要是改了版权协议, 还有修改并使用了 ANSI 和 UNICODE 的兼容函数, 可以通过 VC2008 的编译,不过没提供 VC2008 的工程文件,使用 VC2008 的朋友可以直接点 DSW 文件进行转换。 完成了 OUTLOOK 风格的导航栏,并写了个 DEMO,支持标准 OUTLOOK 和 OUTLOOK2003 风格。 示例程序截图如下: 原文地址:http://blog.csdn.net/ringphone                                凡尘制作:http://fanchen.org    50 / 50    帮助文件还没更新,最怕写这东东,慢慢来吧。 在此谢谢大家的支持,接下来会写一些比较酷一点的示例程序和教程,比如仿 QQ 界面,敬请期待。                                
还剩49页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享pdf获得金币 ] 7 人已下载

下载pdf

pdf贡献者

fanchen

贡献于2011-11-08

下载需要 15 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf