滚动条的使用中的两个需要注意的地方
在自绘窗口界面的时候,经常需要自己控制滚动条,下面就是以添加垂直滚动条为例,怎么自己增加滚动条的代码:
1.首先,在创建窗口Create的时候,设置window Style的风格:WS_VSCROLL
2.然后,一般在OnSize中,会调整滚动条的各项参数,比如range,page,pos等等。
其中,SCROLLINFO的nMin设置为0,nMax设置为要显示的全部内容的高度,当不需 要显示滚动条的时候,设置nMax为0即可。nPage设置为显示区域的高度。
代码如下:
void CGG::OnSize(UINT nType, int cx, int cy) { CWnd::OnSize(nType, cx, cy); // TODO: 在此处添加消息处理程序代码 int nVScrollMax=0; int iTotalHeight=GetInnerHeight(); if (iTotalHeight> cy) { nVScrollMax = iTotalHeight - cy; //默认是向下扩展显示的范围,如果下面显示已经完全,那么就修改显示的起点 m_nVScrollPos if (m_nVScrollPos + cy>=iTotalHeight) { m_nVScrollPos=iTotalHeight-cy; } } else { nVScrollMax = 0; //设置起始位置为0,否则但滚动条从显示到隐藏的时候,tree不是从开始的地方开始显示的 m_nVScrollPos=0; } SCROLLINFO vsi; vsi.cbSize = sizeof(SCROLLINFO); vsi.fMask = SIF_ALL; // SIF_ALL = SIF_PAGE | SIF_RANGE | SIF_POS; vsi.nMin = 0; //修改nMax和nPage的设置方式,否则滚动条的滚动块显示不自然 if (iTotalHeight> cy) { vsi.nMax = iTotalHeight; } else { vsi.nMax =0; } vsi.nPage = cy; vsi.nPos = m_nVScrollPos; SetScrollInfo(SB_VERT, &vsi, TRUE); Invalidate(); UpdateWindow(); }
处理WM_VSCROLL 消息,OnVScroll函数,代码如下:
void CGG::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { // TODO: 在此添加消息处理程序代码和/或调用默认值 int nDelta; int iTotalHeight=GetInnerHeight(); CRect rc; GetWindowRect(&rc); int nMaxPos = (iTotalHeight - rc.Height())/SCROLL_POWER; switch (nSBCode) { case SB_LINEDOWN: if (m_nVScrollPos >= nMaxPos) return; nDelta = min(nMaxPos/100,nMaxPos-m_nVScrollPos); break; case SB_LINEUP: if (m_nVScrollPos <= 0) return; nDelta = -min(nMaxPos/100,m_nVScrollPos); break; case SB_PAGEDOWN: if (m_nVScrollPos >= nMaxPos) return; nDelta = min(nMaxPos/10,nMaxPos-m_nVScrollPos); break; case SB_THUMBPOSITION: case SB_THUMBTRACK: nDelta = (int)nPos - m_nVScrollPos; break; case SB_PAGEUP: if (m_nVScrollPos <= 0) return; nDelta = -min(nMaxPos/10,m_nVScrollPos); break; default: return; } m_nVScrollPos += nDelta; m_nVScrollPos= min(m_nVScrollPos,nMaxPos); SetScrollPos(SB_VERT,m_nVScrollPos,TRUE); Invalidate(); UpdateWindow(); CWnd::OnVScroll(nSBCode, nPos, pScrollBar); }
3。最后,在OnPaint中绘制
INT64 iTop=-m_nVScrollPos;也就是在滚动了这么多的基础上开始绘制内容。
看起来没有问题是吧。确实么有多少问题。
这是一个树的控件,在我插入少与1000条 item的时候,么有任何问题。
问题1.
但是当我插入上万条item的时候,惊奇的发现,通过拉动滑块往下拉的过程中,在下拉1000条左右,就会发现中间会闪白板,然后又重新回到开始的内容开始显示了。
问题2.
当插入上万条item的时候,我发现点击向下箭头的时候,滑块在向下走,显示的内容也是在往下走,但是看起来的视觉却也是往下滚,这种情况下应该是看起来往上滚的。同样,在item个数小于1000条的时候 ,没有这个问题。
问题1的 原因:
OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
下面是关于 SB_THUMBPOSITION和 SB_THUMBTRACK的说明:
当把鼠标的光标在滑块上按鼠标时,就会产生SB_THUMBTRACK 和 SB_THUMBPOSITION 通知码的滚动条消息。当 wParam 的低字节是 SB_THUMBTRACK时,wParam 的高字节是使用者在拖动滑块时的当前位置。当 wParam 的低字节是 SB_THUMBPOSITION 时,wParam 的高字节是使用者释放滑块的最终位置。 滚动条范围使用 32 位的值也是可以的,但是当使用了32位的时候,wParam(16位) 的高字节就不能准确获得滑块当前的位置了,这时候,需要使用 GetScrollInfo 函数来得到信息。
然后就明白了,原来这个时候的位置已经超过了16位的表示范围,自然就重新开始了。
解决方法是,在处理这两个消息的时候,采用GetScrollInfo来获取。
相关的代码:
void CGG::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { // TODO: 在此添加消息处理程序代码和/或调用默认值 int nDelta; int iTotalHeight=GetInnerHeight(); CRect rc; GetWindowRect(&rc); SCROLLINFO info; info.cbSize=sizeof(SCROLLINFO); info.fMask=SIF_TRACKPOS; switch (nSBCode) { case SB_THUMBPOSITION: case SB_THUMBTRACK: GetScrollInfo(SB_VERT,&info,SIF_TRACKPOS); nDelta = (int)info.nTrackPos - m_nVScrollPos; break; } m_nVScrollPos += nDelta; //m_nVScrollPos= min(m_nVScrollPos,nMaxPos); SetScrollPos(SB_VERT,m_nVScrollPos,TRUE); Invalidate(); UpdateWindow(); CWnd::OnVScroll(nSBCode, nPos, pScrollBar); }
问题2的原因:SB_LINEDOWN的处理,看看代码:
case SB_LINEDOWN:
if (m_nVScrollPos >= nMaxPos)
return;
nDelta = min(nMaxPos/100,nMaxPos-m_nVScrollPos);
break;
其中 int nMaxPos = (iTotalHeight - rc.Height());
这样其实每次按箭头向下滚动的值就会根据iTotalHeight也就是整个内容高度的变化而变化,这样当在恰当的时候,也就是每次刚好滚动到相对原来的位置靠下的地方时候,就会给人产生在向上滚的视觉效果。 如果每次刚好滚动cy的高度,那么视觉效果应该就是不动的了,相反,如果刚好滚动到相对原来的位置靠上的位置,那么就给人产生在向下滚动的视觉效果。 所以,很简单,如果要在按下箭头的时候,给人向上滚动的效果,就要滚动到相对靠下的位置。
比如,每次增加cy/10的值,那么肯定就是向上滚动的效果。所以把代码改动如下:
void CGG::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { // TODO: 在此添加消息处理程序代码和/或调用默认值 int nDelta; int iTotalHeight=GetInnerHeight(); CRect rc; GetWindowRect(&rc); switch (nSBCode) { case SB_LINEDOWN: if (m_nVScrollPos >= nMaxPos) return; nDelta = min(rc.Height()/10,nMaxPos-m_nVScrollPos); break; case SB_LINEUP: if (m_nVScrollPos <= 0) return; nDelta = -min(rc.Height()/10,m_nVScrollPos); break; } m_nVScrollPos += nDelta; m_nVScrollPos= min(m_nVScrollPos,nMaxPos); SetScrollPos(SB_VERT,m_nVScrollPos,TRUE); Invalidate(); UpdateWindow(); CWnd::OnVScroll(nSBCode, nPos, pScrollBar); }
这样,两个问题都解决了。以前在设置高度不是很高的滚动条的时候,根本就么有
发现这两个问题,以后要注意下。