Ch 13 顯示 BMP 檔 (1)


點陣圖簡介

在電腦上的圖片大致分成兩種,向量圖與點陣圖。向量圖是指像 AutoCAD 所存的 *.DWG 檔,或是像網際網路最流行的 Flash,它們的圖片主要是以線、圓……等基本幾何圖形構成,例如畫一張人臉,只需一個橢圓當輪廓、兩個小圓當眼睛、一個三角形當鼻子、一個橢圓當嘴,然後各塗上適當顏色,而描述圓或橢圓只需要少數的幾個點或半徑即可,所以畫出一幅向量圖必須經過一些計算,著色,當圖形複雜時就會比較慢。向量圖的優點是檔案的大小較小,因為只要存下少量的關鍵資料就可以了。

但是要存一張人物照或風景等照片,就沒有辦法用簡單的幾何圖形表示,這些就是點陣圖。一般可以把點陣圖看成是由許多點由左而右、由上而下一個點一個點堆砌而成 ( 有些點陣圖不是這樣排列,但此處小木偶只是提一提這個觀念而已 )。而每一點由不同的顏色表現出來,所以就可以顯出一幅圖片,例如副檔名為 BMP 的圖片 ( Bitmap 的縮寫,也稱為『位元圖』,檔案體積很大 )。但是因為這樣的圖片檔案太大了,所以產生各式各樣的壓縮方式,而造成多種副檔名,如 GIF、JPG、PNG 等等。

這一章的主角位元圖,也就是副檔名為 BMP 的圖檔,它是沒有經過壓縮的點陣圖,也是 Windows 直接支援的。在這一章堙A小木偶要把一張 game13.bmp 檔顯示在螢幕上,它是一張 1024*768 大小的圖案,共 2359350 位元組大小,圖片的樣子如下:

Yuna and Lulu
Yuna 是電動玩具太空戰士的女主角 ( 上圖右 ),很可愛吧?

但是因為這個檔案太大了,所以上圖是小木偶把它縮小變為原來的一半,實際上,小木偶所撰寫的程式,ViewYUNA.ASM,會把這個 game13.bmp 檔完整的顯示於視窗中。也因為 game13.bmp 長 1024,寬 768 個點,所以 ViewYUNA.EXE 產生的視窗擁有捲軸,可以移動捲軸上的操縱桿。如下圖:


顯示 BMP 位元圖的原理

要顯示 BMP 位元圖的步驟大致可分為以下幾個步驟:

  1. 在資源檔中定義位元圖。
  2. 用 LoadBitmap API 載入位元圖。
  3. 用 BeginPaint 取得設備內容,稱此設備內容為 hdc。
  4. 用 CreateCompatibleDC API 另外再建立與該設備內容一樣的設備內容,稱為 hdcMem。
  5. 用 SelectObject API 把自資源檔載入的位元圖畫在 hdcMem 上。
  6. 用 BitBlt API 把 hdcMem 的位元圖傳送到 hdc 上。
  7. 顯示圖片後,用 DeleteDC、EndPaint 分別釋放 hdc、hdcMem。

請繼續看底下進一步的說明。

BMP 圖檔也是資源的一種,如前所述,欲使 BMP 檔顯示在視窗堙A最簡單的方法是使它變成資源的一部份。在資源描述檔中,可以用下面的敘述定義 BMP 檔成為資源:

位元圖識別碼  BITMAP  [DISCARDABLE]   位元圖檔案名

就如同前面的圖示一樣,『位元圖識別碼』是一個字串,將來在程式執行時以這個識別碼字串來代表後面的『位元圖檔案名』。BITMAP 是表示這段落是描述位元圖用的,因為資源描述檔中有許多不同的段落,有定義選單的,有定義圖示的,有定義對話盒的等等。DISCARDABLE 是表示位元圖不再使用的時候可以暫時從記憶體中釋放以節省記憶體,它是一個可有可無的參數。

LoadBitmap API

把位元圖定義成資源後,當系統執行具有位元圖資源的程式時,還必須用 LoadBitmap 把資源中的位元圖載入到記憶體堙CLoadBitmap 的原型是:

