介绍 这是我的第一篇文章。首先,我要感谢CodeProject和所有无私的人们。 我试图寻找一个示例来演示如何皮肤窗口的内部滚动条,但不幸的是,我失败了。几天前,我得到了灵感:为了给窗口的内部滚动条皮肤,可以将窗口的滚动条隐藏在一个框架窗口下面,该框架窗口的大小比窗口小,但却是窗口的父窗口。 我试了一下,成功了! 两个主要的组件 在我的代码,你会发现两个主要组件: CSkinScrollBar(派生自CScrollBar) CSkinScrollBar提供了一个所有者绘制滚动条。我所做的只是简单地处理鼠标输入和绘制消息,我不打算详细描述它。如果你感兴趣,你可以看看我的代码。 CSkinScrollWnd是代码的核心 隐藏,收缩,复制Code
BOOL CSkinScrollWnd::SkinWindow(CWnd *pWnd,HBITMAP hBmpScroll) {//create a frame windows set ASSERT(m_hWnd==NULL); m_hBmpScroll=hBmpScroll; //calc scrollbar wid/hei according to the input bitmap handle BITMAP bm; GetObject(hBmpScroll,sizeof(bm),&bm); m_nScrollWid=bm.bmWidth/9; CWnd *pParent=pWnd->GetParent(); ASSERT(pParent); RECT rcFrm,rcWnd; pWnd->GetWindowRect(&rcFrm); pParent->ScreenToClient(&rcFrm); rcWnd=rcFrm; OffsetRect(&rcWnd,-rcWnd.left,-rcWnd.top); UINT uID=pWnd->GetDlgCtrlID(); //remove original window's border style and add it to frame window DWORD dwStyle=pWnd->GetStyle(); DWORD dwFrmStyle=WS_CHILD|SS_NOTIFY; DWORD dwFrmStyleEx=0; if(dwStyle&WS_VISIBLE) dwFrmStyle|=WS_VISIBLE; if(dwStyle&WS_BORDER) { dwFrmStyle|=WS_BORDER; pWnd->ModifyStyle(WS_BORDER,0); int nBorder=::GetSystemMetrics(SM_CXBORDER); rcWnd.right-=nBorder*2; rcWnd.bottom-=nBorder*2; } DWORD dwExStyle=pWnd->GetExStyle(); if(dwExStyle&WS_EX_CLIENTEDGE) { pWnd->ModifyStyleEx(WS_EX_CLIENTEDGE,0); int nBorder=::GetSystemMetrics(SM_CXEDGE); rcWnd.right-=nBorder*2; rcWnd.bottom-=nBorder*2; dwFrmStyleEx|=WS_EX_CLIENTEDGE; } //create frame window at original window's rectangle and //set its ID equal to original window's ID. this->CreateEx(dwFrmStyleEx,AfxRegisterWndClass(NULL), "SkinScrollBarFrame",dwFrmStyle,rcFrm,pParent,uID); //create a limit window. it will clip target window's scrollbar. m_wndLimit.Create(NULL,"LIMIT",WS_CHILD|WS_VISIBLE,CRect(0,0,0,0),this,200); //create my scrollbar ctrl m_sbHorz.Create(WS_CHILD,CRect(0,0,0,0),this,100); m_sbVert.Create(WS_CHILD|SBS_VERT,CRect(0,0,0,0),this,101); m_sbHorz.SetBitmap(m_hBmpScroll); m_sbVert.SetBitmap(m_hBmpScroll); //change target's parent to limit window pWnd->SetParent(&m_wndLimit); //attach CSkinScrollWnd data to target window's userdata. //Remark: use this code, obviously, you will never try to use userdata!! SetWindowLong(pWnd->m_hWnd,GWL_USERDATA,(LONG)this); //subclass target window's wndproc m_funOldProc=(WNDPROC)SetWindowLong(pWnd->m_hWnd,GWL_WNDPROC,(LONG)HookWndProc); pWnd->MoveWindow(&rcWnd); //set a timer. it will update scrollbar's information at times. //I have tried to hook some messages so as to update scrollinfo timely. //For example, WM_ERESEBKGND and WM_PAINT. //But with spy++'s aid, I found if the window's client area need not update, // my hook proc would hook nothing except some control-depending interfaces. SetTimer(TIMER_UPDATE,500,NULL); return TRUE; } static LRESULT CALLBACK HookWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {//my hook function CSkinScrollWnd *pSkin=(CSkinScrollWnd*)GetWindowLong(hwnd,GWL_USERDATA); LRESULT lr=::CallWindowProc(pSkin->m_funOldProc,hwnd,msg,wp,lp); if(pSkin->m_bOp) return lr; if(msg==WM_ERASEBKGND) {//update scroll info SCROLLINFO si; DWORD dwStyle=::GetWindowLong(hwnd,GWL_STYLE); if(dwStyle&WS_VSCROLL) { memset(&si,0,sizeof(si)); si.cbSize=sizeof(si); si.fMask=SIF_ALL; ::GetScrollInfo(hwnd,SB_VERT,&si); pSkin->m_sbVert.SetScrollInfo(&si); pSkin->m_sbVert.EnableWindow(si.nMax>=si.nPage); } if(dwStyle&WS_HSCROLL) { memset(&si,0,sizeof(si)); si.cbSize=sizeof(si); si.fMask=SIF_ALL; ::GetScrollInfo(hwnd,SB_HORZ,&si); pSkin->m_sbHorz.SetScrollInfo(&si); pSkin->m_sbHorz.EnableWindow(si.nMax>=si.nPage); } }else if(msg==WM_NCCALCSIZE && wp) {//recalculate scroll bar display area. LPNCCALCSIZE_PARAMS pNcCalcSizeParam=(LPNCCALCSIZE_PARAMS)lp; DWORD dwStyle=::GetWindowLong(hwnd,GWL_STYLE); DWORD dwExStyle=::GetWindowLong(hwnd,GWL_EXSTYLE); BOOL bLeftScroll=dwExStyle&WS_EX_LEFTSCROLLBAR; int nWid=::GetSystemMetrics(SM_CXVSCROLL); if(dwStyle&WS_VSCROLL) { if(bLeftScroll) pNcCalcSizeParam->rgrc[0].left-=nWid-pSkin->m_nScrollWid; else pNcCalcSizeParam->rgrc[0].right+=nWid-pSkin->m_nScrollWid; } if(dwStyle&WS_HSCROLL) pNcCalcSizeParam->rgrc[0].bottom+=nWid-pSkin->m_nScrollWid; RECT rc,rcVert,rcHorz; ::GetWindowRect(hwnd,&rc); ::OffsetRect(&rc,-rc.left,-rc.top); nWid=pSkin->m_nScrollWid; if(bLeftScroll) { int nLeft=pNcCalcSizeParam->rgrc[0].left; int nBottom=pNcCalcSizeParam->rgrc[0].bottom; rcVert.right=nLeft; rcVert.left=nLeft-nWid; rcVert.top=0; rcVert.bottom=nBottom; rcHorz.left=nLeft; rcHorz.right=pNcCalcSizeParam->rgrc[0].right; rcHorz.top=nBottom; rcHorz.bottom=nBottom+nWid; }else { int nRight=pNcCalcSizeParam->rgrc[0].right; int nBottom=pNcCalcSizeParam->rgrc[0].bottom; rcVert.left=nRight; rcVert.right=nRight+nWid; rcVert.top=0; rcVert.bottom=nBottom; rcHorz.left=0; rcHorz.right=nRight; rcHorz.top=nBottom; rcHorz.bottom=nBottom+nWid; } if(dwStyle&WS_VSCROLL && dwStyle&WS_HSCROLL) { pSkin->m_nAngleType=bLeftScroll?1:2; }else { pSkin->m_nAngleType=0; } if(dwStyle&WS_VSCROLL) { pSkin->m_sbVert.MoveWindow(&rcVert); pSkin->m_sbVert.ShowWindow(SW_SHOW); }else { pSkin->m_sbVert.ShowWindow(SW_HIDE); } if(dwStyle&WS_HSCROLL) { pSkin->m_sbHorz.MoveWindow(&rcHorz); pSkin->m_sbHorz.ShowWindow(SW_SHOW); }else { pSkin->m_sbHorz.ShowWindow(SW_HIDE); } pSkin->PostMessage(UM_DESTMOVE,dwStyle&WS_VSCROLL,bLeftScroll); } return lr; } //the only global function //param[in] CWnd *pWnd: target window //param[in] HBITMAP hBmpScroll: bitmap handle used by scrollbar control. //return CSkinScrollWnd*:the frame pointer
CSkinScrollWnd* SkinWndScroll(CWnd *pWnd,HBITMAP hBmpScroll); 在我的代码的帮助下,您只需要在您的代码中添加一行代码。例如,假设您在窗口中有一个treectrl,并且您想替换它的滚动条。首先,你给它一个名字m_ctrlTree。下一步是当它被初始化时,添加如下一行: 隐藏,复制Code
SkinWndScroll(&m_ctrlTree,hBmpScroll)
如何测试我的项目? 界面中有4种控件,分别是listbox、treectrl、editctrl、richeditctrl。单击list_addstring按钮将填充listctrl,您将看到一个左边的滚动条。点击tree_addnode按钮将填充treectrl,您可能会看到两个ownerdraw滚动条替换了它的内部滚动条。在两个编辑框中输入文本,以查看它是否工作。 如何准备你的滚动条位图? 垂直和水平滚动条都需要4个图像片段。它们是向上/向左,滑动,拇指和向下/向右。每一个都包括3种状态:正常状态,悬停状态,按下状态。(很容易扩展对国家的支持。因为我不擅长图像处理,所以这个位图样本来自于一个软件的资源。)除了这些片段,位图还包括位于位图右侧的两个角度片段。 现在我想向你们展示我遇到的问题 当我开始这段代码时,我尝试使用scrollbarctrl来覆盖窗口的内部滚动条。在我看来,只有滚动条窗口的Z阶较高,它才能正常工作。但事实上,这是行不通的。虽然我的滚动条窗口的z顺序较高,但当鼠标移动到滚动条区域时,内部的滚动条将立即呈现。我必须添加一个新窗口作为一个框架到目标窗口。一开始,我并没有打算支持leftscrollbar样式,所以我的代码运行得很好。最后,我决定支持它。但让我沮丧的是,它不再工作。在花了很多时间调试后,我发现它是错误的移动窗口的子类回调函数。因此,我定义了一个用户消息,并将该消息发布到消息队列。 如何应用它到一个列表ctrl ? 感谢康康发现了这个问题。 当将其应用到ListCtrl时,拖动拇指框将不起作用。我尽了我最大的努力去处理它。现在我向你们展示另一种方法。 我从CListCtrl派生了一个新类,并使用sbcode = SB_THUMBTRACK来处理WM_VSCROLL/WM_HSCROLL。 例如: 隐藏,收缩,复制Code
LRESULT CListCtrlEx::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { if(message==WM_VSCROLL||message==WM_HSCROLL) { WORD sbCode=LOWORD(wParam); if(sbCode==SB_THUMBTRACK ||sbCode==SB_THUMBPOSITION) { SCROLLINFO siv={0}; siv.cbSize=sizeof(SCROLLINFO); siv.fMask=SIF_ALL; SCROLLINFO sih=siv; int nPos=HIWORD(wParam); CRect rcClient; GetClientRect(&rcClient); GetScrollInfo(SB_VERT,&siv); GetScrollInfo(SB_HORZ,&sih); SIZE sizeAll; if(sih.nPage==0) sizeAll.cx=rcClient.right; else sizeAll.cx=rcClient.right*(sih.nMax+1)/sih.nPage ; if(siv.nPage==0) sizeAll.cy=rcClient.bottom; else sizeAll.cy=rcClient.bottom*(siv.nMax+1)/siv.nPage ; SIZE size={0,0}; if(WM_VSCROLL==message) { size.cx=sizeAll.cx*sih.nPos/(sih.nMax+1); size.cy=sizeAll.cy*(nPos-siv.nPos)/(siv.nMax+1); }else { size.cx=sizeAll.cx*(nPos-sih.nPos)/(sih.nMax+1); size.cy=sizeAll.cy*siv.nPos/(siv.nMax+1); } Scroll(size); return 1; } } return CListCtrl::WindowProc(message, wParam, lParam); }
好的,就这样。希望对你有帮助。欢迎提出任何建议。 历史 2007-03-07 修正了skinscrollwnd.cpp中的错误比较。谢谢巫师们向我报告这件事。 2007.1.23 仔细测试了项目并进行了一些优化 2007.1.22 修正了一个滚动条的零div错误,修改了滚动条的自动滚动代码 2006.12.22 修改代码以将其应用于组合ctrl 2006.7.26 展示了一个方法,应用它到ListCtrl等。 2006.7.12 修正了一个滚动条的错误 2006.7.9 完成一个主框架 本文转载于:http://www.diyabc.com/frontweb/news6970.html