WindowsAPI窗口程序设计-基础语法

基本概念

WinMain

入口函数。

1
2
3
4
5
6
INT WINAPI _tWinMain(
_In_ HINSTANCE hInstance, //应用程序当前实例句柄
_In_opt_ HINSTANCE hPrevInstance, //废弃
_In_ LPSTR lpCmdLine, //命令行参数字符串 不包括可执行文件名
_In_ INT nShowCmd //该程序最初怎么显示
);

MessageBox

消息提示框。

1
2
3
4
5
6
INT WINAPI MessageBox(
_In_opt_ HWND hWnd, //所有者窗口句柄
_In_opt_ LPCTSTR lpText, //显示的消息内容
_In_opt_ LPCTSTR lpCaption, //标题
_In_ UINT uType //图标按钮样式
)

按钮取值:

1
2
3
4
5
6
7
8
MB_ABORTRETRYIGNORE
MB_CANCELTRYCONTINUE
MB_HELP
MB_OK
MB_OKCANCEL
MB_RETRYCANCEL
MB_YESNO
MB_YESNOCANCEL

图标取值:

1
2
3
4
5
6
7
8
MB_ICONEXCLAMATION
MB_ICONWARNING
MB_ICONINFORMATION
MB_ICONASTERISK
MB_ICONQUESTION
MB_ICONSTOP
MB_ICONERROR
MB_ICONHAND

默认按钮:

1
2
3
4
MB_DEFBUTTON1
MB_DEFBUTTON2
MB_DEFBUTTON3
MB_DEFBUTTON4

返回值:

1
2
3
4
5
6
7
8
9
IDABORT
IDCANCEL
IDCONTINUE
IDIGNORE
IDNO
IDOK
IDRETRY
IDTRYAGIN
IDYES

例子:

1
2
3
4
5
6
7
8
9
INT nRet = ::MessageBox(NULL, TEXT("Hello World!"), TEXT("Caption"), MB_OKCANCEL | MB_ICONINFORMATION | MB_DEFBUTTON2);
switch (nRet) {
case IDOK: {
//...
};
case IDCANCEL: {
//...
};
};

字符串

开始

Visual C++中存在两套编码系统,分别是ANSI和Unicode,两套各自有不同的一套API,这里同一使用tchar的方法,让编译器根据项目属性决定(当然一般就Unicode)。

需要引入:

1
2
#include <tchar.h>
#include <locale.h>

_tcslen

字符串长度。

1
2
3
TCHAR szStr[] = TEXT("C语言");
_tprintf(TEXT("%d\n"), _tcslen(szStr));
return 0;

_tcschr

字符串中首次出现的指定字符。

1
2
3
4
TCHAR szStr[] = TEXT("C语言");
LPTSTR lp = _tcschr(szStr, TEXT("C"));
setlocale(LC_ALL, "chs"); //_tprintf输出中文字符时需要设置区域
_tprintf(TEXT("%p %s\n"), lp, lp);

_tcsstr

字符串中查找另一个字符串。

1
2
3
TCHAR szStr[] = TEXT("C语言");
TCHAR szStrSearch[] = TEXT("语言");
_tprintf(TEXT("%s\n"), _tcsstr(szStr, szStrSearch));

_tcsupr/_tcslwr

转大、小写。这俩不安全,可使用_tcsupr_s/_tcslwr_s

1
2
3
TCHAR szStr[] = TEXT("C语言");
_tprintf(TEXT("%s\n"), _tcsupr_s(szStr, _tcslen(szStr)+1));
_tprintf(TEXT("%s\n"), _tcslwr_s(szStr, _tcslen(szStr)+1));

_tcscat

字符串拼接,用安全版本。

1
2
3
TCHAR szStrDest[] = TEXT("C语言");
TCHAR szStrSour[] = TEXT("WindowsAPI");
_tcscat_s(szStrDest, _countof(szStrDest), szStrSour);

StringCchCopy

字符串复制。同类型函数有_tcscpy_smemcpy_s等,但当出现不以0结尾的字符串时,这俩不能正确使用。