HBITMAP LoadBitmap(
    HINSTANCE	hInstance,      // handle of application instance
    LPCTSTR     lpBitmapName	// address of bitmap resource name
   );

其中,hInstance 是程式的模組代碼,lpBitmapName 是一個長指標,指向位元圖識別碼字串的位址,此字串須以零為結尾,執行完 LoadBitmap API 後,EAX 會傳回位元圖代碼。接下來,就要準備在螢幕上畫出圖片了。還記得凡是在螢幕上畫出圖形都得先取得設備內容 ( device context )。在回應 WM_PAINT 時,用 BeginPaint 取得,而結束 WM_PAINT 時用 EndPaint 釋放裝置內容。

當取得裝置內容後,本來應該對螢幕畫圖,但一般的做法是先建立一個和此視窗相同的裝置內容,稱為記憶體裝置內容,作為緩衝區,再由這個緩衝區直接把位元圖傳送到要顯示的裝置內容上。這是因為點陣圖檔案通常很大,在較慢速的電腦直接對視窗的裝置內容輸出,容易造成畫面『抖動』,但如果直接做傳送的話,可以避免。

CreateCompatibleDC API

要建立相同的裝置內容,可以用 CreateCompatibleDC API,其原型為

HDC CreateCompatibleDC(
    HDC     hdc     // handle to memory device context
   );

它只有一個參數,就是和那一個設備內容相同,如果成功,返回值存於 EAX,為新建的設備內容代碼。

當建立好新的記憶體設備內容之後,就可以對這個緩衝區畫圖了,所用的 API 是 SelectObject,它可以把資源檔內定義的位元圖物件畫在裝置內容上。SelectObject 的原型、用法請參考第五章

當記憶體裝置內容的圖形已經畫好之後,就可以把緩衝區的資料傳送到原視窗的裝置內容了,可以用 BitBlt API 完成。

BitBlt API

BitBlt 是傳送一個區塊的位元資料 ( bit-block transfer ),意即使在記憶體內的位元圖,傳送到設備內容。BitBlt 原型為:

BOOL BitBlt(
  HDC   hdcDest, // handle to destination device context 
  int   nXDest,  // x-coordinate of destination rectangle's upper-left corner
  int   nYDest,  // y-coordinate of destination rectangle's upper-left corner
  int   nWidth,  // width of destination rectangle 
  int   nHeight, // height of destination rectangle 
  HDC   hdcSrc,  // handle to source device context 
  int   nXSrc,   // x-coordinate of source rectangle's upper-left corner  
  int   nYSrc,   // y-coordinate of source rectangle's upper-left corner
  DWORD dwRop    // raster operation code 
  );

hdcDest 是目的地的設備內容代碼,nXDest、nYDest 是目的地設備內容的座標,nWidth、nHeight 是傳送圖片的寬度與長度。hdcSrc 是來源設備內容,nXSrc、nYSrc 是來源的座標,dwRop 是傳送後的操作方式。當要顯示位元圖時,顯然要把視窗的工作區所代表的設備內容設為目的設備內容,假如要從左上角開始顯示點陣圖的話,nXDest、nYDest 均設為零,而所要顯示的位元圖多寬,多高則由 nWidth、nHeight 指定,而來源設備內容必須指向位元圖所在的記憶體設備內容。

最後一個參數,dwRop,是用來在傳送時可和特定顏色運算。下表顯示可能的數值:

數值說明
BLACKNESS 全部變黑色。即目的資料填入 0
DSTINVERT NOT 目的資料
MERGECOPY 來源資料 AND 畫刷顏色
MERGEPAINT ( NOT 來源資料 ) OR 畫刷顏色
NOTSCRCOPY NOT 來源資料
NOTSRCERASE NOT ( 來源資料 OR 目標資料 )
PATCOPY 畫刷顏色
PATINVERT 畫刷顏色 XOR 目標資料
PATPAINT 畫刷顏色 OR ( NOT 來源資料 ) OR 目標資料
SRCAND 來源資料 and 目標資料
SRCCOPY 來源資料
SRCERASE 來源資料 AND ( NOT 目標資料 )
SRCINVERT 來源資料 XOR 目標資料
SRCPAINT 來源資料 OR 目標資料
WHITENESS 填入白色。

