第十章 捲動軸(二)

這一章會接著解說 SYSMTR1.ASM,然後再介紹 GetScrollInfo 與 SetScrollInfo,它們是在 Win32 時代引入的 Windows API。最後再以這兩個 API 撰寫 SYSMTR2。


解說 SYSMTR1.ASM

處理 WM_SIZE 訊息

SYSMTR1 的鉛垂捲動軸是以一列文字為捲動單位,意思是使用者以滑鼠點擊向上或向下的捲動箭頭時,SYSMTR1 會使工作區的內容往上或往下捲動一列。當執行 SYSMTR1 建立視窗後,其視窗函式第二個先處理的訊息是 WM_SIZE。在處理這個訊息中,最重要的就是設定鉛垂捲動軸的資料。這些資料包含鉛垂捲動軸的捲動方塊可移動的範圍及每個頁面有多少列。當使用者改變視窗大小時,也必須做同樣的工作。

工作區內能顯示多少列,稱為一個頁面能顯示的列數,記錄在 nPage 變數裡。因為 SYSMTR1.ASM 僅有鉛垂捲動軸,所以只要考慮工作區的高度即可。而工作區的高度可由 WM_SIZE 中的 lParam 的第 16∼31 位元得到,而每一列的高度其實就是字元的高度,於處理 WM_CREATE 時就已存於 cyChar 中。因此 nPage=工作區高度÷cyChar ( 見第 79∼85 行 )。

得到一個頁面能顯示幾列後,還要設定鉛垂捲動軸的捲動方塊所能移動的範圍。可能會有人以為,GetSystemMetrics 有 95 種參數,所以此捲動方塊所能移動的範圍不就是 0∼94 嗎?應該不必調整捲動方塊所能移動的範圍。雖然這樣也是可以,但是如果將捲動方塊移到 94 的位置時,GetSystemMetrics 的最後一筆資料會顯示在工作區的最上面一列,而底下則全為空白。雖然也沒什麼問題,但卻不符合我們的習慣。

那要如何改進呢?有兩種方法:①假想每頁顯示 20 列,那麼將捲動方塊移到 94 的位置時,工作區最底下一列是第 94 個參數 ( 參數編號由 0∼94,總共有 95 列 ),而這時候顯示 20 列,因此最上面應該顯示的是第 75 個參數,所以鉛垂捲動軸的範圍是 0∼75。如果以一般式表示,那麼鉛垂捲動軸的範圍是 0∼( LINES-nPage )。②改用 Win32 才提供的 API,稍後再說明。設定好範圍之後,還要呼叫 SetScrollPos 重新繪製捲動軸。

底下是處理 WM_SIZE 訊息的程式碼:

76
77
78
79
80
81
82
83
84
85
86
87
88
  .case WM_SIZE
        ;設定捲動方塊位置的範圍為0∼(LINES-nPage)
        ;設定每頁有幾列存於nPage,nPage=工作區高度÷cyChar
                mov     rax,lParam      ;lParam的第16∼31位元是工作區高度
                xor     rdx,rdx
                cdqe                    ;使RAX的第32∼63位元變為零
                shr     rax,10h         ;RAX=工作區高度
                div     cyChar          ;EAX=每頁顯示列數=工作區高度÷字元高度
                mov     r11,LINES
                mov     nPage,eax
                sub     r11d,eax        ;R11D=LINES-nPage
                invoke  SetScrollRange,hWnd,SB_VERT,0,r11d,0
                invoke  SetScrollPos,hWnd,SB_VERT,iVertPos,1

要注意的是第 80 行,將 RDX 設為零的指令不能省略,否則 RDX 之值為 WM_SIZE,除以 cyChar 時,CPU 其實是以 EDX:EAX 組合成 64 位元長度的數值去除以 cyChar,所得的商會太大,有可能發生溢位而使程式當掉。

處理 WM_VSCROLL 訊息

當使用者操作鉛垂捲動軸時,會產生 WM_VSCROLL 訊息,視窗函式依據 wParam 參數中 0∼15 位元的通知碼改變捲動方塊的位置。捲動方塊的位置,是記錄在 iVertPos 變數中,其初始值是零。可以看到底下第 93∼105 行的 .if/.elseif/.endif 就是在處理使用者操作鉛垂捲動軸時,iVertPos 相對應的變化。例如使用者按下鉛垂捲動軸上方的捲動箭頭,那麼 wParam 的 0∼15 位元是 SB_LINEUP,必須使 iVertPos 減一 ( 見第 93∼94 行 )。