1
2
3
TCHAR szStrDest[] = TEXT("C语言");
TCHAR szResType[128] = { 0 };
StringCchCopy(szResType, 5, szStrDest); //szStrDest的前5-1个字符被复制给szResType 剩余截断 第5个字符填0

_tcscmp/CompareStringEx

字符串比较,短的小,一般按照字典序比较。

1
2
3
4
TCHAR szStr1[] = TEXT("我爱老王");
TCHAR szStr2[] = TEXT("我是老王");
setlocale(LC_ALL, "chs");
INT n = _tcscmp(szStr1, szStr2); //<0小于 =0等于 >0大于

还能按照中文拼音排序:

1
2
3
4
TCHAR szStr1[] = TEXT("我爱老王");
TCHAR szStr2[] = TEXT("我是老王");
setlocale(LC_ALL, "chs");
INT n = _tcscoll(szStr1, szStr2); //<0小于 =0等于 >0大于

这些内部都是用的CompareStringEx实现的:

1
2
3
4
5
6
7
8
9
10
11
INT CompareStringEx(
_In_opt_ LPCWSTR lpLocaleName, //区域设置名称
_In_ DWORD dwCmpFlags, //比较方式
_In_ LPCWSTR lpString1, //字符串1
_In_ INT cchCount1, //字符串1字符长度 可-1
_In_ LPCWSTR lpString2, //字符串2
_In_ INT cchCount2, //字符串2字符长度 可-1
_In_ LPNLSVERSIONINFO lpVersionInformation,
_In_opt_ LPVOID lpReserved,
_In_opt_ LPARAM lParam
)//返回值CSTR_LESS_THAN=1 CSTR_EQUAL=2 CSTR_GREATER_THAN=3

dwCmpFlags一些枚举值:

标志 含义
LINGUISTIC_IGNORECASE/NORM_IGNORECASE 忽略大小写
NORM_IFNORESYMBOLS 忽略符号和标点符号
NORM_LINGUISTIC_CASING 大小写使用语言规则,而不是文件系统规则
SORT_DIGITSASNUMBERS 字串前数字字符解释为数值型数字

例如上文的compare函数可修改为:

1
2
3
INT compare(CONST PVOID arg1, CONST PVOID arg2) {
return CompareStringEx(LOCALE_NAME_SYSTEM_DEFAULT, SORT_DIGITSASNUMBERS, *(LPTSTR*)arg1, -1, *(LPTSTR*)arg2, -1, NULL, NULL, NULL) - 2;
};

_tcstok

字符串分割。

1
2
3
4
5
6
7
8
9
TCHAR strToken[] = TEXT("A string\tof ,,tokens\nand some  more tokens");
TCHAR strDelimit[] = TEXT(" ,\t\n");
LPTSTR lpToken = NULL;
LPTSTR lpTokenNext = NULL;
lpToken = _tcstok_s(strToken, strDelimit, &lpTokenNext);
while (lpToken != NULL) {
_tprintf(TEXT("%s\n"), lpToken);
lpToken = _tcstok_s(NULL, strDelimit, &lpTokenNext);
};

qsort

字符串快速排序。

1
2
3
4
5
6
7
8
setlocale(LC_ALL, "chs");
LPTSTR arrStr[] = {
TEXT("xxx"),
TEXT("xxx")
};
qsort(arrStr, _countof(arrStr), sizeof(LPTSTR), compare);
for (SIZE_T i = 0; i < _countof(arrStr); i++)
_tprintf(TEXT("%s\n"), arrStr[i]);

排序回调函数:

1
2
3
INT compare(CONST PVOID arg1, CONST PVOID arg2) {
return _tcscoll(*(LPSTR*)arg1, *(LPSTR*)arg2);
};

数值与字符串互转

这东西太多了,这里只讲int转字符串。

1
2
3
4
INT n = 0x12CFFE20;
TCHAR szBuf[16] = { 0 };
_itot_s(n, szBuf, _countof(szBuf), 10); //n转10进制再转字符串
_tprintf(TEXT("%s\n"), szBuf);

格式化字符串

