重要头文件:windows.h;

word 是字,占2个字节;

不会的函数可以去微软查出来,vs里选中F1;

链接器 子系统 选择 窗口;

windows是操作消息的,它有一个消息队列,可获取如鼠标键盘产生的消息;

数据类型

  • UINT unsigned int

  • DWORD double word

  • PDWORD pointer double word

  • BOOL bool

  • short short int

  • LRSULT 32 函数返回值

  • WPARAM LPARAM 32 消息参数

  • HANDLE 理解成windows对象,句柄

  • HWND 窗口句柄

  • HINSTANCE 实例句柄

主函数

1
2
3
4
5
6
int WINAPI WinMain(
HINSTANCE hInstance, //程序的实例句柄
HINSTANCE hPreInstance, //上一个程序实例句柄(遗弃)
LPSTR lpCmdLine, //命令行参数
int nCmdShow //显示方式(最大化,窗口)
)

参数一个不能少;

弹窗

1
2
3
4
5
6
int MessageBox(
HWND hWnd; //所有者窗口句柄(父级窗口) 无可填NULL
LPCTSTR lpText; //显示内容
LPCTSTR lpCaption, //标题
UINT uType //风格(确认,取消一类的按键)
);

选择不同的按键返回不同的值;

字符串处理

  • ascii码对应普通字符串 CHAR -> char
  • utf系列对应宽字符串 WCHAR -> wchar_t 输出是 %ls 用 L 修饰
  • 通用字符串 TCHAR -> 类型随环境变化,引用 tchar.h 头 用_T()修饰;

由此引申出了三个版本的操作函数;

长度操作

strlen wcslen _tcslen

字符串转数字

atoi strtol

_wtoi wcstol

_ttoi tcstol

数字转字符串

itoa _itow _itot

因为字符串,所以分三个版本:A W T;

如 MessageBoxA ,以及 MessageBoxW ,前者处理多字节,后者处理宽字节;

字节转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
//宽字节转多字节  (用的时候直接用宏定义)
WideCharToMultiByte()
---
#define WCHAR_TO_CHAR(lpW_Char, lpChar) \
WideCharToMultiByte(CP_ACP, NULL, lpW_Char, -1, \
lpChar, sizeof(lpChar), NULL, FALSE)

//多字节转宽字节
MultiByteToWideChar()
---
#define CHAR_TO_WCHAR(lpChar, lpWchar) \
MultiByteToWideChar(CP_ACP, NULL, lpChar, -1, \
lpWchar, sizeof(lpWchar))

创建窗口

先创建WinMain函数

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
{
//都用W版本
//创建窗口类
WNDCLASSW wnd = { 0 };
wnd.lpszClassName = L"Second_BC"; //类名唯一
wnd.lpfnWndProc = WindowProc; //窗口回调函数

//注册窗口类
RegisterClassW(&wnd);

//创建窗口
HWND window = CreateWindowW(
wnd.lpszClassName, //类名
L"1049", //窗口名
WS_OVERLAPPEDWINDOW,//风格
CW_USEDEFAULT, //x,y坐标,默认款式
0,
CW_USEDEFAULT, //长宽,默认款式
0,
NULL, //父窗口的句柄
NULL, //菜单
hInstance, //实例句柄
0 //传给回调函数参数
);

//显示窗口
ShowWindow(
window, //窗口句柄
SW_NORMAL //默认显示方式
);

//获取消息
MSG msg = { 0 }; //消息类
while (GetMessageW( //不断获取消息
&msg, //消息类
0, //窗口句柄 0默认全部窗口
0, //消息类型默认
0
))
{
DispatchMessageW(&msg); //分发消息给处理函数
}
return 0;
}

处理函数\回调:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
LRESULT CALLBACK WindowProc(
_In_ HWND hwnd, //窗口句柄
_In_ UINT uMsg, //消息
_In_ WPARAM wParam, //参数
_In_ LPARAM lParam
)
{
switch (uMsg) //操作消息
{
case WM_CLOSE:
DestroyWindow(hwnd);//销毁窗口
PostQuitMessage(0); //退出消息,终止循环
break;
default:
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam); //默认窗口处理
}

简单说说回调函数机制:

每个窗口类会带有一个回调函数,用于处理这个类创建的窗口所获取的信息;

在无限循环的信息捕获中,当收到信息后(晃动鼠标,点击,按键),则使得 DispatchMessageW(&msg); 激活,调用指定窗口回调函数;

在这里指定窗口是 0 ,则是所有窗口;

回调函数中,为了当 点击叉掉 窗口时就 结束程序 就应该设置 退出信息 : WM_CLOSE 摧毁窗口,并退出 postmessage,此时就会退出在主函数里的循环;

回调函数的参数,第二个是信息,第三第四个是一些参数,这些参数有一定的作用,可以知道实际的状态:键盘按下但无弹起 / 鼠标处于窗口位置,等等;

消息处理

定义在 WINUSER.H 中, 以 WM_开头 (windows message);

