这一篇本来应该演示如何实现仿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和WM_MOUSELEAVE消息,这两个消息会在鼠标移入控件和离开控件各发送一次,因此不需要象前面的贴图按钮一样用个标志记录是否绘制过按钮的各种状态,只需要在WM_MOUSEHOVER消息里绘制按钮的高亮状态,在WM_MOUSELEAVE消息里恢复按钮的原始状态就行了。界面库的RingStatic类因为在这两个消息里有自己的处理,没有把这两个消息转发给父窗口,而是发送了WM_LINKHOVER和WM_LINKLEAVE两个自定义消息,因此演示程序的绘制是在这两个消息里处理,实际是跟WM_MOUSEHOVER和WM_MOUSELEAVE消息一样的。绘制代码很简单,把两张用到的资源图片贴出来,大家一看就知道了,就是把图片的不同区域绘制到窗口。第2张图片采用连续绘制实现了动画显示,因为需要调色的关系,这张图片是实时生成的。
RINGMSG(WndQQButton,WM_LINKHOVER) { if((HWND)param.wParam == m_btn->Handle()) { int sx = BTN_PICWIDTH; if(m_bIsSideToolHide) sx += m_dibArrBtn.Width()/2; m_dibArrBtn.Draw((BTN_WIDTH - BTN_PICWIDTH)/2,0,sx,0,BTN_PICWIDTH,BTN_PICHEIGHT,BTN_PICWIDTH,BTN_PICHEIGHT); } else if((HWND)param.wParam == m_btnQQ->Handle()) { m_dibQQBtn.Draw(0,0,0,0,QQBTN_WIDTH,QQBTN_HEIGHT,QQBTN_WIDTH,QQBTN_HEIGHT); Sleep(80); m_dibQQBtn.Draw(0,0,QQBTN_WIDTH,0,QQBTN_WIDTH,QQBTN_HEIGHT,QQBTN_WIDTH,QQBTN_HEIGHT); Sleep(80); m_dibQQBtn.Draw(0,0,QQBTN_WIDTH*2,0,QQBTN_WIDTH,QQBTN_HEIGHT,QQBTN_WIDTH,QQBTN_HEIGHT); } return TRUE; } RINGMSG(WndQQButton,WM_LINKLEAVE) { if((HWND)param.wParam == m_btn->Handle()) { int sx = 0; if(m_bIsSideToolHide) sx = m_dibArrBtn.Width()/2; m_dibArrBtn.Draw((BTN_WIDTH - BTN_PICWIDTH)/2,0,sx,0,BTN_PICWIDTH,BTN_PICHEIGHT,BTN_PICWIDTH,BTN_PICHEIGHT); } else if((HWND)param.wParam == m_btnQQ->Handle()) { m_dibQQBtn.Draw(0,0,QQBTN_WIDTH,0,QQBTN_WIDTH,QQBTN_HEIGHT,QQBTN_WIDTH,QQBTN_HEIGHT); Sleep(80); m_dibQQBtn.Draw(0,0,0,0,QQBTN_WIDTH,QQBTN_HEIGHT,QQBTN_WIDTH,QQBTN_HEIGHT); Sleep(80); m_dibBkg.Draw(0,0,QQBTN_X,QQBTN_Y,QQBTN_WIDTH,QQBTN_HEIGHT,QQBTN_WIDTH,QQBTN_HEIGHT); } return TRUE; }
HBITMAP = LoadBitmap(...); HIMAGELIST himg = ImageList_Create(20,20,ILC_COLOR32|ILC_MASK,9,4); //这里的参数请根据图片自行调整 ImageList_AddMasked(himg,hbm,0x00FF00FF); //指定紫红为透明色 SendMessage(hWndToolbar,TB_SETIMAGELIST,0,(LPARAM)himg);
case WM_NOTIFY: { LPNMHDR lpnm = (LPNMHDR)lParam; if(lpnm->code == NM_CUSTOMDRAW) { LPNMCUSTOMDRAW lpnmdraw = (LPNMCUSTOMDRAW)lParam; if(lpnmdraw->dwDrawStage == CDDS_PREPAINT) return CDRF_NOTIFYITEMDRAW; //指定通知按钮自绘 else if(lpnmdraw->dwDrawStage == CDDS_ITEMPREPAINT) return TBCDRF_NOEDGES; //其余交给系统绘制,指定不需要绘制按钮外边框 } break; }
case WM_NOTIFY: { LPNMHDR lpnm = (LPNMHDR)lParam; if(lpnm->code == NM_CUSTOMDRAW) { LPNMCUSTOMDRAW lpnmdraw = (LPNMCUSTOMDRAW)lParam; if(lpnmdraw->dwDrawStage == CDDS_PREPAINT) return CDRF_NOTIFYITEMDRAW; //指定通知按钮自绘 else if(lpnmdraw->dwDrawStage == CDDS_ITEMPREPAINT) { int sx; int off = (lpnmdraw->rc.bottom - lpnmdraw->rc.top - 20)/2; if((lpnmdraw->uItemState & CDIS_SELECTED)) sx = 20; else if((lpnmdraw->uItemState,CDIS_HOT)) sx = 0; else return TBCDRF_NOEDGES; HDC hMemDC = CreateCompatibleDC(lpnmdraw->hdc); SelectObject(hMemDC,hbm); //hbm为事先加载的边框图案的HBITMAP BitBlt(lpnmdraw->hdc,lpnmdraw->rc.left + off,lpnmdraw->rc.top + off,20,20,hMemDC,sx,0,SRCCOPY); DeleteDC(hMemDC); return TBCDRF_NOEDGES; //其余交给系统绘制,指定不需要绘制按钮外边框 } } break; }