1
2
3
4
5
6
7
8
TCHAR szName[] = TEXT("xxx");
TCHAR szAddress[] = TEXT("xxx");
INT nAge = 18;
TCHAR szBuf[128] = { 0 };
HRESULT hResult = E_FAIL;
hResult = StringCchPrintf(szBuf, _countof(szBuf), TEXT("xxx%sxxx%sxxx%d\n"), szName, szAddress, nAge);
if(SUCCEEDED(hResult))
//...

ANSI与Unicode互转

ANSI转Unicode用MultiByteToWideChar

1
2
3
4
5
6
7
8
INT MultiByteToWideChar(
_In_ UINT CodePage, //转换时用的代码页 CP_ACP系统默认ANSI代码页
_In_ DWORD dwFlags, //指定转换类型
_In_ LPCSTR lpMultiByteStr, //要转换的多字节字符串
_In_ INT cbMultiByte, //大小 单位字节 以0结尾可-1
_Out_opt_ LPWSTR lpWideCharStr, //接收转换后字符串缓冲区
_In_ INT cchWideChar //指向的缓冲区的大小 单位字节 0返回所需缓冲区大小
)

例如:

1
2
3
4
5
6
LPCSTR lpMultiByteStr = "xxx";
INT nCchWideChar = MultiByteToWideChar(CP_ACP, 0, lpMultiByteStr, -1, NULL, 0); //第一次获取所需缓冲区大小
LPWSTR lpWideCharStr = new WCHAR[nCchWideChar]; //按需分配缓冲区大小
MultiByteToWideChar(CP_ACP, 0, lpMultiByteStr, -1, lpWideCharStr, nCchWideChar);
//...
delete[] lpWideCharStr;

Unicode转ANSI用WideCharToMultiByte,参数同上,后面多出来那俩就NULL就行。

1
2
3
4
5
6
7
8
9
10
INT WideCharToMultiByte(
_In_ UINT CodePage,
_In_ DWORD dwFlags,
_In_ LPWSTR lpWideCharStr,
_In_ INT cchWideChar
_Out_opt_ LPCSTR lpMultiByteStr,
_In_ INT cbMultiByte,
_In_opt_ LPCSTR lpDefaultChar,
_Out_opt_ LPBOOL lpUsedDefaultChar
)

这里不写了。

结构体对齐

对齐颗粒度可手动设置为1、2、4、8,例如4时:

1
2
3
4
5
6
7
8
#pragma pack(4)
struct MyStruct {
INT a;
CHAR b;
DOUBLE c;
FLOAT d;
};
#pragma pack()

想用系统默认就填show,然后警告里会提示,Win32是8,Win64是16。

窗口基础

概览

直接用Visual Studio自带模板即可,打开差不多就这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// WindowsProject1.cpp : 定义应用程序的入口点。
#include "framework.h"
#include "WindowsProject1.h"
#define MAX_LOADSTRING 100
// 全局变量:
HINSTANCE hInst; // 当前实例
TCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, INT);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
INT APIENTRY wWinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPWSTR lpCmdLine,_In_ INT nCmdShow){
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此处放置代码。
// 初始化全局字符串
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance(hInstance, nCmdShow))
return FALSE;
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));
MSG msg;
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0))
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
};
return (INT)msg.wParam;
};
// 函数: MyRegisterClass()
// 目标: 注册窗口类。
ATOM MyRegisterClass(HINSTANCE hInstance) {
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
};
// 函数: InitInstance(HINSTANCE, int)
// 目标: 保存实例句柄并创建主窗口
// 注释:
// 在此函数中,我们在全局变量中保存实例句柄并创建和显示主程序窗口。
BOOL InitInstance(HINSTANCE hInstance, INT nCmdShow) {
hInst = hInstance; // 将实例句柄存储在全局变量中
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
return FALSE;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
};
// 函数: 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_COMMAND: {
INT wmId = LOWORD(wParam);
// 分析菜单选择:
switch (wmId) {
case IDM_ABOUT: {
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
};
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;
};
// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
UNREFERENCED_PARAMETER(lParam);
switch (message) {
case WM_INITDIALOG: {
return (INT_PTR)TRUE;
};
case WM_COMMAND: {
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
};
break;
};
};
return (INT_PTR)FALSE;
};

