13 篇文章 0 订阅
59 篇文章 0 订阅
8 篇文章 0 订阅
﻿﻿

# Replace a Window's Internal Scrollbar with a customdraw scrollbar Control

Shows how to replace a window's scrollbar with a skinable scrollbarctrl

## 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:

1. CSkinScrollBar (derived from CScrollBar)
2. 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)
{//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);
::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);
::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 the help of my code, you just need to add a line of code in your code. For example, assume you have atreectrl in a window and you want to replace it's scrollbar. At first, you give it a namem_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. Clickinglist_addstring button will fill listctrl and you will see a left scrollbar. Clickingtree_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

1. 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.
2. 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_HSCROLLwith 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);
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
• Fixed a scrollbar's bug
• 2006.7.9
• Finished a primary frame

﻿﻿
01-04
09-15
03-25 1657
11-18 4628
08-28
01-04 7070
04-06 1128

• 非常没帮助
• 没帮助
• 一般
• 有帮助
• 非常有帮助