90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
  .case WM_VSCROLL
                mov     rdx,wParam
                mov     eax,nPage
        .if dx==SB_LINEUP
                dec     iVertPos
        .elseif dx==SB_LINEDOWN
                inc     iVertPos
        .elseif dx==SB_PAGEUP
                sub     iVertPos,eax
        .elseif dx==SB_PAGEDOWN
                add     iVertPos,eax
        .elseif dx==SB_THUMBPOSITION
                mov     r9,wParam
                shr     r9,10h
                mov     iVertPos,r9d
        .endif
        ;調整iVertPos,使其在0到(LINES-nPage)之間,iVertPos可以是0或(LINES-nPage)
                mov     r11,LINES
                sub     r11d,eax
                cmp     iVertPos,r11d
                jl      ok
                mov     iVertPos,r11d
                jmp     short finish
ok:             cmp     iVertPos,0
                jg      finish
                mov     iVertPos,0
finish:         invoke  GetScrollPos,hWnd,SB_VERT
        .if eax{}iVertPos
                invoke  SetScrollPos,hWnd,SB_VERT,iVertPos,1
                invoke  InvalidateRect,hWnd,0,1
        .endif

第 106∼115 行的程式碼,是用來調整 iVertPos 的範圍在 0∼( LINES-nPage ) 之間。例如捲動方塊位置已經是在最上面了,也就是 0 的位置,如果使用者再以滑鼠點擊上端的捲動箭頭,豈不成負值,因此需要調整。

方法也不難,首先先計算出捲動方塊位置的最大值是 ( LINES-nPage ),記錄在 R11D 暫存器中,再比較 iVertPos 與 R11D,如果 iVertPos 較大,那就把 iVertPos 設為 R11D,調整範圍完成;如果 iVertPos 較小,再繼續比較 iVertPos 與零,如果 iVertPos 為負,就把 iVertPos 設為零,調整範圍完成;如果 iVertPos 大於零,調整範圍完成。

第 116∼120 行是用來檢查捲動方塊的新位置 ( iVertPos ) 與舊位置 ( 呼叫 GetScrollPos 的回傳值,EAX ) 是否一樣,只有兩者不同時,才需要設定新的捲動方塊位置,並將工作區設為無效區域並重新繪製。

有幾種情形雖然使用者有操作捲動軸,但捲動方塊新位置與舊位置一樣。例如當捲動方塊已經在最上面位置,而使用者此刻又把滑鼠游標移至上方的捲動箭頭並點擊它,那麼 iVertPos 還是 0,也就沒有必要重新繪製。再例如,使用者拖曳捲動方塊的過程中,雖然螢幕上捲動方塊位置改變了,但程式並沒有處理 SB_THUMBTRACK,所以 iVertPos 其實也沒有變,所以不會重繪工作區。假如想要在拖曳過程中,看工作區的即時變化,可以把第 99 行的 SB_THUMBPOSITION 改成 SB_THUMBTRACK 就可以了。

處理 WM_PAINT 訊息

在處理 WM_PAINT 訊息時,要繪製一整個工作區的內容,整個工作區有幾列文字,在處理 WM_SIZE 訊息中已經計算出來,並存於 nPage 變數裡。所以在 WM_PAINT 訊息中繪製整個工作區時,可以設置一個迴圈,每執行一次迴圈就繪製一列資料,同時使計數器裡面的數值減一。因為每次重新繪製時,都會用到 nPage,而使用者有可能僅移動捲動方塊而沒改變視窗大小,所以不能更改 nPage 內的數值,必須另設一個變數作為計數器,此計數器是 nLines。

在進入迴圈之前,也就是下面程式第 32∼34 行,先將 nLines 設為 nPage,然後於第 38 行進入迴圈,執行到第 72∼73 行,已執行完一次迴圈,使 nLines 減一,如果 nLines 減一後不是零,就回到迴圈一開始的第 38 行處,執行下一次迴圈。如果 nLines 減一後為零,就跳出迴圈,繪製整個工作區就完成了。