上面这个加了些东西,导致不适合上手,删去快捷键、菜单、关于,程序比较简单了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include "framework.h"
#include "WindowsProject1.h"
#define MAX_LOADSTRING 100
HINSTANCE hInst;
TCHAR szTitle[MAX_LOADSTRING];
TCHAR szWindowClass[MAX_LOADSTRING];
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, INT);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
INT APIENTRY wWinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPWSTR lpCmdLine,_In_ INT nCmdShow){
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
if (!InitInstance(hInstance, nCmdShow))
return FALSE;
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
};
return (INT)msg.wParam;
};
ATOM MyRegisterClass(HINSTANCE hInstance) {
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = NULL;
return RegisterClassEx(&wcex);
};
BOOL InitInstance(HINSTANCE hInstance, INT nCmdShow) {
hInst = hInstance;
HWND hWnd = CreateWindowEx(0,szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 180, NULL,NULL, hInstance, NULL);
if (!hWnd)
return FALSE;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
};
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_CREATE: {
break;
};
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
break;
};
case WM_DESTROY: {
PostQuitMessage(0);
break;
};
default: {
return DefWindowProc(hWnd, uMsg, wParam, lParam);
};
};
return 0;
};

注册窗口类

用这个函数:

1
2
3
ATOM WINAPI RegisterClassEx(
_In_ CONST PWNDCLASSEX lpwcx
)

分析WNDCLASSEX这个结构:

1
2
3
4
5
6
7
8
9
10
11
12
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW; //窗口类样式 窗口宽、高发生变化时重绘整个窗口
wcex.lpfnWndProc = WndProc; //窗口过程
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1)); //图标
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); //光标
wcex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //背景画刷
wcex.lpszMenuName = NULL; //菜单
wcex.lpszClassName = szWindowClass; //窗口类名 绘制窗口用
wcex.hIconSm = NULL; //小图标

图标和光标的加载方式:

1
2
3
4
5
6
7
8
HICON WINAPI LoadIcon(
_In_opt_ INSTANCE hInstance,
_In_ LPCTSTR lpIconName
)
HCURSOR WINAPI LoadCursor(
_In_opt_ HINSTANCE hInstance,
_In_ LPCTSTR lpCursorName
)

系统预定义图标:

1
2
3
4
5
6
IDI_APPLICATION/IDI_WINLOGO
IDI_ASTERISK/IDI_INFORMATION
IDI_ERROR/IDI_HAND
IDI_QUESTION
IDI_EXCLAMATION/IDI_WARNING
IDI_SHIELD

系统预定义光标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
IDC_APPSTARTING
IDC_ARROW
IDC_CROSS
IDC_HAND
IDC_HELP
IDC_IBEAM
IDC_NO
IDC_SIZEALL
IDC_SIZENESW
IDC_SIZENS
IDC_SIZENWSE
IDC_SIZEWE
IDC_UPARROW
IDC_WAIT

背景画刷用GetStockObject获取系统预定义背景画刷:

1
2
3
HGDIOBJ GetStockObject(
_In_ INT fnObject
)

预定义的背景画刷在这儿就不写了,太多了。

创建窗口

创建窗口,并产生WM_CREATE消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
HWND WINAPI CreateWindowEx(
_In_ DWORD dwExStyle, //窗口样式
//WS_EX_WINDOWEDGE/WS_EX_CLIENTEDGE 窗口边缘有凸起/凹陷边缘
//WS_EX_ACCEPTFILES 接受拖放文件
//WS_EX_EX_TOPMOST 始终保持在最顶层
//WS_EX_LAYERED 分层或透明
//WS_EX_TOOLWINDOW 工具窗口 作为浮动工具条
_In_opt_ LPCTSTR lpClassName, //窗口类名
_In_opt_ LPCTSTR lpWindowName, //窗口标题
_In_ DWORD dwStyle, //窗口样式
//WS_BORDER 窗口有细线边框
//WS_CAPTION 窗口有标题栏+WS_BORDER
//WS_HSCROLL/WS_VSCROLL 有水平/垂直滚动条
//WS_MAXIMIZEBOX/WS_MINIMIZEBOX 有最大化/最小化窗口
//WS_ICONIC(WS_WS_MINIMIZE)/WS_MAXIMIZE 最初最小化/最大化
//WS_SIZEBOX(WS_THICKFRAME) 通过边框调整窗口大小
//WS_SYSMENU 有个系统菜单 必须有WS_CAPTION
//WS_OVERLAPPED(WS_TILED) 重叠窗口 有标题栏和边框
//WS_CHILD 子窗口
//WS_CLIPSIBLINGS 不对兄弟窗口绘制
//WS_OVERLAPPEDWINDOW(WS_TILEDWINDOW) 等于WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX
_In_ INT x, //初始水平位置 单位像素 默认CW_USEDEFAULT
_In_ INT y,
_In_ INT nWidth, //初始宽度 单位像素 默认CW_USEDEFAULT
_In_ INT nHeight,
_In_opt_ HWND hWndParent, //父窗口
_In_opt_ HMENU hMenu, //菜单句柄
_In_opt_ HINSTANCE hInstance, //窗口关联的实例句柄
_In_opt_ LPVOID lpParam
)