經過這麼多步驟,終於大功告成了。小木偶把這些步驟濃縮成下圖:

把 BITMAP 資源顯示於視窗

在這一章堙A我們只單純顯示位元圖即可,不作運算,所以在呼叫 BitBlt 時,dwRop 參數用 SCRCOPY 即可。最後再由 DeleteDC 釋放由 CreateCompatibleDC 所建立的記憶體裝置內容。

DeleteDC API

DeleteDC 是用來釋放指定的裝置內容。原型為:

BOOL DeleteDC(
    HDC     hdc     // handle to device context 
   );

hdc 是要被釋放的裝置內容。若成功,則 EAX 傳回非零數,若失敗,則 EAX 為零。


為視窗加上捲軸

要在視窗上加上捲軸,必須在呼叫 CreateWindow 建立視窗時,在 dwStyle 參數中指定 WS_HSCROLL 或 WS_VSCROLL 這兩個風格,前者表示水平捲軸,後者表示垂直捲軸。注意,此處所加入的捲軸不是控制元件,而是視窗捲軸,兩者雖然相像,但意義上不同 ( 捲軸控制元件見第十章 )。如果只指定了這兩個風格,而不做其它事,那麼只是顯示出捲軸而已,但是無法正常作用。為了讓捲軸能夠發揮作用,還須做兩件事:第一,要先用 SetScrollRange 設好捲軸範圍;第二,處理 WM_HSCROLL、WM_VSCROLL 訊息。

SetScrollRange API

設定捲軸範圍一般是用 SetScrollRange API,其原型是:

BOOL SetScrollRange(
    HWND	hWnd,		// handle of window with scroll bar
    int		nBar,		// scroll bar flag
    int		nMinPos,	// minimum scrolling position
    int		nMaxPos,	// maximum scrolling position
    BOOL	bRedraw		// redraw flag
   );

hWnd 是視窗代碼,此視窗代碼是指擁有捲軸的視窗。nBar 是只要設定範圍的捲軸,可以是下面三種的其中一種:SB_CTL、SB_HORZ、SB_VERT,一個視窗最多只有一個水平捲軸和一個垂直捲軸,要設定水平捲軸時,用 SB_HORZ;要設定垂直捲軸時,用 SB_VERT;如果要設定對話盒的捲軸範圍,用 SB_CTL。nMinPos、nMaxPos 分別是操縱桿的最小和最大範圍。bRedraw 是是否重新繪製,TRUE 表重繪;FALSE 表示不重繪。

一般捲軸外觀如下:

捲軸上有一個操縱桿 ( thrmb ),可以在滑動桿 ( shaft ) 上移動,捲軸兩端各有一個捲軸箭頭 ( scroll arrow )。我想使用者用滑鼠去操作捲軸的方法,大家應該很清楚,不須特別說明,下面對捲軸產生的訊息做一個介紹。

WM_HSCROLL、WM_VSCROLL 訊息

操作捲軸這種看似簡單,但對 Win32 系統而言,卻產生了複雜的訊息。滑鼠對水平捲軸操作時,例如滑鼠在左箭頭或右箭頭按一下,或以滑鼠拖著操縱桿移動,或在左滑動桿或在右滑動桿上按一下等等的動作,都會產生 WM_HSCROLL 訊息,這個 WM_HSCROLL 訊息將放到程式的訊息佇列中等待處理。同樣的,以滑鼠對垂直捲軸操作時,會產生 WM_VSCROLL 訊息。

WM_HSCROLL 是使用者對水平捲軸操作時,對所擁有的視窗發出的訊息。WM_HSCROLL 的 wParam 較低的十六位元是使用者對捲軸做了什麼動作,此動作稱為通知碼,下表說明其意義:

