http://hi.topsage.com/thread-2316243-1-1.html
滚动条处处可见,一次性展示不完整,要用它拖动查看全部,而且下载的软件,滚动条漂亮多样,本文就是一步步和大家一起制作一个比较漂亮的滚动条。
严格来说这个滚动条,不是Scrollbar,为了DIY一个漂亮的滚动条,这里我们用CStatic控件。
要实现自定义滚动条控件,主要有三种方法。
一是利用钩子技术重新绘制滚动条,该方法实现起来比较复杂。
二是获得滚动条的显示区域,将其扣除,然后在该区域显示自定义的滚动条控件。
三是自定义一个滚动条,将其与对话框中的某个控件关联,在创建滚动条控件时,将对话框中的某个控件隐藏,并在该控件的位置显示滚动条控件。
本文采用第3种方法。
利用CStatic控件派生一个自定义滚动条CCustomScroll,在CStatic控件上利用位图绘制滚动条箭头、滚动块及滚动条的滚动区域。在绘制滚动条时,由于滚动块能够被拖动,因此需要频繁绘制滚动条。为了防止出现屏幕闪烁,可以定义一个临时的CDC对象,将所有的绘图操作都在该临时对象上进行,然后再将临时对象的内容绘制在滚动块的显示区域。
为了简化操作,本文将临时CDC对象功能封装为 CMemDC 类,在该类释放时会自动将其自身的内容绘制到某一个显示区域上:
class CMemDC : public CDC
{
private:
CBitmap* m_bmp;
CBitmap* m_oldbmp;
CDC* m_pDC;
CRect m_Rect;
public:
CMemDC(CDC* pDC, const CRect& rect) : CDC()
{
CreateCompatibleDC(pDC);
m_bmp = new CBitmap;
m_bmp->CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());
m_oldbmp = SelectObject(m_bmp);
m_pDC = pDC;
m_Rect = rect;
}
~CMemDC()
{
m_pDC->BitBlt(m_Rect.left, m_Rect.top, m_Rect.Width(), m_Rect.Height(),
this, m_Rect.left, m_Rect.top, SRCCOPY);
SelectObject(m_oldbmp);
if (m_bmp != NULL)
delete m_bmp;
}
};
1. 新建一个基于对话框的MFC应用程序,在对话框中添加按钮、编辑框、静态文本控件,向对话框类中添加成员变量:
class CCustomScroll : public CStatic
{
// Construction
public:
CCustomScroll();
UINT m_ThumbWidth; //滚动块和箭头宽度
UINT m_ThumbHeight; //滚动块和箭头高度
CWnd* m_pParent; //父窗口
CRect m_ClientRect; //窗口客户区域
CRect m_ThumbRect; //滚动块区域
BOOL m_ButtonDown; //鼠标是否单击滚动块
CPoint m_Startpt; //鼠标按下时的起点
BOOL m_IsLeft; //滚动块是否超过左箭头
BOOL m_IsLeftArrow; //是否单击左滚动条按钮
BOOL m_IsRightArrow;//是否单击右滚动条按钮
BOOL m_IsLeftRange; //是否单击了左滚动区域
BOOL m_IsRightRange;//是否单击了右滚动区域
UINT m_MinRange; //最小滚动范围
UINT m_MaxRange; //最大滚动范围
UINT m_CurPos; //当前的位置(逻辑单位)
double m_Rate; //物理像素与逻辑单位的比率
UINT m_LeftArrow; //左箭头位图ID
UINT m_RightArrow; //右箭头位图ID
UINT m_ChanelBK; //背景位图ID
UINT m_ThumbBK; //滚动块位图ID
2. 在CCustomScroll类的构造函数中初始化成员变量,代码如下:
CCustomScroll::CCustomScroll()
{
m_ButtonDown = FALSE;
m_IsLeft = FALSE;
m_MinRange = 0;
m_MaxRange = 200;
m_CurPos = 0;
m_IsLeftArrow = FALSE;
m_IsRightArrow = FALSE;
m_IsLeftRange = FALSE;
m_IsRightRange = FALSE;
}
3. 向CCustomScroll类中添加CreateStatic成员函数,用于创建滚动条控件,如是:
BOOL CCustomScroll::CreateStatic(CWnd *pParent, DWORD dwStyle, UINT nIDStatic, UINT nID)
{
m_pParent = pParent;
ASSERT(m_pParent);
//获取父窗口中的静态文本
ASSERT(::IsWindow(pParent->GetDlgItem(nIDStatic)->m_hWnd));
CRect recttemp;
pParent->GetDlgItem(nIDStatic)->GetWindowRect(recttemp);
pParent->ScreenToClient(&recttemp);
m_ClientRect = recttemp;
pParent->GetDlgItem(nIDStatic)->ShowWindow(SW_HIDE);
BOOL ret = CStatic::Create("",dwStyle,m_ClientRect,pParent,nID);
pParent->GetDlgItem(nIDStatic)->GetClientRect(m_ClientRect);
if (ret)
{
CBitmap bmp;
bmp.LoadBitmap(m_LeftArrow);
BITMAP bInfo;
bmp.GetBitmap(&bInfo);
m_ThumbHeight = bInfo.bmHeight;
m_ThumbWidth = bInfo.bmWidth;
if (bmp.GetSafeHandle())
bmp.DeleteObject();
bmp.LoadBitmap(IDB_THUMB);
bmp.GetBitmap(&bInfo);
m_ThumbRect.CopyRect(CRect(m_ThumbWidth,0,m_ThumbWidth+bInfo.bmWidth,bInfo.bmHeight));
if (bmp.GetSafeHandle())
bmp.DeleteObject();
SetScrollRange(m_MinRange,m_MaxRange);
}
ShowWindow(SW_SHOW);
return ret;
}
4. 向CCustomScroll类中添加DrawHorScroll方法,绘制滚动条:
void CCustomScroll::DrawHorScroll()
{
CClientDC dc(this);
CMemDC memdc(&dc,m_ClientRect);
CDC bmpdc;
bmpdc.CreateCompatibleDC(&dc);
CBitmap bmp;
bmp.LoadBitmap(m_LeftArrow);
CBitmap* pOldbmp = bmpdc.SelectObject(&bmp);
CRect LeftArrowRect (m_ClientRect.left,m_ClientRect.top,m_ClientRect.left+m_ThumbWidth,m_ClientRect.bottom);
memdc.StretchBlt(m_ClientRect.left,m_ClientRect.top,m_ThumbWidth,m_ThumbHeight,&bmpdc,0,0,m_ThumbWidth,m_ThumbHeight,SRCCOPY);
if (pOldbmp)
bmpdc.SelectObject(pOldbmp);
if (bmp.GetSafeHandle())
bmp.DeleteObject();
pOldbmp = NULL;
//通道的开始位置和宽度
int nChanelStart = m_ClientRect.left+m_ThumbWidth;
int nChanelWidth = m_ClientRect.Width()- 2*m_ThumbWidth;
//绘制通道
bmp.LoadBitmap(m_ChanelBK);
pOldbmp = bmpdc.SelectObject(&bmp);
memdc.StretchBlt(nChanelStart,m_ClientRect.top,nChanelWidth,m_ClientRect.Height(),&bmpdc,0,0,1,10,SRCCOPY);
if (pOldbmp)
bmpdc.SelectObject(pOldbmp);
if (bmp.GetSafeHandle())
bmp.DeleteObject();
//绘制右箭头
bmp.LoadBitmap(m_RightArrow);
pOldbmp = bmpdc.SelectObject(&bmp);
int nRArrowStart = m_ThumbWidth+nChanelWidth;
memdc.StretchBlt(nRArrowStart,m_ClientRect.top,m_ThumbWidth,m_ClientRect.Height(),&bmpdc,0,0,m_ThumbWidth,m_ThumbHeight,SRCCOPY);
//绘制滚动块
if (bmp.GetSafeHandle())
bmp.DeleteObject();
bmp.LoadBitmap(m_ThumbBK);
pOldbmp = bmpdc.SelectObject(&bmp);
memdc.StretchBlt(m_ThumbRect.left,m_ThumbRect.top,m_ThumbRect.Width()+1,m_ThumbRect.Height(),&bmpdc,0,0,m_ThumbRect.Width(),m_ThumbRect.Height(),SRCCOPY);
}
5. 处理CCustomScroll类的WM_LBUTTONDOWN消息,根据用户单击的不同区域移动滚动块,如下:
void CCustomScroll::OnLButtonDown(UINT nFlags, CPoint point)
{
m_Startpt = point;
//确定滚动区域
CRect rcScroll = m_ClientRect;
rcScroll.left += m_ThumbWidth;
rcScroll.right-= m_ThumbWidth;
DWORD wparam;
SetCapture();
if (m_ThumbRect.PtInRect(point))
{
m_ButtonDown = TRUE;
}
else if (rcScroll.PtInRect(point)) //单击滚动区域
{
CPoint centerPt = m_ThumbRect.CenterPoint();
int offset = point.x-centerPt.x;
if ((int)point.x<m_ThumbRect.left) //左滚动区域
m_IsLeftRange = TRUE;
if ((int)point.x>m_ThumbRect.right)
m_IsRightRange= TRUE;
m_ThumbRect.OffsetRect(offset,0);
int left = m_ThumbRect.left;
int right = m_ThumbRect.right;
if (left<(int)m_ThumbWidth) //判断当前滚动量是否超出了滚动范围
{
int width = m_ThumbRect.Width();
m_ThumbRect.left = m_ThumbWidth;
m_ThumbRect.right = m_ThumbRect.left+width;
m_CurPos = m_MinRange;
wparam = MAKELONG(SB_PAGELEFT,m_CurPos) ;
::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd);
DrawControl();
return;
}
else if (right>(int)(m_ClientRect.Width()-m_ThumbWidth))
{
int width = m_ThumbRect.Width();
m_ThumbRect.right = m_ClientRect.Width()-m_ThumbWidth;
m_ThumbRect.left = m_ThumbRect.right -width;
m_CurPos = m_MaxRange;
wparam = MAKELONG(SB_PAGERIGHT,m_CurPos);
::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd);
DrawControl();
return;
}
int range = m_ThumbRect.left-m_ThumbWidth;
m_CurPos = m_Rate*(range);
if (m_IsLeftRange)
{
wparam = MAKELONG(SB_PAGELEFT,m_CurPos) ;
::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd);
}
else if (m_IsRightRange)
{
wparam = MAKELONG(SB_PAGERIGHT,m_CurPos);
::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd);
}
DrawControl();
}
else //单击箭头按钮
{
if (point.x<=(int)m_ThumbWidth) //单击左箭头
{
if (m_CurPos>m_MinRange)
wparam = MAKELONG(SB_LINELEFT ,1);
else
wparam = MAKELONG(SB_LINELEFT ,0);
::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd);
if (m_CurPos>m_MinRange)
m_CurPos-=1;
m_IsLeftArrow = TRUE;
}
else //单击右箭头
{
if (m_CurPos>=m_MaxRange)
wparam = MAKELONG(SB_LINERIGHT ,0);
else
wparam = MAKELONG(SB_LINERIGHT ,1);
::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd);
if (m_CurPos<m_MaxRange)
m_CurPos+=1;
m_IsRightArrow = TRUE;
}
int factpos = m_CurPos/m_Rate;
int width = m_ThumbRect.Width();
m_ThumbRect.left = m_ThumbWidth +factpos;
m_ThumbRect.right = m_ThumbRect.left+width;
DrawControl();
SetTimer(1,100,NULL);
}
CStatic::OnLButtonDown(nFlags, point);
}
6. 处理CCustomScroll类的WM_MOUSEMOVE消息,如果用户正在拖动滚动条,将滚动块移动到适当的位置:
void CCustomScroll::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_ButtonDown)
{
int offset = point.x-m_Startpt.x;
m_Startpt = point;
DWORD wparam;
if (offset<=0) //向左拖动滚动块
{
if (m_ThumbRect.left<=(int)m_ThumbWidth)
return;
else if (abs(offset)>(int)(m_ThumbRect.left-m_ThumbWidth)) //判断当前滚动量是否超出了滚动范围
{
int width = m_ThumbRect.Width();
m_ThumbRect.left = m_ThumbWidth;
m_ThumbRect.right = m_ThumbRect.left+width;
m_CurPos = 0;
wparam = MAKELONG(SB_THUMBPOSITION,m_CurPos);
::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd);
DrawControl();
return;
}
}
else if (offset>0) //向右拖动滚动块
{
if (m_ThumbRect.right>=m_ClientRect.Width()-m_ThumbWidth) //超出右箭头
{
return;
}
else if ( offset> m_ClientRect.Width()-m_ThumbWidth-m_ThumbRect.right) //判断当前滚动量是否超出了滚动范围
{
int width = m_ThumbRect.Width();
m_ThumbRect.right = m_ClientRect.Width()-m_ThumbWidth;
m_ThumbRect.left = m_ThumbRect.right -width;
m_CurPos = m_MaxRange;
wparam = MAKELONG(SB_THUMBPOSITION,m_CurPos);
::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd);
DrawControl();
return;
}
}
m_ThumbRect.OffsetRect(offset,0);
int range = m_ThumbRect.left-m_ThumbWidth;
m_CurPos = m_Rate*(range);
wparam = MAKELONG(SB_THUMBPOSITION,m_CurPos);
::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd);
DrawHorScroll();
}
CStatic::OnMouseMove(nFlags, point);
}
7. 处理对话框的WM_TIMER消息,当用户按下滚动条两端的箭头时,连续移动滚动块,代码如下:
void CCustomScroll::OnTimer(UINT nIDEvent)
{
DWORD wparam;
if (m_IsLeftArrow)
{
if (m_CurPos>m_MinRange)
wparam = MAKELONG(SB_LINELEFT ,1);
else
wparam = MAKELONG(SB_LINELEFT ,0);
::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd);
if (m_CurPos>m_MinRange)
m_CurPos-=1;
}
else if (m_IsRightArrow)
{
if (m_CurPos< m_MaxRange)
wparam = MAKELONG(SB_LINERIGHT,1);
else
wparam = MAKELONG(SB_LINERIGHT ,0);
::SendMessage(GetParent()->m_hWnd,WM_HSCROLL,wparam,(LPARAM)m_hWnd);
if (m_CurPos<m_MaxRange)
m_CurPos+=1;
}
int factpos = m_CurPos/m_Rate;
int width = m_ThumbRect.Width();
m_ThumbRect.left = m_ThumbWidth +factpos;
m_ThumbRect.right = m_ThumbRect.left+width;
DrawControl();
CStatic::OnTimer(nIDEvent);
}