☆ 設備上下文
在第一章裡,我們創建和註冊了一個窗口類,其中有一行定義了窗口的風格(功能),是這個樣子:
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,如果你不想用窗口類裡的默認畫刷重繪窗口,你必須自己做一些事情,包括使用我們還沒有講的圖形部分。永遠不要害怕,我們過一會兒就講。現在我們在討論消息,還有一些事情我必須解釋。
留言列表