在 SYSMTR1 一開始將捲動方塊位置設為最上面,也就是零,此刻會將第一筆資料,也就是 SYSMTR.INC 內的 n00 那筆資料印在工作區最上面一列。但是經過使用者操作鉛垂捲動軸之後,最上面那一筆資料不再是 n00 了,但應該就是捲動方塊的位置,iVertPos,或許可以想成是 nXX,XX=iVertPos。例如,假設 SYSMTR1 執行後,使用者以滑鼠點擊上端捲動箭頭五次,那麼工作區最上面一列就應該顯示 SYSMTR.INC 內的 n05 那筆資料,iVertPos 也就等於 5。

在工作區印出多列的資料時,也需要有一個變數記錄著由 iVertPos 到工作區最後一筆資料 ( iVertPos+nPage ),這個變數是 i。換句話說,在迴圈中 i 會由 iVertPos 逐漸增加,每執行一次迴圈便增加一,至 iVertPos+nPage 為止。這個 i 也代表著在 SYSMTR.INC 中的第幾筆資料,亦即 nXX 中的第 XX 筆資料。

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
  .case WM_PAINT
                invoke  BeginPaint,hWnd,ADDR ps
                mov     r10d,nPage
                mov     r11d,iVertPos
                mov     nLines,r10d
                mov     i,r11d          ;i由iVertPos開始至iVertPos+nPage
                mov     y,0
        ;計算某一筆資料的起始位址之位址,R9=aryPt之位址+8×i
paint_a_line:   xor     r10,r10         ;使R10變為0
                lea     r9,aryPt        ;使R9變為aryPt的起始位址
                mov     r10d,i
                shl     r10,3           ;R10=8×i
                add     r9,r10          ;R9=aryPt陣列中,第i的元素的位址
                mov     r11,[r9]        ;R11=某一筆資料的起始位址
                movzx   r10,WORD PTR [r11]
                mov     nIndex,r10d
                add     r11,2
                mov     pt1,r11         ;pt1=nIndex名稱位址
                invoke  lstrlen,r11
                mov     len1,rax        ;len1=nIndex名稱長度
                inc     rax
                add     rax,pt1
                mov     pt2,rax         ;pt2=nIndex的說明字串
        ;印出第一欄,也就是GetSystemMetrics的參數,nIndex的名稱
                invoke  SetTextAlign,ps.hdc,TA_LEFT or TA_TOP
                invoke  TextOut,ps.hdc,0,y,pt1,len1
        ;印出第二欄,GetSystemMetrics的參數值
                invoke  SetTextAlign,ps.hdc,TA_RIGHT or TA_TOP
                invoke  wsprintf,ADDR szValue,ADDR szDecFmt,nIndex
                invoke  TextOut,ps.hdc,x1,y,ADDR szValue,rax
        ;印出第三欄,GetSystemMetrics的參數說明
                invoke  SetTextAlign,ps.hdc,TA_LEFT or TA_TOP
                invoke  lstrlen,pt2
                invoke  TextOut,ps.hdc,x2,y,pt2,rax
        ;印出第四欄,GetSystemMetrics的回傳值
                invoke  SetTextAlign,ps.hdc,TA_RIGHT or TA_TOP
                invoke  GetSystemMetrics,nIndex
                invoke  wsprintf,ADDR szValue,ADDR szDecFmt,rax
                invoke  TextOut,ps.hdc,x3,y,ADDR szValue,rax
                mov     r11d,cyChar
                inc     i
                add     y,r11d
                dec     nLines
                jnz     paint_a_line
                invoke  EndPaint,hWnd,ADDR ps

還有一些 WM_PAINT 內的程式碼,還有如何取得 nXX 的資料,以及呼叫 SetTextAlign、TextOut 將文字繪製於工作區內,在之前的內容中已做過說明,請參考前一章。


用 Win32 新增的 API 管理捲動軸

