close

遊戲編程起源(初學者)Ⅴ - GameRes.com


☆ 設備上下文
在第一章裡,我們創建和註冊了一個窗口類,其中有一行定義了窗口的風格(功能),是這個樣子:

sampleClass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW; // standard settings

其中三個屬性是很一般的,但這個——CS_OWNDC,需要解釋一下。如果你記得,我曾經告訴過你這個屬性允許窗口有自己獨特的設備上下文,但直到現在,我們還沒有具體的講,OK,時間到了,開講!
設備上下文是一個結構,是一個表現一組圖形對象和屬性的結構,還有一些輸出設備的設置和屬性。使用設備上下文允許你直接操縱圖形,不用考慮低級細節。 Windows GDI是一個圖形翻譯系統,是介於應用程序和圖形硬體之間的一層。GDI可以輸出到任意的兼容設備,不過最常使用的設備是視頻監視器、圖形硬拷貝設備(如 打印機或繪圖儀),或者是內存中的圖元文本。GDI函數能夠繪製直線、曲線、封閉的圖形和文本。所有訪問GDI的Windows函數都需要一個設備上下文 句柄作為參數。感謝上帝,這是非常容易做到的。你若想得到一個窗口的設備上下文句柄,你可以用這個函數:

HDC GetDC(
    HWND hWnd  // handle to a window
);


很簡單,是不是?所有你做的是,把要操作的窗口的句柄傳遞給它,然後返回一個設備上下文句柄。如果你傳遞的是NULL,將返回整個螢幕的設備上下文(DC,以後都用DC表示)句柄。如果函數呼叫失敗,將返回NULL。
設備上下文不僅僅處理圖形,但我們習慣於泛泛的認為它是處理圖形的。處理顯示圖形的DC類型,稱作顯示DC,處理打印的,稱作打印DC;處理位圖數據的, 稱作內存DC,還有其它一些設備DC。感覺有點複雜吧,不要緊,這是Windows,它的主要功能就是迷惑群眾。^_^一旦我們接觸一些代碼,就不會覺得 難了。
當你結束使用DC時,一定要釋放它,也就是釋放它佔用的內存空間。要把這種思想貫穿到以後的編程中去,佔用了內存,不用時要釋放,切記!釋放DC是一個很簡單的函數:

int ReleaseDC(
    HWND hWnd, // handle to window
    HDC hDC    // handle to device context
);


若成功釋放,返回值是1,否則是0。參數有註釋,我還是說一下:
HWND hWnd:你所要控制的那個窗口的句柄。如果你開始傳遞的是NULL,現在還要傳遞NULL。
HDC hDC:DC的句柄。
在用DC和GDI進行圖形顯示前,我們先看看創建窗口實例時要遇到的幾條重要的消息。我將要提到的四條消息是:WM_MOVE、WM_SIZE、WM_ACTIVATE、WM_PAINT。

☆ 追蹤窗口狀態
頭兩個是很簡單的。當窗口被用戶移動時將發送WM_MOVE消息,窗口新位置的坐標儲存在lparam中。(還記得嗎,消息在lparam和wparam 中被進一步描述,它們是消息控制函數的參數)lparam的低端字中存儲窗口客戶區左上角的坐標x,高端字中存儲坐標y。
當窗口的大小被改變時,將發送WM_SIZE消息。同WM_MOVE消息差不多,lparam的低端字中存儲客戶區的寬度,高端字存儲高度。同WM_MOVE不同的是,wparam參數也控制了一些重要的東西。它可以是下列中任意一個值:
※ SIZE_MAXHIDE:其它的窗口被最大化了。
※ SIZE_MAXIMIZED:本窗口被最大化了。
※ SIZE_MAXSHOW:其它的窗口被還原了。
※ SIZE_MINIMIZED:本窗口被最小化了。
※ SIZE_RESTORED:窗口被改變了尺寸,但既沒最大化,也沒有最小化。
當我編寫窗口實例時,我通常喜歡把窗口的當前位置和大小保留在幾個全局變量裡。假設我們命名這些全局變量為xPos,yPos,xSize和ySize,你最好這樣控制WM_SIZE和WM_MOVE這兩個消息:

if (msg == WM_SIZE){
    xSize = LOWORD(lparam);
    ySize = HIWORD(lparam);
}
if (msg == WM_MOVE){
    xPos = LOWORD(lparam);
    yPos = HIWORD(lparam);
}


現在輪到WM_ACTIVATE消息了。它告訴你一個新窗口被激活。這是很有用的,因為如果出現優先的申請,你就不可能處理程序裡的所有邏輯。有時,例如 寫一個全屏的DIRECTX程序,忽略WM_ACTIVATE消息將導致你的程序出現致命的錯誤,可能它做了一些你不希望它做的事情。在任何情況下,守候 WM_ACTIVATE消息從而採取行動,是一個好主意。
窗口被激活和被解除激活都會發出WM_ACTIVATE消息,我們可以通過檢測wparam的低端字來得知是被激活還是被取消。這將有三種可能的值:
※ WA_CLICKACTIVE:窗口被游標激活。
※ WA_ACTIVE:窗口被其它東西激活。(鍵盤、函數呼叫、等等)
※ WA_INACTIVE:窗口被解除激活。
為了處理這個消息,我保留了另一個全局變量bFocus,當接收到WM_ACTIVATE消息,它的值將改變。示例如下:

if (msg == WM_ACTIVATE){
    if (LOWORD(wparam) == WA_INACTIVE){
        focus = FALSE;
   }else{
        focus = TRUE;

   }

    // tell Windows we handled it
    return(0);
}


有兩個相關聯的消息WM_KILLFOCUS和WM_SETFOCUS,在窗口接收到輸入焦點的時候,Windows消息WM_SETFOCUS被發送給 它,在失去焦點的時候則發送WM_KILLFOCUS消息。應用程序可以截取這些消息以得知輸入焦點的任何改變情況。什麼是輸入焦點呢?存有輸入焦點的應 用程序(窗口)就是被激活的那個窗口。你就認為被激活的窗口就是輸入焦點就行了。因為可能出現沒有窗口具有輸入焦點,所以我建議用WM_ACTIVATE 消息跟蹤你的窗口狀態。(有些胡塗?不要緊,你就記住用WM_ACTIVATE就行了)往下進行。

☆ WM_PAINT 消息
WN_PAINT消息通知程序,全部或部分客戶窗口需要重新繪製。當用戶在最小化、重疊或調整客戶窗口區域的時候,就會產生這條消息。重新繪製,你需要做兩件事,首先是要用到WM_PAINT消息專用的一對函數,第一個是BeginPaint()函數,這是它的原形:

HDC BeginPaint(
    HWND hwnd,             // handle to window
    LPPAINTSTRUCT lpPaint  // pointer to structure for paint information
);


在我告訴你返回值是什麼之前,讓我們先看看參數:
HWND hwnd:需要重繪的窗口的句柄。你應該已經對於這種參數比較熟悉了。
LPPAINTSTRUCT lpPaint:這是很重要的一個。是指向PAINTSTRUCT結構的指針,該結構包含所有的要被重繪區域的信息。
繼續之前,我應該給你看看PAINTSTRUCT結構:

typedef struct tagPAINTSTRUCT { // ps
    HDC hdc;
    BOOL fErase;
    RECT rcPaint;
    BOOL fRestore;
    BOOL fIncUpdate;
    BYTE rgbReserved[32];
} PAINTSTRUCT;