通 知 碼
wParam 較低的十六位元
數值 說   明
SB_LINELEFT 0 當使用者按一下捲軸的左箭頭時,操縱桿被向左移動一格
SB_LINERIGHT 1 當使用者按一下捲軸的右箭頭時,操縱桿被向右移動一格
SB_LEFT 6 當使用者把操縱桿拖移動到滑動桿的最左邊,也就是 SetScrollRange 的最小值參數處
SB_RIGHT 7 當使用者把操縱桿拖移動到滑動桿的最右邊,也就是操縱桿在 SetScrollRange 的最大值參數處
SB_PAGELEFT 2 當使用者在操縱桿左邊的滑動桿上按一下時,視窗內的資料將顯示向左邊的一個螢幕畫面
SB_PAGERIGHT 3 當使用者在操縱桿右邊的滑動桿上按一下時,視窗內的資料將顯示向右邊的一個螢幕畫面
SB_THUMBPOSITION 4 當使用者拖操縱桿移動而後停止時,發出具有 SB_THUMBPOSITION 的 WM_HSCROLL 訊息
SB_THUMBTRACK 5 當使用者拖操縱桿移動時,發出具有 SB_THUMBTRACK 的 WM_HSCROLL 訊息

小木偶稍稍說明上表。當使用者以 SetScrollRange 設定捲軸範圍時,用參數 nMinPos 和 nMaxPos 設定最大值和最小值,例如

        invoke  SetScrollRange,hWnd,SB_HORZ,0,3,TRUE

那麼捲軸的操縱桿由左而右共有 0、1、2、3 四個位置,假如現在操縱桿在 0 的位置,當使用者按一下向右箭頭,則捲軸發出具有 SB_LINERIGHT 的 WM_HSCROLL 訊息,且操縱桿位置移到 1 的位置。滑動桿被操縱桿分成兩部分,當使用者按一下操縱桿右邊的滑動桿,則捲軸發出具有 SB_PAGERIGHT 的 WM_HSCROLL 訊息;若按一下左邊的滑動桿,則產生具有 SB_PAGELEFT 的 WM_HSCROLL,這些動作系統會自動更新捲軸上操縱桿的位置,但是視窗中工作區的內容必須程式自行更新。


原始檔

底下是原始程式,ViewYUNA.ASM,的內容:

        .386
        .model  flat,stdcall
        option  casemap:none

IDM_EXIT        equ     4000

include         windows.inc
include         user32.inc
include         gdi32.inc
include         kernel32.inc
includelib      user32.lib
includelib      gdi32.lib
includelib      kernel32.lib

WndProc         proto   :HWND,:UINT,:WPARAM,:LPARAM

;***********************************************************
        .DATA
hMenu           HMENU       ?
hInstance       HINSTANCE   ?
hwnd            HWND        ?
hBitmap         dd          ?   ;022 位元圖物件代碼
nxClient        dd          ?   ;023 工作區寬度
nyClient        dd          ?   ;024 工作區高度
ncx             dd          ?   ;025 BMP 圖檔的寬度
ncy             dd          ?   ;026 BMP 圖檔的高度
iVPos           dd          ?   ;027 垂直捲軸操縱桿位置
iHPos           dd          ?   ;028 水平捲軸操縱桿位置
iVMax           dd          ?   ;029 垂直捲軸最大範圍
iHMax           dd          ?   ;030 水平捲軸最大範圍
ClassName       db          'ViewBMPWinClass',0
AppName         db          '觀看位元圖 game13.bmp',0
MenuName        db          'VBMPMenu',0
IconName        db          'BMPIcon',0
BMPName         db          'VBMP',0
wc              WNDCLASSEX  <?>
msg             MSG         <?>
;***********************************************************
        .CODE