眼尖的讀者可能已經發現,SYSMTR1.EXE 執行時,捲動軸上的捲動方塊大小是固定的,不會隨視窗高度改變,這種情形與現在視窗的捲動方塊不同。這是因為 SYSMTR1.ASM 呼叫 GetScrollRange、SetScrollRange、GetScrollPos、SetScrollPos 來管理捲動軸,這些 Windows API 是在 Win16 時代的方式。到了 Win32 時代,微軟建議程式設計師改用新的 Windows API 來處理捲動軸,它們是 GetScrollInfo 與 SetScrollInfo。

GetScrollInfo 與 SetScrollInfo API

GetScrollInfo 可以用來獲得捲動軸中捲動方塊的範圍、位置,一頁的大小以及使用者拖曳捲動方塊時的即時位置等性質;而 SetScrollInfo 則是用來設定捲動軸中捲動方塊的範圍、位置,以及一個頁面大小等性質,GetScrollInfo 的語法是:

invoke  GetScrollInfo,\
        hwnd,\          ; handle of window with scroll bar
        fnBar,\         ; scroll bar flag
        lpsi            ; pointer to structure for scroll parameters

SetScrollInfo 的語法是:

invoke  SetScrollInfo,\
        hwnd,\          ; handle of window with scroll bar
        fnBar,\         ; scroll bar flag
        lpsi,\          ; pointer to structure with scroll parameters
        fRedraw         ; redraw flag

這兩個 API 的前三個參數的意義均相同,只不過 GetScrollInfo 是獲得捲動軸的資料,而 SetScrollInfo 則是設定捲動軸的資料。說明如下:

  1. hwnd:捲動軸所在視窗的視窗代碼。
  2. fnBar:捲動軸的類型,可以是下面其中一種:
  3. lpsi:SCROLLINFO 結構體的位址。
  4. fRedraw:當捲動軸設定新的資料時,是否要重新繪製。如果 fRedraw 為一時,重新繪製;為零時,不重新繪製。

取得或設定捲動軸的資料,都放在 SCROLLINFO 結構體內,它的所有欄位如下:

SCROLLINFO      STRUC
  cbSize        DWORD   ?   ;SCROLLINFO 結構體大小,單位是位元組
  fMask         DWORD   ?   ;旗標,表示要設定或存取哪一個捲動軸的性質
  nMin          DWORD   ?   ;捲動方塊位置之最小值
  nMax          DWORD   ?   ;捲動方塊位置之最大值
  nPage         DWORD   ?   ;頁面大小
  nPos          DWORD   ?   ;捲動方塊位置
  nTrackPos     DWORD   ?   ;使用者正在拖曳捲動方塊時,捲動方塊的即時位置
SCROLLINFO      ENDS

底下是這些欄位的說明:

SetScrollInfo 會對 SCROLLINFO 中的 nPage 和 nPos 進行檢查。nPage 必須介於 0 到 nMax-nMin+1 之間。nPos 必須在 nMin 與 nMax-max ( nPage-1,0) 之間。「max ( nPage-1,0)」是 C 語言的表示法,亦即在 nPage-1 與 0 選其較大者,所以「nMax-max ( nPage-1,0)」的意思就是 nMax 減去 0 與 ( nPage-1 ) 較大者。如果任一個超出範圍,SetScrollInfo 會將它設定為前述的範圍內。

如果工作區的高度夠大,而不需要鉛垂捲動軸也能顯示所有內容,那麼 Windows 作業系統會自動隱藏鉛垂捲動軸;水平捲動軸也有同樣的效果。但如果把 fMask 設為 SIF_DISABLENOSCROLL,再呼叫 SetScrollInfo,那麼 Windows 僅讓捲動軸失效變成灰色,而不會消失不見。如下圖的水平捲動軸:

SetScrollInfo 與 GetScrollInfo 新增了三個特點:

  1. 可以由視覺上大約估算出,顯示在視窗內的內容與全部內容的比率:
  2. 可以提供捲動方塊更大的範圍,這是因為 nMin 與 nMax 均為雙字組,所以捲動方塊的位置可以超過十六位元。即使如此,但還是受限於螢幕大小。
  3. SetScrollInfo 會自動檢查 nPage 與 nPos,所以處理最後一頁時,不會讓最後一頁只顯示最上面一行,而底下全為空白。

用 SetScrollInfo 與 GetScrollInfo 撰寫 SYSMTR2.ASM

