用customdraw滚动条控件替换窗口的内部滚动条
介绍 这是我的第一篇文章。首先,我要感谢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

浙公网安备 33010602011771号