类型

  1. 窗口消息,如之前遇到的 WM_CLOSE

  2. 命令消息,特指 WM_COMMAND ,点击菜单,控件等会产生;

    WM_COMMAND LOW WPARAM HIGH WPARAM LPARAM
    标准控件 id 响应码 控件句柄
    快捷键 id 1 0
    菜单 id 0 0
  3. 通知消息,特指 WM_NOTIFY ,只使用 用 windows 的公共控件,如列表,视图;

    WM_NOTIFY WPARAM LPARAM
    id NMHDR指针

    NMHDR -> notify message header

控件消息,如:

BM_ 按钮

EM_ 编辑框

STM_ 静态文本

CM_ 组合框

LBM_ 列表

以及用户自定义消息,消息号大于 WM_USER ;

发送

PostMessage, SendMessage;

前者放到消息队列,后者主动调用 指定的回调函数;

变参函数

用于如printf输出宽字节;

1
2
3
4
5
6
7
8
9
10
void func(LPCWSTR format, ...)
{
WCHAR wchar_buff[100]{ 0 };
va_list arglist; //本质char类型,用于存放后面的参数

va_start(arglist, format); //第二个参数是指针起始+1,也就是后面的参数
wvsprintfW(wchar_buff, format, arglist);
va_end(arglist); //结束
wprintf(format, wchar_buff); //输出
}

窗口控件

使用控件引头文件: <CommCtrl.h>

窗口风格分两类:

窗口关系:

1
2
3
WS_OVERLAPED	重叠
WS_POPUP 弹窗
WS_CHILD 子窗口

窗口外观:

1
2
WS_BORDER
WS_CAPITON

它们之间可以用 | 运算结合使用;

控件的本质,还是窗口,在父窗口创建开始添加即可;

标准控件:

类名 名称
WC_BUTTON 按钮
WC_STATIC 静态文本
WC_COMBOBOX 复合框
WC_EDIT 编辑框
WC_LISTBOX 列表框
WC_SCROLLBAR 滚动条

通用控件,如:

WC_LISTVIEW 列表框控件

WC_TREEVIEW 树控件

WC_TABCONTROL Tab控件

子控件响应父窗口,使用 命令消息 和 通知消息,标准控件使用前者,通用控件使用后者;

修改之后的回调函数:

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
LRESULT CALLBACK WindowProc(
_In_ HWND hwnd, //窗口句柄
_In_ UINT uMsg, //消息
_In_ WPARAM wParam, //参数
_In_ LPARAM lParam
)
{
static HINSTANCE hInstance = GetModuleHandleW(NULL); //NULL默认获取当前程序实例句柄

switch (uMsg) //操作消息
{
case WM_CREATE:
//创建窗口时创建子控件,menu栏是控件ID,存于wParam低位,lParam存控件句柄
CreateWindowW(WC_BUTTON, L"Button", WS_CHILD | WS_VISIBLE, 10, 10, 80, 30, hwnd, (HMENU)0x100, hInstance, 0);
break;
case WM_CLOSE:
DestroyWindow(hwnd);//销毁窗口
PostQuitMessage(1); //退出消息
break;

case WM_COMMAND:
{
//激活控件,获取控件ID,执行相应操作
WORD controlId = LOWORD(wParam);
switch (controlId)
{
case 0x100:
MessageBoxW(hwnd, L"Click", L"Button", MB_OK);
break;
}
break;
}

}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

如上可以实现一个按钮弹窗功能;

窗口操作函数

背景刷

1
wnd.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));		//白色背景

移动窗口位置

1
2
3
4
5
6
7
RECT rect{ 0 };
GetClientRect(hwnd, &rect); //获取窗口工作区范围,返回给rect结构体

int x = rand() % (rect.right - weight); //x,y坐标在工作范围内随机取
int y = rand() % (rect.bottom - height);

MoveWindow((HWND)lParam, x, y, weight, height, TRUE); //移动窗口函数

获取和设置文本框内容

1
2
3
4
5
6
7
8
9
10
HWND hedit = GetDlgItem(hwnd, 0x102);		//获取文本框句柄,第一个是父窗口句柄,第二个是id
WCHAR buffer[max]{ 0 }; //缓冲区

GetWindowTextW(hedit, buffer, max); //用按钮实现
SetWindowTextW(hedit, L"123");
---
//实际上,对于Dlg的操作可以简化,下面等价于获取hedit之后写文本框
SetDlgItemTextW(hwnd, 0x102, L"123");

TranslateMessage(&msg); //放到message循环里,接收键盘信息编辑文本框

根据窗口名获取句柄

1
HWND hwnd = FindWindowW(ClassName, Name);		//第一个窗口类名,第二个窗口名,不知道可以填0

设置父窗口

1
SetParent((HWND)lParam, hwnd);			//第一个是要被设置的,第二个是新的父窗口

资源操作

资源就是icon,光标,菜单一类的东西;

在VS里,代码下方可以创建资源,资源创建后,有资源本身,有.rc文件,以及resource.h头文件;

.rc保存了资源本身在文件中的宏命名,一般是int型,而头文件则是声明;