既已介紹完了新的 Windows API 來管理捲動軸:SetScrollInfo 與 GetScrollInfo,當然就沒有理由不用這兩個 API 來撰寫程式。這裡,小木偶要把前面的 SYSMTR1.ASM 改用 SetScrollInfo 與 GetScrollInfo 來管理捲動軸。

新的程式稱為 SYSMTR2.ASM,它仍然列出 GetSystemMetrics 的 95 種引數名稱、引數值、引數說明及回傳值。同樣的,SYSMTR2.ASM 檔案太大,所以不完整列出來,讀者可自行到 MEGA 下載。下圖是 SYSMTR2 執行的樣子。SYSMTR2.ASM 與 SYSMTR1.ASM 很相像,所以底下只說明較特別的地方。

處理 WM_CREATE 訊息

當 SYSMTR2 執行建立視窗時,其視窗函式最先處理的訊息是 WM_CREATE,在處理此訊息的程式碼,與 SYSMTR1 完全相同,就是設定①一列的高度,存於 cyChar 變數中。②平均字元的寬度,存於 cxChar 變數中。③大寫字母的平均寬度,存於 cxCaps 變數中。④計算第二欄右緣的 X 座標,存於 x1 內。⑤計算第三欄左緣的 X 座標,存於 x2 內。⑥計算第四欄右緣的 X 座標,存於 x3 內。

處理 WM_SIZE 訊息

執行 SYSMTR2 建立視窗後,其視窗函式第二個先處理的訊息是 WM_SIZE。在處理這個訊息中,最重要的就是設定鉛垂捲動軸和水平捲動軸的資料。這些資料包含這兩個捲動軸的捲動方塊可移動的範圍及每個頁面有多少內容。

先來看鉛垂捲動軸。設定鉛垂捲動軸的資料,大致與 SYSMTR1 類似,差別在捲動方塊可移動範圍是在 0∼(LINES-1) 之間,故 stsi.nMin 設為零,stsi.nMax 設為 (LINES-1)。stsi 是 SCROLLINFO 結構體。這是因為將來 SetScrollInfo 會自動調整捲動軸方塊位置與頁面的大小,不必應用程式費心,應用程式只要按實際情形設定即可,方便許多。頁面的大小,跟 SYSMTR1 一樣,不多做敘述。

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
  .case WM_SIZE
        ;設定鉛垂捲動軸每頁有幾列,以及其捲動方塊位置的範圍
        ;nPage=工作區高度÷cyChar=每頁有幾列,nMax=LINES-1,nMin=0
                mov     rax,lParam      ;lParam的第16∼31位元是工作區高度
                xor     rdx,rdx
                cdqe                    ;使RAX的第32∼63位元變為零
                shr     rax,10h         ;RAX=工作區高度
                div     cyChar
                mov     stsi.nPage,eax  ;每頁顯示列數=工作區高度÷字元高度
                mov     stsi.nMax,LINES-1
                mov     stsi.fMask,SIF_RANGE or SIF_PAGE or SIF_DISABLENOSCROLL
                invoke  SetScrollInfo,hWnd,SB_VERT,ADDR stsi,1
        ;設定水平捲動軸每頁有幾行,以及其捲動方塊位置的範圍
        ;nPage=工作區寬度÷cxChar=每頁有幾行,nMax=總寬度÷cxChar=總共有幾行,nMin=0
                mov     rax,lParam      ;lParam的第0∼15位元是工作區寬度
                xor     rdx,rdx
                and     rax,0ffffh      ;RAX=工作區寬度
                div     cxChar
                mov     stsi.nPage,eax
                mov     eax,x3
                xor     rdx,rdx
                div     cxChar
                mov     stsi.nMax,eax
                invoke  SetScrollInfo,hWnd,SB_HORZ,ADDR stsi,1

再來說說設定水平捲動軸。水平捲動軸是以字元的平均寬度作為捲動單位,也就是說,當使用者點擊左端或右端的捲動箭頭一次,SYSMTR2 會使工作區內的畫面,向左或向右平移一個字元的平均寬度。字元的平均寬度已在 WM_CREATE 中計算得到,並存於 cxChar 變數內。一個頁面水平方向有多少字元,就是工作區的寬度除以字元的平均寬度,然後儲存於 stsi.nPage 中,見上面程式第 105∼109 行。