start:  invoke  GetModuleHandle,NULL
        mov     hInstance,eax
        mov     wc.cbSize,sizeof WNDCLASSEX
        mov     wc.style,CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc,offset WndProc
        mov     eax,hInstance
        mov     wc.hInstance,eax
        invoke  LoadIcon,hInstance,offset IconName
        mov     wc.hIcon,eax
        mov     wc.hIconSm,eax
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor,eax
        mov     wc.hbrBackground,COLOR_WINDOW+1
        invoke  LoadMenu,hInstance,offset MenuName
        mov     hMenu,eax
        mov     wc.lpszClassName,offset ClassName
        invoke  RegisterClassEx,offset wc
        invoke  CreateWindowEx,NULL,offset ClassName,offset \ 
                AppName,WS_VSCROLL or WS_HSCROLL or \           ;058 風格
                WS_OVERLAPPEDWINDOW,0,0,400,400,0,hMenu,hInstance,NULL
        mov     hwnd,eax
        invoke  ShowWindow,hwnd,SW_MAXIMIZE                     ;061 最大化
        invoke  UpdateWindow,hwnd

.while  TRUE
        invoke  GetMessage,offset msg,NULL,0,0
.break  .if     !eax
        invoke  DispatchMessage,offset msg
.endw        
        mov     eax,msg.wParam
        invoke  ExitProcess,eax
;-----------------------------------------------------------
WndProc proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
        local   bitmap:BITMAP           ;073 存放位元圖屬性
        local   ps:PAINTSTRUCT
        local   hdc,hdcMem:HDC

.if uMsg==WM_CREATE
        invoke  LoadBitmap,hInstance,offset BMPName             ;078 載入位元圖
        mov     hBitmap,eax
        invoke  GetObject,hBitmap,sizeof BITMAP,addr bitmap     ;080 位元圖屬性
        mov     ecx,bitmap.bmWidth      ;081 位元圖寬度存於 ncx
        mov     ncx,ecx
        mov     ecx,bitmap.bmHeight     ;083 位元圖高度存於 ncy
        mov     ncy,ecx

.elseif uMsg==WM_SIZE
        mov     eax,lParam              ;087 取得工作區大小,ECX=高度,EAX=寬度
        mov     ecx,eax
        and     eax,0ffffh
        shr     ecx,16
        mov     nxClient,eax
        mov     nyClient,ecx
        mov     ecx,ncx                 ;093 計算水平捲軸最大範圍
        sub     ecx,eax
        shr     ecx,3
        mov     iHMax,ecx
        invoke  SetScrollRange,hWnd,SB_HORZ,0,ecx,TRUE
        mov     eax,ncy                 ;098 計算垂直捲軸最大範圍
        sub     eax,nyClient
        shr     eax,3
        mov     iVMax,eax
        invoke  SetScrollRange,hWnd,SB_VERT,0,eax,TRUE
        sub     edx,edx
        mov     iVPos,edx
        mov     iHPos,edx

.elseif uMsg==WM_PAINT
        invoke  BeginPaint,hWnd,addr ps ;108 取得視窗的設備內容
        mov     hdc,eax
        invoke  CreateCompatibleDC,eax  ;110 建立相同的設備內容作為來源
        mov     hdcMem,eax
        invoke  SelectObject,hdcMem,hBitmap     ;112 選定來源設備內容的位元圖
        mov     eax,iVPos                       ;113 此四行計算顯示點陣圖的起點
        shl     eax,3
        mov     ecx,iHPos
        shl     ecx,3
        invoke  BitBlt,hdc,0,0,nxClient,nyClient,hdcMem,\
                ecx,eax,SRCCOPY         ;118 傳送位元圖到視窗的設備內容
        invoke  DeleteDC,hdcMem         ;119 釋放來源設備內容
        invoke  EndPaint,hWnd,addr ps   ;120 釋放視窗設備內容

.elseif uMsg==WM_HSCROLL
        mov     eax,wParam
        and     eax,0ffffh
 .if ax==SB_LEFT
go_lft: mov     iHPos,0
 .elseif ax==SB_RIGHT
