Windows 程式設計/視窗建立
在 Windows 作業系統中,大多數使用者可互動的介面物件被稱為“視窗”。每個視窗都與一個特定的類相關聯,一旦該類向系統註冊,就可以建立該類的視窗。
要註冊一個視窗類,你需要填寫 WNDCLASS 結構中的資料欄位,並需要將該結構傳遞給系統。但是,首先,你需要為你的類提供一個名稱,以便 Windows(系統)可以識別它。習慣上將視窗類名定義為全域性變數
LPTSTR szClassName = TEXT("My Class");
你可以隨意命名它,這只是一個例子。
獲得類名後,就可以開始填充 WNDCLASS 結構。WNDCLASS 被定義如下
typedef struct {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS, *PWNDCLASS;
有關此結構的更多資訊,請參見此 Microsoft 開發人員網路文章。
請注意,最後一個數據欄位是指向名為“lpszClassName”的字串的指標?這裡是指向你剛剛定義的類名。名為“hInstance”的欄位是你在其中提供程式的例項控制代碼的地方。我們將把其餘欄位分成幾個不同的類別。
WNDCLASS 結構中有許多不同的資料型別以字母“h”開頭。正如我們從匈牙利表示法討論中記住的那樣,如果一個變數以“h”開頭,則該變數本身儲存一個 HANDLE 物件。
- HICON hIcon
- 這是指向程式將使用的圖示的控制代碼,該圖示位於左上角和工作列中。我們將在稍後討論圖示。但是,在我們下面的示例中,我們將為此項使用預設值。
- HCURSOR hCursor
- 這是指向視窗將使用的標準滑鼠指標的控制代碼。在我們的示例中,我們也將為此使用預設值。
- HBRUSH hbrBackground
- 這是指向視窗背景的畫刷(畫刷本質上是一種顏色)的控制代碼。以下是 Windows 提供的預設顏色列表(這些顏色會根據計算機上啟用的“主題”而改變)
COLOR_ACTIVEBORDER COLOR_ACTIVECAPTION COLOR_APPWORKSPACE COLOR_BACKGROUND COLOR_BTNFACE COLOR_BTNSHADOW COLOR_BTNTEXT COLOR_CAPTIONTEXT COLOR_GRAYTEXT COLOR_HIGHLIGHT COLOR_HIGHLIGHTTEXT COLOR_INACTIVEBORDER COLOR_INACTIVECAPTION COLOR_MENU COLOR_MENUTEXT COLOR_SCROLLBAR COLOR_WINDOW COLOR_WINDOWFRAME COLOR_WINDOWTEXT
由於軟體問題,必須將任何這些值的 1 新增到任何這些值中才能使其成為有效的畫刷。
另一個值得一提的值是“lpszMenuName”變數。lpszMenuName 指向一個字串,該字串包含程式選單欄的名稱。如果你的程式沒有選單,你可以將其設定為 NULL。
WNDCLASS 結構中還有 2 個“額外”資料成員,允許程式設計師指定要為類分配的額外空間量(以位元組為單位)(cbClsExtra)和要為每個特定視窗例項分配的額外空間量(cbWndExtra)。如果你想知道,字首“cb”代表“位元組數”。
int cbClsExtra;
int cbWndExtra;
如果你不知道如何使用這些成員,或者不想使用它們,你可以將它們都保留為 0。我們將在稍後更詳細地討論這些成員。
WNDCLASS 中有 2 個欄位專門用於處理視窗的執行方式。第一個是“style”欄位,它本質上是一組位標誌,將確定系統可以對類採取的一些操作。這些標誌可以使用|運算子進行按位或運算(使用|運算子)以將多個標誌組合到“style”欄位中。MSDN WNDCLASS 文件包含更多資訊。
下一個(也是最重要的)WNDCLASS 成員是 lpfnWndProc 成員。該成員指向一個 WNDPROC 函式,該函式將控制視窗,並將處理所有視窗的訊息。
在初始化 WNDCLASS 結構的欄位後,你需要將你的類註冊到系統。這可以透過將指向 WNDCLASS 結構的指標傳遞給 RegisterClass 函式來完成。如果 RegisterClass 函式返回零值,則註冊失敗,並且系統無法註冊新的視窗類。
視窗通常使用“CreateWindow”函式建立,儘管還有一些其他函式也很有用。一旦 WNDCLASS 註冊成功,你就可以透過將類名(還記得我們定義的那個全域性字串嗎?)傳遞給 CreateWindow 函式來告訴系統從該類建立一個視窗。
HWND CreateWindow(
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);
(有關更多資訊,請參見此 MSDN 文章。)
第一個引數“lpClassName”與我們的視窗類關聯的字串。引數“lpWindowName”是將在視窗標題欄中顯示的標題(如果視窗有標題欄)。
“dwStyle”是一個包含多個按位或運算標誌的欄位,這些標誌將控制視窗建立。
引數“x”和“y”指定視窗左上角在螢幕上的座標。如果“x”和“y”都為零,則視窗將出現在螢幕的左上角。“nWidth”和“nHeight”分別指定視窗的寬度和高度(以畫素為單位)。
有 3 個 HANDLE 值需要傳遞給 CreateWindow:hWndParent、hMenu 和 hInstance。hwndParent 是指向父視窗的控制代碼。如果你的視窗沒有父視窗,或者你不想讓你的視窗相互關聯,可以將其設定為 NULL。hMenu 是指向選單的控制代碼,hInstance 是指向程式例項值的控制代碼。
要將值傳遞給新視窗,可以在 CreateWindow 的 lpParam 值中傳遞一個通用 LPVOID 指標(一個 32 位值)。通常,透過這種方法傳遞引數比將所有變數都設定為全域性變數更好。如果要將多個引數傳遞給新視窗,則應將所有值放入結構體中,並將指向該結構體的指標傳遞給視窗。我們將在稍後更詳細地討論這一點。
最後,我們將展示此過程的一個簡單示例。該程式將在螢幕上顯示一個簡單的視窗,但視窗不會執行任何操作。該程式是一個精簡的程式,它包含了使任何 Windows 程式執行任何操作所需的大部分框架。除此之外,可以輕鬆地向程式新增更多功能。
#include <windows.h>
LPSTR szClassName = "MyClass";
HINSTANCE hInstance;
LRESULT CALLBACK MyWndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow)
{
WNDCLASS wnd;
MSG msg;
HWND hwnd;
hInstance = hInst;
wnd.style = CS_HREDRAW | CS_VREDRAW; //we will explain this later
wnd.lpfnWndProc = MyWndProc;
wnd.cbClsExtra = 0;
wnd.cbWndExtra = 0;
wnd.hInstance = hInstance;
wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION); //default icon
wnd.hCursor = LoadCursor(NULL, IDC_ARROW); //default arrow mouse cursor
wnd.hbrBackground = (HBRUSH)(COLOR_BACKGROUND+1);
wnd.lpszMenuName = NULL; //no menu
wnd.lpszClassName = szClassName;
if(!RegisterClass(&wnd)) //register the WNDCLASS
{
MessageBox(NULL, "This Program Requires Windows NT",
"Error", MB_OK);
return 0;
}
hwnd = CreateWindow(szClassName,
"Window Title",
WS_OVERLAPPEDWINDOW, //basic window style
CW_USEDEFAULT,
CW_USEDEFAULT, //set starting point to default value
CW_USEDEFAULT,
CW_USEDEFAULT, //set all the dimensions to default value
NULL, //no parent window
NULL, //no menu
hInstance,
NULL); //no parameters to pass
ShowWindow(hwnd, iCmdShow); //display the window on the screen
UpdateWindow(hwnd); //make sure the window is updated correctly
while(GetMessage(&msg, NULL, 0, 0)) //message loop
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK MyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
隨著每一代的更新,Win32 API 的功能越來越強大,儘管微軟一直致力於保持 API 與舊版本 Windows 的向後相容性。為了新增更多功能,微軟需要新增新的函式和結構,以利用新特性。WNDCLASS 結構的擴充套件版本稱為“WNDCLASSEX”結構,它具有更多欄位,並允許更多選項。要註冊 WNDCLASSEX 結構,必須使用 RegisterClassEx 函式。
此外,還有一個具有擴充套件功能的 CreateWindow 函式版本:CreateWindowEx。要了解有關這些擴充套件的更多資訊,可以在 MSDN 上搜索。
對話方塊是特殊型別的視窗,其建立和管理方式與其他視窗不同。要建立對話方塊,我們將使用 CreateDialog、DialogBox 或 DialogBoxParam 函式。我們將在後面討論這些函式。可以透過定義 WNDCLASS 並呼叫 CreateWindow 來建立對話方塊,但 Windows 已經將所有定義儲存在內部,並提供了一些易於使用的工具。有關完整討論,請參閱:對話方塊。
Windows 系統中已經定義並存儲了許多視窗類。這些類包括按鈕和編輯框等元素,手動定義這些元素需要花費太多工作。以下是部分預製視窗型別的列表:
- 按鈕
- 按鈕視窗可以包含從按鈕到複選框和單選按鈕的一切。按鈕視窗的“標題”是按鈕上顯示的文字。
- 捲軸
- 捲軸視窗是滑塊控制元件,通常用於較大的視窗邊緣,用於控制滾動。捲軸型別也可以用作滑塊控制元件。
- MDICLIENT
- 這種客戶端型別支援 多文件介面 (MDI) 應用程式。我們將在後面的章節中討論 MDI 應用程式。
- 靜態
- 靜態視窗是簡單的文字顯示視窗。靜態視窗很少接受使用者輸入。但是,如果需要,可以修改靜態視窗使其看起來像超連結。
- 列表框、組合框
- 列表框視窗是下拉列表框,其中可以填充使用者可以選擇的不同選項。組合框視窗類似於列表框,但它可以包含複雜專案。
- 編輯、富文字編輯
- 編輯視窗允許使用游標輸入文字。基本的編輯視窗還允許進行復制貼上操作,儘管需要自己提供處理這些選項的程式碼。富文字編輯控制元件允許文字編輯和格式化。可以將編輯控制元件視為 Notepad.exe,將富文字編輯控制元件視為 WordPad.exe。
視窗或對話方塊中可以包含多種不同的選單。最常見(也是最重要的)選單之一是顯示在視窗或對話方塊頂部的下拉選單欄。此外,許多程式在滑鼠右鍵單擊視窗時會提供選單。視窗頂部的欄稱為“選單欄”,我們將在下面首先討論它。有關在資源指令碼中建立選單的資訊,請參閱本書附錄中的 資源指令碼參考頁面。
在資源指令碼中建立選單是最簡單直觀的方法。假設我們要建立一個包含一些常見標題的選單:“檔案”、“編輯”、“檢視”和“幫助”。這些是大多數程式都有的常見選單項,也是大多數使用者熟悉的選單項。
建立選單時,建議使用常見的名稱和公認的順序建立選單,以便計算機使用者知道在哪裡找到這些選單項。 |
我們在資源指令碼中建立一個專案來定義這些選單項。我們將透過數字識別符號“IDM_MY_MENU”來表示我們的資源。
IDM_MY_MENU MENU DISCARDABLE BEGIN POPUP "File" POPUP "Edit" POPUP "View" POPUP "Help" END
關鍵字 POPUP 表示一個選單,當您單擊它時會開啟。但是,假設我們不希望“幫助”選單項彈出,而是希望單擊“幫助”一詞,然後立即開啟幫助視窗。我們可以這樣更改它:
IDM_MY_MENU MENU DISCARDABLE BEGIN POPUP "File" POPUP "Edit" POPUP "View" MENUITEM "Help" END
MENUITEM 指示符表明,當我們單擊“幫助”時,不會開啟另一個選單,而會向程式傳送一個命令。
現在,我們不希望選單為空,因此我們將在“檔案”和“編輯”選單中填充一些常見命令,使用與上面相同的 MENUITEM 關鍵字:
IDM_MY_MENU MENU DISCARDABLE
BEGIN
POPUP "File"
BEGIN
MENUITEM "Open"
MENUITEM "Save"
MENUITEM "Close"
END
POPUP "Edit"
BEGIN
MENUITEM "Cut"
MENUITEM "Copy"
MENUITEM "Paste"
END
POPUP "View"
MENUITEM "Help"
END
現在,在“檢視”類別中,我們希望再建立一個彈出選單,顯示“工具欄”。當我們將滑鼠懸停在“工具欄”命令上時,將在右側開啟一個子選單,其中包含所有選擇項:
IDM_MY_MENU MENU DISCARDABLE
BEGIN
POPUP "File"
BEGIN
MENUITEM "Open"
MENUITEM "Save"
MENUITEM "Close"
END
POPUP "Edit"
BEGIN
MENUITEM "Cut"
MENUITEM "Copy"
MENUITEM "Paste"
END
POPUP "View"
BEGIN
POPUP "Toolbars"
BEGIN
MENUITEM "Standard"
MENUITEM "Custom"
END
END
MENUITEM "Help"
END
這起初比較容易,只是現在我們需要提供一種方法來將我們的選單與程式連線起來。為此,我們必須為每個 MENUITEM 分配一個命令識別符號,我們可以在標頭檔案中定義它。通常,使用“IDC_”字首來命名這些命令資源,然後加上簡短的文字來說明它是什麼。例如,對於“檔案 > 開啟”命令,我們將使用名為“IDC_FILE_OPEN”的 ID。我們將在後面的資源標頭檔案中定義所有這些 ID 標籤。以下是包含所有 ID 的選單:
IDM_MY_MENU MENU DISCARDABLE
BEGIN
POPUP "File"
BEGIN
MENUITEM "Open", IDC_FILE_OPEN
MENUITEM "Save", IDC_FILE_SAVE
MENUITEM "Close", IDC_FILE_CLOSE
END
POPUP "Edit"
BEGIN
MENUITEM "Cut", IDC_EDIT_CUT
MENUITEM "Copy", IDC_EDIT_COPY
MENUITEM "Paste", IDC_EDIT_PASTE
END
POPUP "View"
BEGIN
POPUP "Toolbars"
BEGIN
MENUITEM "Standard", IDC_VIEW_STANDARD
MENUITEM "Custom", IDC_VIEW_CUSTOM
END
END
MENUITEM "Help", IDC_HELP
END
當我們單擊視窗中的一個條目時,訊息迴圈將收到一個 WM_COMMAND 訊息,訊息中的 WPARAM 引數包含識別符號。
我們將在標頭檔案中定義所有識別符號,使其在任意範圍內成為數值,並且不會與其他輸入源(加速器表、按鈕等)的命令識別符號重疊。
//resource.h
#define IDC_FILE_OPEN 200
#define IDC_FILE_SAVE 201
#define IDC_FILE_CLOSE 202
#define IDC_EDIT_COPY 203
#define IDC_EDIT_CUT 204
#define IDC_EDIT_PASTE 205
#define IDC_VIEW_STANDARD 206
#define IDC_VIEW_CUSTOM 207
#define IDC_HELP 208
然後,我們將此資源標頭檔案包含到主程式程式碼檔案和資源指令碼中。當我們想要將選單載入到程式中時,我們需要建立一個指向選單的控制代碼,即 HMENU。HMENU 資料項的大小和形狀與其他控制代碼型別相同,只是它們專門用於指向選單。
當我們啟動程式時,通常是在 WinMain 函式中,我們將使用 HMENU 資料項使用 LoadMenu 函式獲取指向此選單的控制代碼:
HMENU hmenu;
hmenu = LoadMenu(hInst, MAKEINTRESOURCE(IDM_MY_MENU));
我們將在下面的另一部分討論如何使用此控制代碼來使選單顯示出來。
要將選單與視窗類關聯,我們需要將選單的名稱包含在 WNDCLASS 結構中。請記住 WNDCLASS 結構:
typedef struct {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS, *PWNDCLASS;
它有一個名為“lpszMenuName”的資料欄位。我們將在這裡包含選單的 ID:
WNDCLASS wnd;
wnd.lpszMenuName = MAKEINTRESOURCE(IDM_MY_MENU);
請記住,我們需要使用 MAKEINTRESOURCE 關鍵字將數字識別符號(IDM_MY_MENU)轉換為適當的字串指標。
接下來,在將選單與視窗類關聯後,我們需要獲取指向選單的控制代碼:
HMENU hmenu;
hmenu = LoadMenu(hInst, MAKEINTRESOURCE(IDM_MY_MENU));
在獲得指向選單的 HMENU 控制代碼後,我們可以將其提供給 CreateWindow 函式,以便在建立視窗時建立選單:
HWND CreateWindow(
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hmenu,
HINSTANCE hInstance,
LPVOID lpParam
);
我們將 HMENU 控制代碼傳遞給 CreateWindow 函式呼叫的 hMenu 引數。這是一個簡單的示例:
HWND hwnd;
hwnd = CreateWindow(szClassName, "Menu Test Window!",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
hmenu,
hInstance,
0);
快速回顧一下,請注意我們正在為所有位置和大小屬性使用預設值。我們將新視窗定義為 WS_OVERLAPPEDWINDOW,這是一種常見且普通的視窗型別。此外,視窗的標題欄將顯示“選單測試視窗!”。我們還需要傳遞 HINSTANCE 引數,它是倒數第二個引數。