除了設定一頁面有多少字元外,還要設定捲動方塊可移動的範圍。其最小值,就設為零。而最大值應該就是四個欄位的總寬度除以字元的平均寬度,四個欄位的總寬度已經在處理 WM_CREATE 訊息時計算出來了,其實就是 x3,字元的平均寬度是 cxChar。所以捲動方塊可移動的範圍,可以寫成上面程式第 110∼113 行。

處理 WM_HSCROLL 訊息

在解說 SYSMTR1.ASM 時,已經解釋過 WM_VSCROLL 訊息處理的過程,所以現在換一種口味,來解說處理 WM_HSCROLL 訊息。底下是處理 WM_HSCROLL 訊息的程式碼:

137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
  .case WM_HSCROLL
                mov     stsi.fMask,SIF_ALL
                invoke  GetScrollInfo,hWnd,SB_HORZ,ADDR stsi
                mov     rdx,wParam
                mov     eax,stsi.nPage
        .if dx==SB_LINELEFT
                dec     stsi.nPos
        .elseif dx==SB_LINERIGHT
                inc     stsi.nPos
        .elseif dx==SB_PAGELEFT
                sub     stsi.nPos,eax
        .elseif dx==SB_PAGERIGHT
                add     stsi.nPos,eax
        .elseif dx==SB_THUMBTRACK
                mov     r11d,stsi.nTrackPos
                mov     stsi.nPos,r11d
        .endif
                mov     stsi.fMask,SIF_POS
                invoke  SetScrollInfo,hWnd,SB_HORZ,ADDR stsi,1
                invoke  InvalidateRect,hWnd,0,1

處理 WM_HSCROLL 訊息的過程是先呼叫 GetScrollInfo,取得原來捲動方塊位置與一個頁面的大小(見第 138∼139 行),然後再根據使用者操作水平捲動軸時,可能是以滑鼠點擊右或左端的捲動箭頭、或點擊右或左端的捲動軌道、或是直接以滑鼠拖曳捲動方塊,來變更水平捲動軸捲動方塊的位置(見第 140∼153 行)。最後再呼叫 SetScrollInfo 設定好變更後的位置(見第 154∼155 行)。

舉例來說,假使執行 SYSMTR2 之後,原來水平捲動軸捲動方塊的位置為 0,而使用者立即以滑鼠點擊水平捲動軸右端的捲動箭頭,那麼 Windows 作業系統會發出通知碼為 SB_LINERIGHT 的 WS_HSCROLL 訊息給視窗函式。經 .if/.elseif 判斷,於是會執行第 145 行,使捲動方塊的位置增加一。最後執行第 154∼155 行,呼叫 SetScrollInfo,設置捲動方塊的位置為增加一後的數值。

第 150∼152 行,SYSMTR2 處理 SB_THUMBTRACK 通知碼,故能及時改變捲動方塊位置,所以 SYSMTR2 能隨著使用者拖曳捲動方塊而更新工作區的內容。

SetScrollInfo 會依據 SCROLLINFO 內的 nMin 與 nMax 兩個欄位,自動調整 nPos 與 nPage 欄位,所以不必像 SYSMTR1 裡面由應用程式自己調整。

處理 WM_PAINT 訊息

SYSMTR2 處理 WM_PAINT 訊息大部分的程式碼都與 SYSMTR1 相同,只有在要把一列一列的資料繪製於工作區時,其每一欄左緣或右緣的 X 座標並非固定 0、x1、x2、x3,會隨著水平捲動軸捲動方塊位置而變。

但這也並不難思考。假想執行 SYSMTR2 之後,如右邊上圖。然後,使用者以滑鼠點擊水平捲動軸的向右捲動箭頭,那麼工作區的內容就往左移動字元的平均寬度,如右邊下圖。以致於讓原本在工作區第一行的字元移出到工作區外(由上而下稱為「行」,第一行字元均為「S」,移出工作區後以粉紅色標示)。可以想成,原來這些第一行的字元,其 X 座標均為零,現在變成負值(應該是-cxChar)。注意!這時候水平捲動軸捲動方塊的位置變為一。