引入头后,使用 LoadXXX 函数获取资源句柄(XXX为资源类型,如图标是Icon);

1
LoadCursor(hInstance, MAKEINTRESOURCE(IDC_CURSOR1))

参数类型第一个为实例句柄,第二个为资源宏,本质上是个指针,所以要强转,微软自带强转宏函数 MAKEINTRESOURCE()

使用如下代码设置类成员:

1
2
//三个参数,第一个为窗口句柄,第二个为GCL_开头宏对应成员如GCL_ICON,第三个为设置的句柄,记得强转long
SetClassLong(hwnd, GCL_ICON, (long)handle);

菜单可以加载后给CreateWindow函数;

子菜单响应也是 WM_COMMAND;

使用以下函数获取子菜单以及弹出菜单:

1
2
GetSubMenu(hMenu, 0); 		//第一个父菜单,第二个相对于父菜单位置
TrackPopupMenu(hMenu, TPM_RIGHTALICGN, x, y, 0, hWnd, NULL); //第二个为对齐方式

转换坐标当前窗口

1
2
3
4
POINT point { 0 };
point.x = x;
point.y = y;
ClientToScreen(hwnd, &point);

对话框

模态的会阻塞主窗口(无法点击主窗口),非模态不会;

创建非模态:

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
CreateDialogW(		
hInstance,
dialogName, //ID
NULL, //父窗口句柄
Dlgproc //回调函数
);

INT_PTR CALLBACK Dlgproc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
switch(uMsg)
{
case WM_INITDIALOG: //初始化
{

break;
}
case WM_CLOSE:
{
DestroyWindow(hWnd);
QuitPostMessage(0);
break;
}
default:
return FALSE; //没能处理返回无
}

return TRUE; //处理返回真
}

创建模态:

它不需要捕获信息,也不需要显示

1
2
3
DialogBoxW( /* same */)
//结束
EndDialog(hWnd, 0);

介绍一些简单的控件:

windowsx.h 头有定义操作控件信息的宏,可读性更高;

复选框,单选框,属于按钮类,其信息为 BM_打头;

图片,其信息 STM_打头;

滑块和进度条,其信息 TBM_ PBM_ 打头;

列表控件

任务管理器就是一个列表;

其重要的信息是 : LVM_INSERTCOLUMN ,插入索引;

其会用到一个结构:LVCOLUMN,其中标识了列表信息;

mask是掩码,说明了之后的成员有效性;

fmt是对齐方式,cx是大小,pszText为名字;

插入行: LVM_INSERTITEM

设置行: LVM_SETITEMTEXT

其结构和上面类似,叫 LVITEM;

item是第几行,subitem是第几列;

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
BOOL InsertColum(HWND hwnd, int id, int nColum, int cx, LPWSTR name)
{
LVCOLUMNW lvColumn = { 0 };
lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
lvColumn.fmt = LVCFMT_CENTER;
lvColumn.cx = cx;
lvColumn.pszText = name;
SendDlgItemMessageW(hwnd, id, LVM_INSERTCOLUMNW, nColum, (LPARAM) & lvColumn);
return TRUE;
}

BOOL InsertItem(HWND hwnd, int id, int item)
{
LVITEMW lvItem = { 0 };
lvItem.mask = LVIF_TEXT;
lvItem.iItem = item;
lvItem.pszText = (LPWSTR)L"";
SendDlgItemMessageW(hwnd, id, LVM_INSERTITEMW, 0, (LPARAM)&lvItem);
return TRUE;
}

BOOL SetListItemText(HWND hwnd, int id, int item, int subItem, LPWSTR name)
{
LVITEMW lvItem = { 0 };
lvItem.mask = LVIF_TEXT;
lvItem.iItem = item;
lvItem.iSubItem = subItem;
lvItem.pszText = name;
SendDlgItemMessageW(hwnd, id, LVM_SETITEMTEXTW, item, (LPARAM)&lvItem);
return TRUE;
}

添加样式:

1
sendDlgItemMessageW(hwnd, id, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, style)

常用style有:LVS_EX_FULLROWSELECT , 全行选中;

​ LVS_EX_GRIDLINES , 添加网格;

点击其成员触发notify信息,其有 NMHDR 结构:

1
2
3
4
5
typedef struct _nmhdr {
HWND hwndFrom; //发起信息句柄
UINT idFrom; //id
UINT code; //操作码
} NMHDR;

如果确认控件是list,则结构体为 NMLISTVIEW ,为 NMHDR 的继承;

捕获点击消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
case WM_NOTIFY:
{
NMHDR* pnmHeader = (NMHDR*)lParam;
switch(pnmHeader->code)
{
case NM_CLICK:
{
...
break;
}
}
break;
}

这种捕获类似于下拉表;

获取dll文件函数:

1
2
3
HMODULE hModule = LoadLibraryW(L"./mydll.dll");
GetProcAddress(hModule, "func_name"); //返回一个函数指针
FreeLibrary(hModule);

Lab

搓了个CPP的类粘合着窗口化编程使用做了个小程序,提取码 a333 ;

原神伤害云计算