文章轉載鏈接:http://www.cppblog.com/szhoftuncun/archive/2008/01/22/41675.html
下面這篇文章來自博客園的一篇文章,這篇文章解釋了我在學習windows程序設計一書中,看到的關於滾動條的代碼的一點疑惑,但是解釋的並不是很清楚。
我的疑惑主要在於:
爲什麼x的取值中,爲什麼用1。
原文如下:
由於屏幕不夠顯示,滾動條成爲必備品。我們也習以爲常了,如果沒有滾動條,我們的電腦生活就沒那麼輕鬆了^_^。要添加滾動條,我們必須知道客戶區的信息,客戶區是不斷變化的,但是變化是就就有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,是自己定義的,保證了文字不太靠近邊緣。
個人見解:
其實無論怎麼拖動水平滾動條,其實它都顯示的是一整行,只是顯示在第一列的字符變了而已,所以滾動條在什麼位置,滾動條所在位置的字符顯示在第一列就行了,和第二個版本的垂直滾動條顯示的原理差不多其實。