如果使用者點擊水平捲動軸的向右捲動箭頭 N 次,那麼也可以想像成,原本在工作區第一行的字元移出到工作區外,其 X 座標變為 -N×cxChar。而此時,水平捲動軸捲動方塊的位置變為 N。同理第二欄、第三欄、第四欄的 X 座標,不論是左緣還是右緣,都往左移,可以想成是原來的 X 座標加上 -N×cxChar。

下面的程式第 42∼45 行,就是計算 -N×cxChar,然後存入區域變數 x 中。原來的座標並非每一欄都是文字左緣,像第二欄及第四欄是數字,為了對齊右邊,所以是紀錄文字右緣的 X 座標。這些原來的 X 座標,其實就是 x1、x2、x3。呼叫 TextOut 時,再將這些 X 座標與區域變數 x 相加即可,見第 68∼70 行、第 74∼76 行、第 81∼83 行。

呼叫 TextOut 時,假如座標是負值,或是超出工作區的範圍,那麼超出來的部分,並不會顯示在工作區內。這性質,很符合 SYSMTR2 的需求。

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
  .case WM_PAINT
                invoke  BeginPaint,hWnd,ADDR ps
                mov     stsi.fMask,SIF_POS or SIF_PAGE
                invoke  GetScrollInfo,hWnd,SB_VERT,ADDR stsi
                mov     r10d,stsi.nPage
                mov     r11d,stsi.nPos
                mov     nLines,r10d
                mov     iVert,r11d      ;iVert=鉛垂捲動方塊位置
                invoke  GetScrollInfo,hWnd,SB_HORZ,ADDR stsi
                mov     r11d,stsi.nPos
                mov     iHorz,r11d      ;iHorz=水平捲動方塊位置
                mov     y,0
        ;計算某一筆資料左上角在工作區的座標
paint_a_line:   xor     rax,rax
                sub     eax,iHorz
                mul     cxChar
                mov     x,eax           ;x=cxChar×(0-iHorz)
        ;計算某一筆資料的起始位址之位址,R9=aryPt之位址+8×i
                xor     r10,r10
                lea     r9,aryPt        ;使R9變為aryPt的起始位址
                mov     r10d,iVert
                shl     r10,3           ;R10=8×iVert
                add     r9,r10          ;R9=aryPt陣列中,第i的元素的位址
                mov     r11,[r9]        ;R11=某一筆資料的起始位址
                movzx   r10,WORD PTR [r11]
                mov     nIndex,r10d
                add     r11,2
                mov     pt1,r11         ;pt1=nIndex名稱位址
                invoke  lstrlen,r11
                mov     len1,rax        ;len1=nIndex名稱長度
                inc     rax
                add     rax,pt1
                mov     pt2,rax         ;pt2=nIndex的說明字串
        ;印出第一欄,也就是GetSystemMetrics的參數,nIndex的名稱
                invoke  SetTextAlign,ps.hdc,TA_LEFT or TA_TOP
                invoke  TextOut,ps.hdc,x,y,pt1,len1
        ;印出第二欄,GetSystemMetrics的參數值
                invoke  SetTextAlign,ps.hdc,TA_RIGHT or TA_TOP
                invoke  wsprintf,ADDR szValue,ADDR szDecFmt,nIndex
                mov     edx,x1
                add     edx,x
                invoke  TextOut,ps.hdc,edx,y,ADDR szValue,rax
        ;印出第三欄,GetSystemMetrics的參數說明
                invoke  SetTextAlign,ps.hdc,TA_LEFT or TA_TOP
                invoke  lstrlen,pt2
                mov     edx,x2
                add     edx,x
                invoke  TextOut,ps.hdc,edx,y,pt2,rax
        ;印出第四欄,GetSystemMetrics的回傳值
                invoke  SetTextAlign,ps.hdc,TA_RIGHT or TA_TOP
                invoke  GetSystemMetrics,nIndex
                invoke  wsprintf,ADDR szValue,ADDR szDecFmt,rax
                mov     edx,x3
                add     edx,x
                invoke  TextOut,ps.hdc,edx,y,ADDR szValue,rax
                mov     r11d,cyChar
                inc     iVert
                add     y,r11d
                dec     nLines
                jnz     paint_a_line
                invoke  EndPaint,hWnd,ADDR ps

捲動軸就介紹到此。