結構內的成員如下:
HDC hdc:哈哈,有理由複習一下設備上下文(DC,有的書中叫「設備描述表」)了。這是要被刷新區域的句柄。
BOOL fErase:指明應用程序是否應該抹去背景。如果是FALSE,說明系統已經刪除了背景。還記得在Windows類中我們曾經用黑色畫刷定義了一個背景嗎?這就意味著系統將用這個畫刷抹去無效的區域。
RECT rcPaint:這是最重要的一個成員。它將告訴你需要被重繪的無效區域的矩形。我將稍後告訴你RECT結構。
BOOL fRestore,BOOL fIncUpdate,BYTE rgbReserved[32]:好消息,這些是保留成員,為老Windows服務的,所有你我都不必管它們。:)
現在我已經給你看了這麼多,這就是BeginPaint()函數的全部。它做了三件事兒。首先,它使窗口再次有效,直到下一次被改變,WM_PAINT消息發出前,這個窗口都是有效的。第二,如果在窗口類(Windows class)裡定義了背景畫刷,就像我們做過的那樣,就用這個畫刷重繪無效的區域。(所謂無效,就是被改變的)第三,返回了被重繪區域的DC句柄。重繪的區域,是由RECT結構定義的:

typedef struct _RECT {
    LONG left;
    LONG top;
    LONG right;
    LONG bottom;
} RECT;


我們已經指出這個結構描繪了一個矩形,但是有一件事情需要說說。RECT包含左上角,但不包含右下角。什麼意思呢?讓我們先定義一個RECT對象:

RECT myRect = {0, 0, 5, 5};

這個RECT包含像素(0,0),但是沒有達到(5,5),所以矩形的右下角實際是(4,4)。看起來沒有什麼意義,但是你得習慣它。
現在,還記得我所說的關於使用DC的事兒嗎?一旦你用完了,你就必須釋放它。OK,你記起來了,用EndPaint()函數釋放。回應WM_PAINT消息,每次呼叫完BeginPaint()函數,必須匹配一個EndPaint()函數釋放DC。這是函數的原形:

BOOL EndPaint(
    HWND hWnd,                 // handle to window
    CONST PAINTSTRUCT *lpPaint // pointer to structure for paint data
);


函數通過返回TRUE或FALSE分別表明成功還是失敗。有兩個簡單的參數:
HWND hWnd:就是窗口的句柄。
CONST PAINSTRUCT *lpPaint:指向PAINTSTRUCT類型的結構變量地址。同BeginPaint()的第二個參數是一回事。不要被CONST迷惑了,它只是保證和確認函數沒有改變結構的內容。
你還可以通過呼叫ValidateRect()函數代替BeginPaint()函數使得窗口再次有效。但你得手工操作一切。可能我們真的什麼時候就要用到它。所以給你它的原形:

BOOL ValidateRect(
    HWND hWnd,         // handle of window
    CONST RECT *lpRect // address of validation rectangle coordinates
);


通過返回TRUE或FALSE來確定函數呼叫成功還是失敗。參數很簡單:
HWND hWnd:煩不煩,我不說了:)
CONST RECT *lpRect:是指向RECT結構是否有效的指針。如果你傳遞NULL,則整個客戶區域都是有效的。
現在把以上講到的做個樣子給你看吧,假設我們已經定義了一個全局的變量hMainWindow作為我們的窗口句柄。

if (msg == WM_PAINT)
{
    PAINTSTRUCT ps; // declare a PAINTSTRUCT for use with this message
    HDC hdc; // display device context for graphics calls

    hdc = BeginPaint(hMainWindow, &ps); // validate the window

    // your painting goes here!

    EndPaint(hMainWindow, &ps); // release the DC

    // tell Windows we took care of it
    return(0);
}


這段代碼很簡單,也沒做什麼令人興奮的事兒!OK,如果你不想用窗口類裡的默認畫刷重繪窗口,你必須自己做一些事情,包括使用我們還沒有講的圖形部分。永遠不要害怕,我們過一會兒就講。現在我們在討論消息,還有一些事情我必須解釋。

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 huenlil 的頭像
    huenlil

    H's 手札

    huenlil 發表在 痞客邦 留言(0) 人氣()