视窗

**視窗**是在使用者介面上的一個可見的範圍。視窗一般都是長方形的。它包含著各種控制項，是用作輸入和輸出的介面。雖然視窗一般用於圖形使用者介面，但又有時用於命令列介面的。而一個使用視窗為主要使用者介面的系統則稱為視窗系統。這個想法源於全錄帕洛阿爾托研究中心。

在視窗系統上，視窗一般被畫成一些二維的物件，被安置在桌面之上。大多數的視窗都可以隨使用者的意願而更改大小、移動、隱藏、回復和關閉。當兩個視窗重疊的時候，就如日常生活中一樣，其中一個要位於另一個之上，而下方的則會有些地方被上方個視窗所遮蓋。而視窗系統中管理這個操作的部份，則叫做視窗管理員。

視窗是數種圖形使用者介面中的一種重要的 widget。當中有 VMS 的 DEC Windows、GNU/Unix-like 系統的 X 視窗系統、Microsoft Windows 和 Open Windows。

## 歷史

這個概念是由斯坦福大學研究所 (由 道格拉斯・恩格爾巴特 領導）開發實現的。 他們最早的系統支援多窗口，但沒明顯的方法顯示它們的分界線（比如窗口邊框、標題列等）。

研究在 Xerox 公司的 Palo Alto 研究中心 / PARC （由 艾倫・凱領導）中繼續。他們使用了重疊的窗口。

在 1980 年代，PARC 提出術語 "WIMP"，分別代表了窗口、圖示、選單、指標。

那時 Apple 與 PARC 進行了簡單的合作。Apple 根據 PARC 的介面開發了自己的新介面。它首次應用是在 Apple 的 Lisa 上，此後用於 Macintosh 電腦。與此同時，Microsoft 在為 "Mac" 開發辦公軟體。之後他們在 Apple 系統的基礎上開發了自己的窗口系統。

## 子類化與超類化

窗口子類化是對一個窗口實例通過 GetWindowLong 和 SetWindowLong 將窗口過程位址修改為一個新函式位址，新的窗口過程函式處理那些感興趣的訊息，將其它訊息傳遞給原窗口過程。即實例子類化。在某控制項已經建立的情況下，為了獲得窗口訊息，必須子類化它。子類化不需要建立一個完整的新窗口類，只需要攔截單個窗口。

窗口超類化是一個已存在的窗口類（WNDCLASS 或 WNDCLASSEX），改變窗口類的特徵。僅影響窗口類新建立的窗口實例，不會影響窗口過程位址修改前已經建立的窗口。超類化只能改變使用者自創的窗口的特性，不能用於系統建立的窗口 (如對話方塊上的標準按鈕）。超類話過程範例如下：

### MFC 的子類化與超類化

MFC 的 CWindowImplBaseT::SubclassWindow 用於動態地把一個窗口實例繫結到當前的 CWnd 物件，訊息迴圈首先呼叫 CWnd 物件的窗口過程，傳遞給基礎類別的訊息將被預設的窗口過程處理。CWnd::SubclassDlgItem 用於把對話方塊的一個控制項子類化繫結到一個基於 CWnd 的類物件。CWindowImplBaseT::UnsubclassWindow 恢復一個被子類化的窗口。

模板類 CWindowImplBaseT 提供一個資料成員 WNDPROC m\_pfnSuperWindowProc 並且初始化為 ::DefWindowProc. 然而在窗口超類化處理時它儲存了已註冊窗口類的窗口過程，在窗口子類化時它儲存窗口實例控制代碼原有的窗口過程。

CWnd::CreateEx 在建立窗口前呼叫 SetWindowsHookEx 函式安裝了一個勾點函式 \_AfxCbtFilterHook。窗口剛建立好，勾點函式 \_AfxCbtFilterHook 就被呼叫。\_AfxCbtFilterHook 呼叫 SetWindowLong 將窗口過程替換為 AfxWndProcBase，並將 SetWindowLong 返回的原窗口位址儲存到成員變數 oldWndProc。可見，通過 CWnd::CreateEx 建立的所有窗口都會被子類化。在 DefWindowProc 函式中，訊息會傳給子類化時儲存的原窗口位址 oldWndProc。

ATL 提供 DECLARE\_WND\_SUPERCLASS 來支援超類化。

## Windows 窗口類型

使用 Windows API 函式 CreateWindowEx，可以指定建立的窗口的類型：

* 層疊窗口 Overlapped Window：一種頂層（top-level) 窗口，作為應用程式的**主窗口**。有標題條、邊界與客戶區，可以有選單、最大最小化按鈕、捲軸、可改變表單尺寸的邊界（sizing border）。使用 CreateWindowEx 函式指定 WS\_OVERLAPPED 或 WS\_OVERLAPPEDWINDOW 風格。
* 彈出式視窗 Pop-up Window：另一種頂層窗口，用於對話方塊、MessageBox，以及其他出現在應用程式主窗口之外的臨時窗口。標題條可選，除此以外與層疊窗口相同。使用 CreateWindowEx 函式，指定 WS\_POPUP 風格。如果需要標題條，指出 WS\_CAPTION 風格。使用 WS\_POPUPWINDOW 風格有 border 與 window 選單。彈出式視窗可以擁有其他頂層窗口；頂層窗口也可以擁有彈出式視窗；前述二者可以同時成立。彈出式視窗總是有 WS\_CLIPSIBLINGS 風格。彈出式視窗的尺寸或定位參數不能使用 CW\_USEDEFAULT，否則尺寸、定位被設為 0。
* 頂層窗口 top-level window：就是「非 Child window」。其 parent 恆定是 Desktop。總是有 WS\_CLIPSIBLINGS 風格。
* 子窗口 Child Window：出現在父窗口（parent window）的客戶區中，超出的部分不可見。子窗口一定有 Parent，一定沒有 Owner。非子窗口的 Parent 一定是桌面，它們不一定有 Owner。典型用於把父窗口的客戶區分割成一些子區域。使用 CreateWindowEx 函式指定 WS\_CHILD 風格。父窗口可以是 Overlapped、Pop-up、其他子窗口。子窗口的唯一必要特性是有客戶區；不能有選單；其他特性可選。定位子窗口時總是用父窗口客戶區左上角坐標系。Destroyed、Hidden、Moved、Shown 等窗口操作與父窗口一致。父窗口如果指定了 WS\_CLIPCHILDREN 風格，則父窗口不能在子窗口上繪製；否則重繪父窗口會覆蓋子窗口的內容。兄弟子窗口（sibling window）可以在彼此重疊的客戶區上繪製，除非指定了 WS\_CLIPSIBLINGS 風格，則不論子窗口之間 z-order，與其他子窗口重疊區域不被繪製。不能使用 CW\_USEDEFAULT，否則將沒有尺寸、沒有定位位置。子窗口的輸入訊息直接發給子窗口，並不傳遞給父窗口。但子窗口被函式 EnableWindow 禁止時，系統把子窗口的輸入訊息發給父窗口。在子窗口上點擊滑鼠，子窗口收到 WM\_LMOUSEBUTTONDOWN 訊息；如果主窗口沒有鍵盤輸入焦點（focus），則主窗口獲得輸入焦點（WM\_SETFOCUS）；如果兄弟子窗口有輸入焦點，則當前子窗口獲得輸入焦點。子窗口有獨一無二整數識別碼。應用程式通過空間的整數識別碼給它發訊息。控制項給父窗口傳送 notification 訊息時，用整數識別碼來表示自身。
* Layered Window：帶有 WS\_EX\_LAYERED 風格的窗口就是分層窗口，用於實現異形窗口和窗口整體透明。異形窗口要把控制項的透明色和窗口後面的圖像進行融合，需要用 UpdateLayeredWindow 函式來繪製。窗口整體透明，需要用 SetLayeredWindowAttribute 函式設定透明度。呼叫 UpdateLayeredWindow 函式後，WM\_PAINT 訊息將失效；更新窗口需要自行呼叫 UpdateLayeredWindow 函式。子窗口無法應用 WS\_EX\_LAYERED 風格。子窗口如果需要是異形或者整體透明，只能去掉 WS\_CHILD 風格，作為 popup 窗口，然後 MOVE 到一定位置，在父窗口移動的時候跟隨移動。
* Message-Only Window，允許傳送 / 接收 Windows 訊息，不可見，沒有 z-order，不能被列舉，不接收廣播訊息。CreateWindowEx 函式的參數 hWndParent 為 HWND\_MESSAGE 常數或者一個已存在的 message-only 窗口的控制代碼。對已經存在的窗口，通過函式 SetParent 的參數 hWndNewParent 為 HWND\_MESSAGE，把該表單修改為 message-only。為搜到 message-only 窗口，呼叫函式 FindWindowEx 指定參數 hwndParent 為 HWND\_MESSAGE。此外，函式 FindWindowEx 搜尋 message-only 窗口與頂層層口，如果參數 hwndParent 與 hwndChildAfter 都為 NULL。
* Owner/Owned Window：Overlapped Window/Pop-up Window 可以被其他 Overlapped Window/Pop-up Window 擁有。子窗口一定沒有 Owner。這種關係具有如下特性：在 z-order 上，Owned 總是在 Owner 之上。當 Owner 被摧毀時，系統自動摧毀它的所有 Owned。當 Owner 極小化時，Owned 自動隱藏（沒有了 WS\_VISIBLE），不顯示在工作列上；但 Owned 窗口自身擁有的窗口不會自動隱藏，即沒有傳遞性。Owner 隱藏（SW\_HIDE），不會影響其擁有的窗口。CreateWindowEx 函式建立具有 WS\_OVERLAPPED 或 WS\_POPUP 風格的窗口時，通過參數 hwndParent 指定 Owner。對話方塊與訊息方塊預設都是 Owned。通過 GetWindow 函式指出 GW\_OWNER 標誌取得窗口的 Owner 的控制代碼。
  * Owner Window：Owner window 不能是 Child window。 Child window 一定沒有 Owner，因此 Owner window 是對 popup 和 Overlapped 而言。popup 和 Overlapped 不一定有 Owner。Owner 為 NULL 的非 Child 窗口可以在工作列上出現它們的按鈕。
  * Owned Window
* top most window：具有「WS\_EX\_TOPMOST」屬性。總是顯示在其它非 top most 窗口的上面。同時是 top most 的窗口是「公平競爭」的關係。owner 為 top most，則其全部 owned 都變成了 top most。child 不能是 top most 窗口。

GetParent 函式，對於子窗口返回其父窗口控制代碼；對於彈出式視窗，返回擁有者窗口控制代碼；對於層疊窗口，返回 0.

GetWindow 函式，指出 GW\_OWNER 參數，則可以返回彈出式視窗或層疊窗口的擁有者窗口控制代碼。

FindWindowEx 函式，用於找到指定父窗口、指定子窗口的下一個子窗口。

## Windows 窗口類與窗口實例的額外儲存空間

WNDCLASS 中 cbClsExtra 和 cbWndExtra，分別用於指定附加於窗口類與窗口實例的額外儲存空間位元組大小，不能超過 40 位元組。分別類似於 C++ 類別中的 static 變數與類似於 C++ 類別中的成員變數。

GetClassLong (hwnd,GCL\_CBCLSEXTRA) 取得的是類附加空間的大小，即 cbClsExtra 的值。GetClassLong (hwnd,GCL\_CBWNDEXTRA) 取得的是窗口附加空間的大小，即 cbWndExtra 的值。

使用方法：

* 存取類附加空間的資料 GetClassLong (hwnd,index)，index 表示存取附加空間以 0 開始的索引。
* 設定類附加空間的資料 SetClassLong (hwnd,index,value)，index 表示存取附加空間以 0 開始的索引，value 表示要設定的值。
* 存取窗口附加空間的資料 GetWindowLong (hwnd,index)，index 表示存取附加空間以 0 開始的索引。
* 設定窗口附加空間的資料 SetWindowLong (hwnd,index,value)，index 表示存取附加空間以 0 開始的索引，value 表示要設定的值。

其它相關 Windows API 有：GetWindowWord、GetWindowLongPtr、SetWindowWord、SetWindowLongPtr。

## 視窗管理員

視窗系統中管理視窗的部分被成為 "視窗管理員"

常見的窗口系統：

* X 視窗系統
* Microsoft Windows

## 參考文獻