go_rgt: mov     eax,iHMax
        mov     iHPos,eax
 .elseif (ax==SB_LINERIGHT)||(ax==SB_PAGERIGHT)
        inc     iHPos                   ;130 處理 SB_LINERIGHT、SB_PAGERIGHT
        mov     eax,iHPos
        cmp     eax,iHMax
        jg      go_rgt
 .elseif (ax==SB_LINELEFT)||(ax==SB_PAGELEFT)
        dec     iHPos
        jl      go_lft
 .elseif ax==SB_THUMBTRACK
        mov     eax,wParam
        shr     eax,16
        mov     iHPos,eax
 .endif
        invoke  SetScrollPos,hWnd,SB_HORZ,iHPos,TRUE
        invoke  InvalidateRect,hWnd,NULL,FALSE
        
.elseif uMsg==WM_VSCROLL                ;145 處理 WM_VSCROLL
        mov     eax,wParam
        and     eax,0ffffh
 .if ax==SB_TOP
go_top: mov     iVPos,0
 .elseif ax==SB_BOTTOM
go_btm: mov     eax,iVMax
        mov     iVPos,eax
 .elseif (ax==SB_LINEDOWN)||(ax==SB_PAGEDOWN)
        inc     iVPos
        mov     eax,iVPos
        cmp     eax,iVMax
        jg      go_btm
 .elseif (ax==SB_LINEUP)||(ax==SB_PAGEUP)
        dec     iVPos
        jl      go_top
 .elseif ax==SB_THUMBTRACK
        mov     eax,wParam
        shr     eax,16
        mov     iVPos,eax
 .endif
        invoke  SetScrollPos,hWnd,SB_VERT,iVPos,TRUE
        invoke  InvalidateRect,hWnd,NULL,FALSE

.elseif uMsg==WM_COMMAND
        mov eax,wParam
;以下是使用者點選選單
 .if lParam==0
   .if ax==IDM_EXIT
        jmp     exit
   .endif
 .endif

.elseif uMsg==WM_CLOSE
exit:   invoke  DestroyWindow,hWnd

.elseif uMsg==WM_DESTROY
        invoke  PostQuitMessage,NULL

.else
def:    invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
.endif
        xor     eax,eax
        ret
WndProc endp
;-----------------------------------------------------------
;***********************************************************
end     start

底下是資源描述檔,ViewYUNA.RC,檔的內容:

#include "c:\masm32\include\resource.h"

#define IDM_EXIT        4000

VBMPMenu        MENU
BEGIN
        MENUITEM      "離開(&X)",IDM_EXIT
END

BMPIcon ICON    EYE.ICO

VBMP    BITMAP  game13.bmp

底下是 makefile 檔,ViewYUNA.MAK,的內容:

ALL:ViewYUNA.EXE

ViewYUNA.EXE : ViewYUNA.ASM ViewYUNA.RES
        ML /coff ViewYUNA.ASM /link ViewYUNA.RES

ViewYUNA.RES : ViewYUNA.RC EYE.ICO game13.bmp
        RC ViewYUNA.RC

程式解說

要顯示位元圖,當然必須先知道這張圖有多大,所以小木偶在處理 WM_CREATE 訊息時,就取得位元圖大小。Win32 API,GetObject 可以做這件事。

GetObject API

GetObject 可以取得圖形物件的屬性資料,這些圖形物件包括位元圖 ( Bitmap )、筆刷 ( Brush )、字型 ( Font )、畫筆 ( Pen ) 等等。首先來看看 GetObject 的原型:

int GetObject(
    HGDIOBJ hgdiobj,    // handle to graphics object of interest
    int     cbBuffer,   // size of buffer for object information 
    LPVOID  lpvObject   // pointer to buffer for object information  
   );

hgdiobj 是要取得屬性資料的圖形物件代碼。cbBuffer 是這些屬性資料的大小,它會依圖形物件、版本不同而改變。lpvObject 參數指向一個結構體位址,該結構體便是存放所獲得的屬性資料。同樣的,依圖形物件不同,lpvObject 所指向的結構體名稱也會不同,請參考下表:

hgdiobj 類型 lpvObject
HBITMAP BITMAP
HPEN LOGPEN
HBRUSH LOGBRUSH
HFONT LOGFONT

