用customdraw滚动条控件替换窗口的内部滚动条

Sample Image - skinscrollbar_demo.gif 介绍 这是我的第一篇文章。首先,我要感谢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种状态:正常状态,悬停状态,按下状态。(很容易扩展对国家的支持。因为我不擅长图像处理,所以这个位图样本来自于一个软件的资源。)除了这些片段,位图还包括位于位图右侧的两个角度片段。 Sample image 现在我想向你们展示我遇到的问题 当我开始这段代码时,我尝试使用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

原文地址:https://www.cnblogs.com/Dincat/p/13467462.html