由于屏幕不够显示,滚动条成为必备品。我们也习以为常了,如果没有滚动条,我们的电脑生活就没那么轻松了^_^。要添加滚动条,我们必须知道客户区的信息,客户区是不断变化的,但是变化是就就有WM_SIZE消息,此时的lparam的高字节保存高度,低字节保存宽度。获取办法如下

caseWM_SIZE:       

   cxClient = LOWORD (lParam) ; 

   cyClient = HIWORD (lParam) ; 

   return 0 ; 

行数ClientLineNumbers=cyClient/cyChar       列数是ClientColNumbers=cxClient/cxChar

滚动条的使用大家都会,可以点箭头,拖拉滑动块,或者点击反色条定位。

滚动范围的设置

SetScrollRange (hwnd, iBar, iMin, iMax, bRedraw) ; 

默认上为最小0,下为最大100,也可以自定义。Ibar有两个值SB_VERT 和SB_HORIZ,表示垂直或水平滚动条。bRedraw设为TRUE表示每次滚动要重画滑动块。滚动块的停留位置是一些离散的值,当设置的位置非常多时可以看做是连续的。

可以强制设定滚动条的位置

SetScrollPos (hwnd, iBar, iPos, bRedraw) ; 

也可以使用GetScrollRange和GetScrollPos来获取当前滚动块的位置和范围。

关于滚动条的信息保存在wparam中,低字节是通知码。wParam的低字组是SB_THUMBPOSITION时,高字节保存拖动滑动块时的位置;低字节是SB_THUMBPOSITION时,高字节保存最终位置。以SB为前缀^_^(scroll bar)。包含LEFT和RIGHT的标识符用于水平滚动条,包含UP、DOWN、TOP和BOTTOM的标识符用于垂直滚动条。每个常量都以按下和弹起作为一组。如图所示:

 

其中最值得注意的是SB_THUMBPOSITION和SB_THUMBTRACK。消息处理函数只处理两者中的一个,处理前者,窗口内容在鼠标停止拖动时显示。后者则不断更新内容。

关键代码如下

LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)

{

 static int cxChar,cyChar,cxCaps,cyClient,iVscrollPos;

 int i,y;

 HDC hdc;

 PAINTSTRUCT ps;

 RECT rect;

 TCHAR szBuffer[10];

 TEXTMETRIC tm;

 switch(message)

 {

 case WM_CREATE:

  hdc=GetDC(hWnd);

  GetTextMetrics(hdc,&tm);

        cxChar=tm.tmAveCharWidth;

  cyChar=tm.tmHeight+tm.tmExternalLeading;

  cxCaps=(tm.tmPitchAndFamily&1?3:2)*cxChar/2;

  ReleaseDC(hWnd,hdc);

  SetScrollRange(hWnd,SB_VERT,0,LINENUMBERS-1,FALSE);

  SetScrollPos(hWnd,SB_VERT,iVscrollPos,TRUE);

  return 0;

 case WM_SIZE:

  cyClient=HIWORD(lParam);

  return 0;

 case WM_VSCROLL:

  switch(LOWORD(wParam))

  {

  case SB_LINEUP:

   iVscrollPos-=1;

   break;

  case SB_LINEDOWN:

   iVscrollPos+=1;

      break;

  case SB_PAGEUP:

   iVscrollPos-=cyClient/cyChar;

   break;

  case SB_PAGEDOWN:

   iVscrollPos+=cyClient/cyChar;

   break;

  case SB_THUMBPOSITION:

   iVscrollPos=HIWORD(wParam);

  default:

   break;

  }

  iVscrollPos=max(0,min(iVscrollPos,LINENUMBERS-1));

  if(iVscrollPos!=GetScrollPos(hWnd,SB_VERT))

  {

   SetScrollPos(hWnd,SB_VERT,iVscrollPos,TRUE);

   InvalidateRect(hWnd,NULL,TRUE);

  }

  return 0;

 case WM_PAINT:

  hdc=BeginPaint(hWnd,&ps);

  for(i=0;i<LINENUMBERS;i++)

  {

   y=cyChar*(i-iVscrollPos);

   TextOut(hdc,0,y,sysmetrics[i].szLable,lstrlen(sysmetrics[i].szLable));

   TextOut(hdc,20*cxCaps,y,sysmetrics[i].szDesc,lstrlen(sysmetrics[i].szDesc));

   SetTextAlign(hdc,TA_RIGHT|TA_TOP);

   TextOut (hdc,22*cxCaps+40*cxChar,y,szBuffer,wsprintf(szBuffer,TEXT("%5d"),GetSystemMetrics(sysmetrics[i].index)));

   SetTextAlign(hdc,TA_LEFT|TA_TOP);

  }

  EndPaint(hWnd,&ps);

  return 0;

 case WM_DESTROY:

  PostQuitMessage(0);

  return 0;

 }

return DefWindowProc(hWnd,message,wParam,lParam);

}