实现:

1
2
3
HWND hWnd = CreateWindowEx(0,szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 180, NULL,NULL, hInstance, NULL);
if (!hWnd)
return FALSE;

窗口显示与刷新

显示窗口,并产生WM_SIZE、WM_SHOWWINDOW消息:

1
2
3
4
BOOL WINAPI ShowWindow(
_In_ HWND hWnd, //窗口句柄
_In_ INT nCmdShow //窗口显示方式 用WinMain的就行 一般SW_SHOWDEFAULT
)

刷新窗口客户区,产生WM_PAINT消息:

1
2
3
BOOL WINAPI UpdateWindow(
_In_ HWND hWnd
)//成功TRUE 失败FALSE

实例:

1
2
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

消息循环

从消息队列中获取消息:

1
2
3
4
5
6
BOOL WINAPI GetMessage(
_Out_ LPMSG lpMsg, //存放消息
_In_opt_ HWND hWnd, //哪个窗口的消息
_In_ UINT wMsgFilterMin, //要获取信息的最大/小值
_In_ UINT wMsgFilterMax
);

MSG结构:

1
2
3
4
5
6
7
8
9
10
11
typedef struct tagMSG {
HWND hwnd; //哪个窗口发生的消息
UINT message; //消息类型
WPARAM wParam; //消息参数
LPARAM lParam; //消息参数
DWORD time; //消息发生的时间
POINT pt; //消息发生时的光标位置 屏幕坐标
#ifdef _MAC
DWORD lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

其中POINT结构为:

1
2
3
4
typedef struct tagPOINT {
LONG x;
LONG y;
} POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT;

将按键信息(按下WM_KEYUP、抬起WM_KEYDOWN)转换为字符消息(WM_CHAR),并将字符消息发送到调用线程的消息队列,下次调用GetMessage时获取这个字符信息:

1
2
3
BOOL WINAPI TranslateMessage(
_In_ CONST PMSG lpMsg //从GetMessage获取的MSG结构
)

GetMessage获取的消息分发送到窗口:

1
2
3
LRESULT WINAPI DispatchMessage(
_In_ CONST PMSG lpmsg
)

例子:

1
2
3
4
5
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
};

窗口过程

名字随便起,但定义这样:

1
2
3
4
5
6
LRESULT CALLBACK WindowProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)

WM_CREATE消息,在CreateWindowEx创建窗口时发送:

1
2
3
case WM_CREATE: {
break;
};

当单击窗口关闭按钮时,产生WM_CLOSE消息,用DestroyWindow完成窗口清理工作并产生WM_DESTROY消息。处理WM_DESTROY消息时用PostQuitMessage并产生WM_QUIT消息,GetMessage获取WM_QUIT消息后程序退出消息循环并将控制权返回给系统。

1
2
3
VOID WINAPI PostQuitMessage(
_In_ INT nExitCode //应用程序退出代码
)

例如:

1
2
3
4
case WM_DESTROY: {
PostQuitMessage(0);
break;
};

对于其他剩余不关心的消息的处理,用DefWindowProc来处理就好了。

1
2
3
4
5
6
LRESULT WINAPI DefWindowProc(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)