此處,小木偶只介紹 BITMAP 結構體。其欄位是:

BITMAP          STRUCT
bmType          DWORD   ?
bmWidth         DWORD   ?
bmHeight        DWORD   ?
bmWidthBytes    DWORD   ? 
bmPlanes        DWORD   ?
bmBitsPixel     DWORD   ?
bmBits          DWORD   ?
BITMAP          ENDS

bmType 是位元圖類型,應該為 0。bmWidth、bmHeight 分別是位元圖寬度、長度,以點為單位。bmWidthBytes 是每一列含多少位元組。bmPlanes 是顏色平面 ( color planes )。bmBitsPixel是每一點以多少位元表示。bmBits 是一個指標。因此在程式第 80 行,也就是處理 WM_CREATE 訊息時,呼叫 GetObject,並在第三個參數堳定結構體變數 bitmap 位址。這樣一來,呼叫完成後,在這個結構體奡N會有位元圖的寬度、高度了。分別存在變數 ncx、ncy 堙C

處理 WM_SIZE 訊息

除了取得位元圖大小外,還得知道視窗工作區 ( client area ) 大小,才能決定要顯示那一部份的位元圖。當系統新建立視窗時,或使用者以滑鼠拖住視窗邊框移動,或者按標題欄右邊的最大化按鈕或最小化按鈕時,都會產生 WM_SIZE,表是視窗大小改變了,此時 wParam 內是視窗因何改變大小,可以是

wParam說  明
SIZE_MAXIMIZED 使用者按下最大化按鈕
SIZE_MINIMIZED 使用者按下最小化按鈕
SIZE_RESTORED 使用者按下恢復原視窗大小的按鈕

lParam 中較低的十六位元是工作區的寬度,較高的十六位元是工作區的高度。程式第 91、92 行,分別把工作區的寬度、高度存入 nxClient、nyClient 變數堙C

程式第 93 行開始計算水平捲軸有幾個位置。小木偶是以每按一次捲軸的滑動桿或箭頭,便移動 8 個點,而 game13.bmp 位元圖比工作區大 ( 小木偶的電腦螢幕設成 1024*768,扣去標題欄、邊框等等,圖片比工作區大。所以 如果你螢幕比 1024*768 大的話,可能會出錯,這點小木偶寫這個程式時,倒沒有考慮到。 ),因此位元圖扣去工作區的大小,再分成每等分 8 點,所得到的商就是捲軸操縱桿可移動的位置。

舉例來說,game13.bmp 高 768 點,而工作區高 680 點,所以視窗上只能顯示 680 點高的位元圖,在工作區外的 88 點就無法顯示了。請參考下圖,白色矩形所圍起來的區域假設是工作區的範圍,圖片高度扣去工作區高度再除以 8,就可得到捲軸應分成幾等分。

小木偶以每次向上捲或向下捲動 8 點,因此操縱桿有 11 個位置,再加上一開始最上方的位置,共有 12 個位置,分別以 0、1、2、3、4……11 表示這 12 個位置。這是程式第 98 到 101 行所作的事情。

小木偶把高度的最大位置存在 iVMax 變數中,而把操縱桿現在位置存在變數 iVPos 變數堙A當使用者每向下移動捲軸操縱桿一次時,iVPos 便增加一,然後再檢查 iVPos 是否大於 iVMax,若大於則使 iVPos 等於 iVMax。同樣地,若使用者每向上移動捲軸操縱桿一次時,iVPos 便減一,然後再檢查 iVPos 是否小於 0,若小於則使 iVPos 恢復為 0。這樣就能使操縱桿在 0∼11 的位置上了。你可以參考程式第 145 行,處理整個 WM_HSCROLL 訊息的方式。

而處理 WM_VSCROLL 也是類似,就不多說了。下次將介紹直接由 BMP 檔案顯示圖片,不再把 BMP 製作成資源,你應該會發現這樣做成的檔案很大,不符合組合語言的優點。


到第十二章回到首頁到第十四章