Introduction
This is my first article. At first, I must express my thanks to CodeProject and all the selfless people.
I have tried to look for a sample to 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 gave it a try and I succeeded!
Two Main Components
In my code, you will find two main components:
CSkinScrollBar
(derived from CScrollBar
)
CSkinScrollWnd
(derived from CWnd
)
CSkinScrollBar
offers an owner draw scrollbar. What I have done is handle mouse input and paint message simply, and I do not intend to describe it in detail. If you are interested in it, you can look into my code.
CSkinScrollWnd is the Code's Core
BOOL CSkinScrollWnd::SkinWindow(CWnd *pWnd,HBITMAP hBmpScroll)
{
ASSERT(m_hWnd==NULL);
m_hBmpScroll=hBmpScroll;
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();
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;
}
this->CreateEx(dwFrmStyleEx,AfxRegisterWndClass(NULL),
"SkinScrollBarFrame",dwFrmStyle,rcFrm,pParent,uID);
m_wndLimit.Create(NULL,"LIMIT",WS_CHILD|WS_VISIBLE,CRect(0,0,0,0),this,200);
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);
pWnd->SetParent(&m_wndLimit);
SetWindowLong(pWnd->m_hWnd,GWL_USERDATA,(LONG)this);
m_funOldProc=(WNDPROC)SetWindowLong(pWnd->m_hWnd,GWL_WNDPROC,(LONG)HookWndProc);
pWnd->MoveWindow(&rcWnd);
SetTimer(TIMER_UPDATE,500,NULL);
return TRUE;
}
static LRESULT CALLBACK
HookWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
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)
{
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)
{
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;
}
CSkinScrollWnd* SkinWndScroll(CWnd *pWnd,HBITMAP hBmpScroll);
With the help of my code, you just need to 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 gets initialized, add a line like this:
SkinWndScroll(&m_ctrlTree,hBmpScroll)
How To Test My Project?
There are 4 types of controls 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 scrollbars replace its internal scrollbar. Input text in two editboxes to see whether it works.
How To Prepare Your Scrollbar Bitmap?
Both vertical and horizontal scrollbars require 4 image segments. They are arrow-up/arrow-left, slide, thumb and arrow-down/arrow-right. Each of them includes 3 states: normal, hover, press. (It is possible to extend support for state easily. Because I'm not good at image processing, the sample bitmap came from a software's resource.) Beside those segments, the bitmap includes two angle segments located at bitmap's right.
Now I Want to Show You the Problems I Have Encountered
- When I began this code, I 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. Although my scrollbar window's z-order is higher, when mouse moves to scrollbar area, the internal scrollbar will render immediately. I have to add a new window as a frame to the target window.
- At first, I did not intend to support leftscrollbar style, and my code worked well. Finally, I decided to support it. But what makes me depressed is that it does not work any more. After spending a lot of time on debugging, I found it's wrong to move the window in the subclass callback function. So I defined a user message, and posted the message to the message queue.
How To Apply It to a ListCtrl?
Thanks to kangcorn for finding the problem.
When applying it to ListCtrl
, dragging the thumb box will have no effect. I tried my best to deal with it. Now I show you an alternate method.
I derive a new class from CListCtrl
and handle WM_VSCROLL
/WM_HSCROLL
with a sbcode
equal to SB_THUMBTRACK
.
For example:
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 suggestions will be welcome.
History
- 2007-03-07
- Fixed a bug of skinscrollwnd.cpp in which a wrong compare was done. Thanks to tHeWiZaRdOfDoS for reporting it to me.
- 2007.1.23
- Tested the project carefully and made some optimizations
- 2007.1.22
- Fixed a scrollbar's zero
div
bug, modified scrollbar's auto scroll codes
- 2006.12.22
- Modified code to apply it to combo ctrl
- 2006.7.26
- Showed a method for applying it to
ListCtrl
, etc.
- 2006.7.12
- 2006.7.9