iVscrollPos=max(0,min(iVscrollPos,LINENUMBERS-1))用来保证值在(0,LINENUMBERS-1)之间。下面是判断位置的变化,然后更具位置更新。

更好的滚动条

前面介绍的四个函数都是过时的,不过依然可以使用。现在只需两个函数实现滚动条的功能。

为了更加人性化,滚动条的大小应该随着内容的多少而发生变化,这也是我们习以为常的。

(滚动块大小/滚动长度)=(页面大小/范围)=(显示文件数量/文件的总大小)

这两个函数是

SetScrollInfo (hwnd, iBar, &si, bRedraw) ;

GetScrollInfo (hwnd, iBar, &si) ; 

其中一个参数是我们没有见过的,他是一种新的数据结构变量

typedef struct tagSCROLLINFO {  

    UINT cbSize;  

    UINT fMask;  

    int  nMin;  

    int  nMax;  

    UINT nPage;  

    int  nPos;  

    int  nTrackPos;  

}   SCROLLINFO, *LPSCROLLINFO;  

typedef SCROLLINFO CONST *LPCSCROLLINFO;

在调用这两个函数前cbSize=sizeof(SCROLLINFO),这个属性指名结构的大小,许多windows数据结构都有这个特点。fMask是一个旗标,通俗点就是一个功能的标志,当取不同的值(SIF为前缀)时,两个函数的功能是不一样的,填充的属性也就不同。更好的滚动条最大的不同点不仅是函数,还在于显示范围上。书上用文字描述,我觉得用图示更好说明

有图很容易知道范围是0到25.

