Introduction
It's my first article. At first I must express my thank to codeproject and all of selflessness people.
I have tried to look for a sample show me how to skin a window's internal scrollbar, but, unfortunately, I failed. some days ago, I got inspiration: In order to skin a window's internal scrollbar, it may be possible to hide a window's scrollbar below a frame window whose size is smaller than the window but is the window's parent.
I have a try and I success!
In my code you will find two main components:
Cskinscrollbar (Drived From cscrollbar) and cskinscrollwnd (Drived From cwnd)
Cskinscrollbar offers a owner draw scrollbar. what I have done is to handle mouse input and paint messge simply, and I'm not intent to descript it in detail. if you are interesting with it, you can look into my code.
Cskinscrollwnd is the code's core. Collapse
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 it's 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: user 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 infomation 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 CALLBACKHookWndProc(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 );
With my code's help, you just need add a line of code in your code. for example, assume you have a treectrl in a window and you want to replace it's scrollbar, at first, you give it a name m_ctrltree, the next step is when it got initialized, add a line like this "skinwndscroll (& m_ctrltree, hbmpscroll )".
How to test my project?
There are 4 types of control in the interface, including ListBox, treectrl, editctrl, richeditctrl respectively. clicking list_addstring button will fill listctrl and you will see a left scrollbar. clicking tree_addnode button will fill treectrl and you may see two ownerdraw scrollbar Replace It's internal scrollbar. input text in two of editboxes to see whether it works.
How to prepare your scrollbar bitmap?
Both vertical and horizontal scrollbar require 4 image segments. they are arrow-up/arrow-left, slide, thumb and arrow-down/arrow-right.each of them include 3 state: Normal, hover, press (it is possible to extent to surport 4 State easily. because I'm not good at image process, the sample bitmap if came from a software's resource .) beside those segments, the bitmap include two angle segments locating at bitmap's right.
Now I want to show you the problems I have encounted.
1. when I begin this code, I have tried to use a scrollbarctrl to cover the window's internal scrollbar. in my mind, only if my scrollbar window's Z order is higher it will work well. but in fact, it does not work. althrough my scrollbar window's Z-order is higher, when mouse moves to scrollbar area, the internal scrollbar will Rander immediately. I have to add a new window as a frame to the target window.
2. at first, I does not intent to surport leftscrollbar style, and my code work well. finally, I deside to surport it. but what makes me depression is it don't work any more. for a long time of debugging, I found it's wrong to move window in subclass callback function. so I define a user message, and post the message to message queue.
How to apply it to a listctrl?
ThankKangcornFor finding the problem.
When apply it to listctrl, traging thumb box will have none effect. I try all my best to deal with it. Now I show a alt method.
I derive a new class from clistctrl and handle wm_vscroll/wm_hscroll with a sbcode equal to sb_thumbtrack.
For example:
Collapse
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);}
OK, that's all. Hope it will be helpful to you. Any suggestion will be wellcome.
// Website: http://www.codeproject.com/useritems/skinscrollbar.asp