win32程序之子窗口編程
一丶簡介.什么是子窗口
在前邊我們已經講解了窗口的本質.以及如何注冊窗口類跟創建窗口. 還講了消息循環.
那么有很多窗口其實Windows已經幫我們創建出來了.我們直接使用即可. 而這些窗口都有自己的消息循環. 只有改變狀態的時候.才會發送消息給我們的父窗口通知.
此時我們捕獲消息就可以進行處理了.
子窗口其實就是繪制在主窗口的一個窗口. 這些窗口包含了 BUTTON (按鈕控件) EDIT(編輯框控件) .....
二丶創建子窗口
1.創建EDIT子窗口
創建子窗口很簡單. 使用CreteWindow API. 類名修改為EDIT. 父窗口句柄修改為我們的主窗口句柄. 並且為子窗口設置創建類型. 以及子窗口標識符即可.
具體代碼如下: 當主窗口創建消息來得時候.我們創建一個EDIT編輯框.
// WindoS.cpp : 定義應用程序的入口點。 // #include "stdafx.h" #include <stdio.h> #include <stdlib.h> #include "WindoS.h" #define MAX_LOADSTRING 100 // 全局變量: HINSTANCE g_hInst; // 當前實例 WCHAR szTitle[MAX_LOADSTRING] = TEXT("第一個我的窗口"); // 標題欄文本 WCHAR szWindowClass[MAX_LOADSTRING] = TEXT("MyWindow"); // 主窗口類名 #define IDC_MY_EDIT_ONE 10 //編輯框的ID 自己定義即可. // 此代碼模塊中包含的函數的前向聲明: LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { //1.自定義窗口樣式 g_hInst = hInstance; WNDCLASS wcex; wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; //設置回調 wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOS)); wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOS); wcex.lpszClassName = szWindowClass; //2.注冊窗口類 BOOL bRet = RegisterClass(&wcex); //A RegisterClass U RegisterClassW 擴展 RegisterClassExA /ExW if (bRet == FALSE) { return 0; } //3.創建窗口 並且顯示跟更新窗口 HWND hWnd = CreateWindowW( szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); if (!hWnd) { return FALSE; } ShowWindow(hWnd, SW_SHOW); UpdateWindow(hWnd); //4.消息循環. MSG msg; /* 1參數是消息結構體.操作系統會往里面填寫消息. 2 參數窗口句柄 因為每個線程可以有多個窗口.表示我要取那個窗口的消息 3.4 參數表示我要取這個窗口的那個消息. 后面三個參數屬於過濾條件 */ while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) { if (bRet == -1) { // handle the error and possibly exit } else { TranslateMessage(&msg); //鍵盤消息轉換為小寫. DispatchMessage(&msg); //分發消息.將我們的消息傳遞給我們的回調函數處理. } } return 0; } // // 函數: WndProc(HWND, UINT, WPARAM, LPARAM) // // 目的: 處理主窗口的消息。 // // WM_COMMAND - 處理應用程序菜單 // WM_PAINT - 繪制主窗口 // WM_DESTROY - 發送退出消息並返回 // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: { CreateWindowW( //創建編輯框 TEXT("EDIT"), TEXT("編輯框所處位置"), WS_CHILD | WS_VISIBLE | WS_VSCROLL, //前3個是通用風格.EDIT風格搜索MSDN 搜索Edit Style即可. 10,10,800,400, //設置X Y 坐標.設置高度跟寬度. hWnd, //父類句柄 (HMENU)IDC_MY_EDIT_ONE, g_hInst, nullptr); break; } case WM_COMMAND: { int wmId = LOWORD(wParam); // 分析菜單選擇: switch (wmId) { case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } } break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); // TODO: 在此處添加使用 hdc 的任何繪圖代碼... EndPaint(hWnd, &ps); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
上面紅色的那塊是很重要的. 重要參數標注出來.
1.窗口類名. 我們是使用的Windows默認的窗口類名.所以填寫EDIT
2.窗口風格. 窗口風格是使用的CreateWindow 中MSDN提供的默認風格. 當然編輯框也有自己的風格.我們可以MSDN搜尋 EDIT styles 查看說明.
3.父窗口句柄. 因為這個是創建在父窗口的所以我們的父窗口句柄一定要填寫.
4.實例句柄.這個必須要填寫的.已經改成全局變量了.
5.編輯框的ID.編輯框的ID屬於是控件ID. 這個位置在MSDN有說明. 如果創建的是父窗口.這個地方填寫的則是菜單.也就是HMENU類型的.但是如果是子窗口.那么這個位置就變成了控件ID了.
具體可以查看MSDN說明. 這個控件ID很重要.關乎到我們處理消息.
2.創建按鈕子窗口
上面創建了EDIT.那我們也可以創建按鈕子窗口了.具體代碼跟創建EDIT位置處一樣.
CreateWindowW( //創建按鈕 TEXT("BUTTON"), TEXT("設置"), WS_CHILD | WS_VISIBLE, // 820, 30, 100, 40, //設置X Y 坐標.設置高度跟寬度. hWnd, //父類句柄 (HMENU)IDC_MY_BUTTON_ONE, g_hInst, nullptr); CreateWindowW( //創建按鈕 TEXT("BUTTON"), TEXT("獲取"), WS_CHILD | WS_VISIBLE, // 820, 80, 100, 40, //設置X Y 坐標.設置高度跟寬度. hWnd, //父類句柄 (HMENU)IDC_MY_BUTTON_TWO, g_hInst, nullptr);
這兩行代碼放到創建EDIT下面即可.
關於按鈕的ID.我們設置一個自定義的整數值即可. 使用的時候需要強轉為HMENU類型.
結果演示.
三丶響應子窗口的消息.
現在我們已經創建完畢子窗口了.那么我們想的是我要響應按鈕消息什么的.
Windows雖然為每個子控件提供了消息處理函數. 也就是回調. 但是Windows為了讓我們處理消息. 所以子窗口有一個特性. 就是說當改變狀態的時候.會通知父窗口.
怎么理解.什么意思? 意思就是說.當我們點擊這個按鈕的時候.windows會發給我們父窗口一個消息. 我們只需要接受這個消息即可. 但是我們如何知道是哪個消息.?
既然我們知道了子窗口改變狀態會發送消息.那么我們可以調試一下.打印一下消息.
也就是在我們父窗口的消息處理回調中打印一下消息. 使用DebugView查看.或者調試查看都可以.
因為當我們點擊才會出現這個消息.那么我們可以看下這個消息是什么消息.
我們可以隨便點擊一個消息.查看定義.即可看到Windows全部的消息了. windows消息都放在了WinUser.h中
可以看到通知父窗口的是WM_COMMAND消息.
所以我們直接捕獲這個消息進行處理即可.
查詢MSDN 查詢WM_COMMAND消息.
詳細說明了.如果是WM_COMMAND消息. 那么參數三是控件ID. 還記得上面我們說的嗎. 要給每個控件分配一個控件ID. 就是在這里使用的.
具體看參數就如上圖所示. 告訴你了.低位才是 ID. 也就是 menu item標記. 所以我們需要取低位來判斷. 因為WPARAME 是32位.所以低位是16位.
我們可以自己使用位運算取.也可以使用操作系統提供的 LOWORD 來取.
具體代碼如下圖所示. PS: 直接拷貝窗口回調函數了.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
{
CreateWindowW( //創建編輯框
TEXT("EDIT"),
TEXT("編輯框所處位置"),
WS_CHILD | WS_VISIBLE | WS_VSCROLL, //前3個是通用風格.EDIT風格搜索MSDN 搜索Edit Style即可.
10,10,800,400, //設置X Y 坐標.設置高度跟寬度.
hWnd, //父類句柄
(HMENU)IDC_MY_EDIT_ONE,
g_hInst,
nullptr);
CreateWindowW( //創建按鈕
TEXT("BUTTON"),
TEXT("設置"),
WS_CHILD | WS_VISIBLE, //
820, 30, 100, 40, //設置X Y 坐標.設置高度跟寬度.
hWnd, //父類句柄
(HMENU)IDC_MY_BUTTON_ONE,
g_hInst,
nullptr);
CreateWindowW( //創建按鈕
TEXT("BUTTON"),
TEXT("獲取"),
WS_CHILD | WS_VISIBLE, //
820, 80, 100, 40, //設置X Y 坐標.設置高度跟寬度.
hWnd, //父類句柄
(HMENU)IDC_MY_BUTTON_TWO,
g_hInst,
nullptr);
break;
}
case WM_COMMAND: //獲取Command消息. 取出低位ID. 根據ID進行不同的操作.
{
int wmId = LOWORD(wParam);
// 分析菜單選擇:
switch (wmId)
{
case IDM_EXIT:
DestroyWindow(hWnd);
break;
case IDC_MY_BUTTON_ONE: //按鈕設置點擊則回來
{
//這個ID是按鈕設置的ID.所以當按鈕設置就會來這里了.
SetDlgItemText(hWnd, IDC_MY_EDIT_ONE, TEXT("設置到編輯框的內容")); //此API時設置指定窗口中控件ID的顯示名稱.我們給編輯框設置.所以ID是編輯框的ID.
break;
}
case IDC_MY_BUTTON_TWO:
{
// ID同上所示
TCHAR str[1024] = { NULL };
GetDlgItemText(hWnd, IDC_MY_EDIT_ONE, str, sizeof(str)); //有設置就有獲取. 獲取就是需要提供緩沖區而已.然后Msg信息框彈出.
MessageBox(hWnd, str, NULL, NULL);
break;
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
最終實現結果.
點擊設置后.編輯框的內容會改變.
點擊獲取后則會獲取編輯框的內容.
四丶完整代碼.
最后附上完整代碼.拷貝就能使用. VS2015編寫.不確定是否可以.不過可以參考代碼.
代碼如下:
// WindoS.cpp : 定義應用程序的入口點。 // #include "stdafx.h" #include <stdio.h> #include <stdlib.h> #include "WindoS.h" #define MAX_LOADSTRING 100 // 全局變量: HINSTANCE g_hInst; // 當前實例 WCHAR szTitle[MAX_LOADSTRING] = TEXT("第一個我的窗口"); // 標題欄文本 WCHAR szWindowClass[MAX_LOADSTRING] = TEXT("MyWindow"); // 主窗口類名 #define IDC_MY_EDIT_ONE 10 //編輯框的ID 自己定義即可. #define IDC_MY_BUTTON_ONE 11 #define IDC_MY_BUTTON_TWO 12 // 此代碼模塊中包含的函數的前向聲明: LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { //1.自定義窗口樣式 g_hInst = hInstance; WNDCLASS wcex; wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; //設置回調 wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOS)); wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOS); wcex.lpszClassName = szWindowClass; //2.注冊窗口類 BOOL bRet = RegisterClass(&wcex); //A RegisterClass U RegisterClassW 擴展 RegisterClassExA /ExW if (bRet == FALSE) { return 0; } //3.創建窗口 並且顯示跟更新窗口 HWND hWnd = CreateWindowW( szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); if (!hWnd) { return FALSE; } ShowWindow(hWnd, SW_SHOW); UpdateWindow(hWnd); //4.消息循環. MSG msg; /* 1參數是消息結構體.操作系統會往里面填寫消息. 2 參數窗口句柄 因為每個線程可以有多個窗口.表示我要取那個窗口的消息 3.4 參數表示我要取這個窗口的那個消息. 后面三個參數屬於過濾條件 */ while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) { if (bRet == -1) { // handle the error and possibly exit } else { TranslateMessage(&msg); //鍵盤消息轉換為小寫. DispatchMessage(&msg); //分發消息.將我們的消息傳遞給我們的回調函數處理. } } return 0; } // // 函數: WndProc(HWND, UINT, WPARAM, LPARAM) // // 目的: 處理主窗口的消息。 // // WM_COMMAND - 處理應用程序菜單 // WM_PAINT - 繪制主窗口 // WM_DESTROY - 發送退出消息並返回 // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: { CreateWindowW( //創建編輯框 TEXT("EDIT"), TEXT("編輯框所處位置"), WS_CHILD | WS_VISIBLE | WS_VSCROLL, //前3個是通用風格.EDIT風格搜索MSDN 搜索Edit Style即可. 10,10,800,400, //設置X Y 坐標.設置高度跟寬度. hWnd, //父類句柄 (HMENU)IDC_MY_EDIT_ONE, g_hInst, nullptr); CreateWindowW( //創建按鈕 TEXT("BUTTON"), TEXT("設置"), WS_CHILD | WS_VISIBLE, // 820, 30, 100, 40, //設置X Y 坐標.設置高度跟寬度. hWnd, //父類句柄 (HMENU)IDC_MY_BUTTON_ONE, g_hInst, nullptr); CreateWindowW( //創建按鈕 TEXT("BUTTON"), TEXT("獲取"), WS_CHILD | WS_VISIBLE, // 820, 80, 100, 40, //設置X Y 坐標.設置高度跟寬度. hWnd, //父類句柄 (HMENU)IDC_MY_BUTTON_TWO, g_hInst, nullptr); break; } case WM_COMMAND: { int wmId = LOWORD(wParam); // 分析菜單選擇: switch (wmId) { case IDM_EXIT: DestroyWindow(hWnd); break; case IDC_MY_BUTTON_ONE: { //這個ID是按鈕設置的ID.所以當按鈕設置就會來這里了. SetDlgItemText(hWnd, IDC_MY_EDIT_ONE, TEXT("設置到編輯框的內容")); break; } case IDC_MY_BUTTON_TWO: { // ID同上所示 TCHAR str[1024] = { NULL }; GetDlgItemText(hWnd, IDC_MY_EDIT_ONE, str, sizeof(str)); MessageBox(hWnd, str, NULL, NULL); break; } default: return DefWindowProc(hWnd, message, wParam, lParam); } } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return DefWindowProc(hWnd, message, wParam, lParam); }