关键代码

 case WM_SIZE:

  cxClient=LOWORD(lParam);

  cyClient=HIWORD(lParam);

  si.cbSize=sizeof(SCROLLINFO);

  si.fMask=SIF_RANGE|SIF_PAGE;

  si.nMin=0;

  si.nMax=2+iMaxWidth/cxChar;

  si.nPage=cyClient/cyChar;

  SetScrollInfo(hWnd,SB_VERT,&si,TRUE);

  return 0;

 case WM_VSCROLL:

  si.cbSize=sizeof(SCROLLINFO);

  si.fMask=SIF_ALL;

  GetScrollInfo(hWnd,SB_VERT,&si);

  iVertPos=si.nPos;

  switch(LOWORD(wParam))

  {

  case SB_TOP:

   si.nPos=si.nMin;

   break;

  case SB_BOTTOM:

   si.nPos=si.nMax;

   break;

  case SB_LINEUP:

   si.nPos-=1;

   break;

  case SB_LINEDOWN:

   si.nPos+=1;

      break;

  case SB_PAGEUP:

   si.nPos-=si.nPage;

   break;

  case SB_PAGEDOWN:

   si.nPos+=si.nPage;

   break;

  case SB_THUMBTRACK:

   si.nPos=si.nTrackPos;

  default:

   break;

  }

  si.fMask=SIF_POS;

  SetScrollInfo(hWnd,SB_VERT,&si,TRUE);

  GetScrollInfo(hWnd,SB_VERT,&si);

  if(iVertPos!=si.nPos)

  {

   ScrollWindow(hWnd,0,cyChar*(iVertPos-si.nPos),NULL,NULL);

   UpdateWindow(hWnd);

  }

  return 0;

    case WM_HSCROLL:

  si.cbSize=sizeof(SCROLLINFO);

  si.fMask=SIF_ALL;

  GetScrollInfo(hWnd,SB_HORZ,&si);

  iHorzPos=si.nPos;

  switch(LOWORD(wParam))

  {

  case SB_LINELEFT:

   si.nPos-=1;

   break;

  case SB_LINERIGHT:

   si.nPos+=1;

      break;

  case SB_PAGELEFT:

   si.nPos-=si.nPage;

   break;

  case SB_PAGERIGHT:

   si.nPos+=si.nPage;

   break;

  case SB_THUMBPOSITION:

   si.nPos=si.nTrackPos;

  default:

   break;

  }

  si.fMask=SIF_POS;

  SetScrollInfo(hWnd,SB_HORZ,&si,TRUE);

  GetScrollInfo(hWnd,SB_HORZ,&si);

  if(iHorzPos!=si.nPos)

  {

   ScrollWindow(hWnd,cxChar*(iHorzPos-si.nPos),0,NULL,NULL);

   UpdateWindow(hWnd);

  }

  return 0;

 case WM_PAINT:

  hdc=BeginPaint(hWnd,&ps);

  si.cbSize=sizeof(SCROLLINFO);

  si.fMask=SIF_POS;

  GetScrollInfo(hWnd,SB_VERT,&si);

  iVertPos=si.nPos;

  GetScrollInfo(hWnd,SB_HORZ,&si);

  iHorzPos=si.nPos;

  iPaintBeg=max(0,iVertPos+ps.rcPaint.top/cyChar);

  iPaintEnd=min(iVertPos+ps.rcPaint.bottom/cyChar,LINENUMBERS-1);

  for(i=iPaintBeg;i<=iPaintEnd;i++)

  {

   x=cxChar*(1-iHorzPos);

   y=cyChar*(i-iVertPos);

   TextOut(hdc,x,y,sysmetrics[i].szLable,lstrlen(sysmetrics[i].szLable));

   TextOut(hdc,x+20*cxCaps,y,sysmetrics[i].szDesc,lstrlen(sysmetrics[i].szDesc));

   SetTextAlign(hdc,TA_RIGHT|TA_TOP);

   TextOut (hdc,x+22*cxCaps+40*cxChar,y,szBuffer,wsprintf(szBuffer,TEXT("%5d"),GetSystemMetrics(sysmetrics[i].index)));

   SetTextAlign(hdc,TA_LEFT|TA_TOP);

  }

  EndPaint(hWnd,&ps);

  return 0;

理解关键一

iPaintBeg=max(0,iVertPos+ps.rcPaint.top/cyChar);

iPaintEnd=min(iVertPos+ps.rcPaint.bottom/cyChar,LINENUMBERS-1);

iVertPos是当前滚动条位置,那么开始绘制的地方就应该是滚动条所在的位置,对应到要显示的整个区域的位置也是这个iVertPos,后面的top可以去掉,因为显示区top等于0。绘制结束的地方应该是iVertPos加上绘制区的高度。这些都需要在显示屏后面的整个显示范围上看,以绘制区为尺度。

理解关键二

x=cxChar*(1-iHorzPos);

y=cyChar*(i-iVertPos);

先看y,比如i=iPaintBeg事,即i=iVertPos,此时对应绘制区0的位置,随着循环递加,逐行绘制。x的值令人比较迷惑,这是由于两个原因的干扰,一个是横向拉动的话还是原来那几行文字,和纵向是不一样的,如果把他们认为是相同的就很难理解了。另一个原因是函数ScrollWindow造成的,作者没有详细介绍这个函数,他的实现肯定决定了这个值的取法。关于1,是自己定义的,保证了文字不太靠近边缘。