Win32概述

一、Win32概述

新建Win32项目

新建项目,Win32项目,空项目,不勾选安全开发周期

右键解决方案名称,新建项,C++文件

Windows四大模块程序

控制台程序,窗口程序,动态库(dll), 静态库(lib)

Win32入口函数

windows是基于C,C++,又想有自己的数据类型,所以重定义,来区别C语言,或者达到顾名思义的效果

(HINSTANCE,LPSTR)

特性:

  1. 如果不认识,先转到定义看这个类型
  2. 大写H开头的叫句柄,(HINSTANCE)
  3. 大写P,LP开头,表示指针
  4. 封装信息:HINSTANCE 就是void*,看不到具体类型
1
2
3
4
5
6
7
8
9
10
11
#include <windows.h>

int WinMain(
HINSTANCE hInstance, //应用程序实例句柄
HINSTANCE hPrevInstance, //父应用程序实例句柄(没什么用)
LPSTR lpCmdLine, // 命令行参数
int nCmdShow //窗口显示方式
) {
MessageBox(0, "Hello", 0, 0); //字符集未设置
return 0;
}
MessageBox

MessageBox(窗口句柄,"文本","标题",类别);

1
MessageBox(nullptr, "Hello", "class1",MB_YESNOCANCEL | MB_ICONWARNING);
  1. MB_OK 确定按钮
  2. MB_YESNOCANCEL 是,否,取消
1
2
3
if (IDYES == MessageBox(0, ptc, 0, MB_YESNO)) { //返回值
//...
}

字符集

项目菜单->最后的属性->配置属性->常规->项目默认值->设置字符集

Unicode 宽字节字符集

1
2
3
4
5
char c = 'a'; //多字节字符
char *pc = "abcd"; //多字节字符串

wchar_t wc = 'a'; //宽字节字符
wchar_t *pwc = L"abcd" //宽字节字符串

为了方便,映入TCHAR来调整,多字节和宽字节

1
2
3
4
5
6
7
#include <tchar.h>

TCHAR tc = 'a';// 会受到字符集的影响,如果是多字节就是char,如果是宽字节就是wchar_t
TCHAR *ptc = &tc;
ptc = _T("abcd"); // 通配字符集的字符串赋值

_tcslen(ptc); // 求长度
MessageBox的类型

如果有函数后缀上有大写的A或W,说明有参数是多字节或宽字节

1
2
3
MessageBox(0, ptc, 0, 0);
MessageBoxA(0, "abc", 0, 0); // 多字节
MessageBoxW(0, L"abc", 0, 0); // 宽字节
Windows学习重点
  1. 搞清楚函数是干嘛的
  2. 参数是什么意思

wav格式的音乐播放

PlaySound函数

1
2
3
4
5
6
#pragma comment (lib,"Winmm.lib")

PlaySound(_T("2.wav"),
nullptr,
SND_LOOP | SND_FILENAME | SND_ASYNC);
// 循环 指定文件名 异步

mp3格式音乐播放

mciSendString

1
2
3
4
mciSendString(_T("open 长安忆—双笙翻唱.mp3 alias abc"), //命令字符串 alias 取别名
nullptr, //接收消息缓冲区,null表示不接受信息
0, //第二个参数的内存大小
nullptr); // 回调函数窗口句柄

二、Win32框架

打开项目->不勾选空项目,默认打开。

CALLBACK 没有实际代码含义,用作解释

1
2
3
4
5
6
7
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);

MSG msg; //消息结构变量

//系统函数:加载字符串
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_CLASS02, szWindowClass, MAX_LOADSTRING);

试图-->资源视图

可以看窗口,可视化编程

注册窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex; //表示一个窗口类信息的结构变量,后缀EX是扩展
// EX后缀表示扩展
wcex.cbSize = sizeof(WNDCLASSEX);

wcex.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; //类风格 水平刷新|垂直刷新|相应双击
wcex.lpfnWndProc = WndProc; // 该窗口用于处理窗口消息的回调函数
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CLASS02)); //加载图标 MAKEINTERSOURCE把数字转字符串
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); //加载光标
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); // 背景颜色
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_CLASS02);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); // 小图标

return RegisterClassEx(&wcex); //返回系统的注册类信息的调用结果
}

应用程序初始化

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

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd; // 窗口句柄

hInst = hInstance; // 将实例句柄存储在全局变量中

// 窗口类名,要和注册时候一致
hWnd = CreateWindow(szWindowClass,
szTitle, //窗口名
WS_OVERLAPPEDWINDOW,//窗口显示风格
CW_USEDEFAULT, 0, //窗口的起始坐标xy,如果x用了宏,那y轴0表示根据默认调整
CW_USEDEFAULT, 0, //窗口宽高,同上
NULL, //父窗口句柄
NULL, //窗口菜单句柄,null就是用注册时的菜单
hInstance, //窗口实例句柄
NULL); //保留参数

if (!hWnd)
{
return FALSE;
}

ShowWindow(hWnd, nCmdShow); // 显示窗口
UpdateWindow(hWnd); //更新窗口框架

return TRUE;
}

加载快捷键

1
hAccelTable = LoadAccelerators(hInstance,MAKEINTRESOURCE(IDC_CLASS02)); 

Win32框架:

  1. 入口函数
  2. 窗口注册类信息
  3. 窗口创建
  4. 窗口显示
  5. 窗口更新
  6. 消息循环
  7. 入口函数返回

三、消息框架

windows操作系统最大的特色是良好的用户交互性

1、产生消息 2、传递消息 3、处理消息

消息类MSG

1
2
3
4
5
6
7
8
9
10
11
typedef struct tagMSG {
HWND hwnd; // 窗口句柄
UINT message; //消息id
WPARAM wParam; //消息的辅助参数
LPARAM lParam; //消息的辅助参数
DWORD time; //消息产生的时间
POINT pt; //消息产生时鼠标的坐标
#ifdef _MAC
DWORD lPrivate;
#endif
} MSG;

windows操作系统为每一个正在运行的应用程序维护一个消息队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 主消息循环: 
while (GetMessage(&msg, NULL, 0, 0)) // 从消息队列里获取消息
{ //最后2个参数表示消息过滤,一般不过滤
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) //翻译快捷键
{
TranslateMessage(&msg);//翻译消息,主要做键盘的工作
DispatchMessage(&msg);//投递消息
}
}

ZeroMemory(&msg, sizeof(msg));
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) // 从消息队列复制一份里获取消息 从消息队列溢出消息
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) //翻译快捷键
{
TranslateMessage(&msg);//翻译消息,主要做键盘的工作
DispatchMessage(&msg);//投递消息
}
}
}

SendMessage(句柄,消息,0,0) 投递消息给窗口,可以理解为递归

PostMessage(句柄,消息,0,0) 投递消息到窗口的消息队列,可以理解为排队

1
2
SendMessage(hWnd, WM_KEYDOWN, 0, 0);
PostMessage(hWnd, WM_KEYDOWN, 0, 0);
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
switch (message)
{
case WM_COMMAND: // 命令消息,用于管理菜单
wmId = LOWORD(wParam);
wmEvent = HIWORD(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: //绘图消息
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY: //销毁消息
PostQuitMessage(0); // 投递退出消息
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}

其他消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
case WM_CREATE: // 创建窗口消息
break;
case WM_ACTIVATE: // 激活消息
switch (wParam)
{
case WA_CLICKACTIVE: // 鼠标单击激活
break;
case WA_ACTIVE: //非鼠标单击激活
break;
case WA_INACTIVE: // 非激活
break;
}
case WM_SYSCOMMAND: // 系统命令消息,包含关闭,最大化,最小化,还原。会拦截系统消息
switch (wParam)
{
case SC_CLOSE: //退出
break;
}
break;

四、重要消息

按键消息

1
2
3
4
5
6
7
8
9
10
11
12
case WM_KEYDOWN:
switch (wParam)
{
case VK_F1: //按下f1
MessageBox(0, _T("f1"), 00, 0);
break;
case VK_MENU: // alt键
break;
case 'A':
MessageBox(0, _T("A"), 0, 0);
break;
}

char消息要经过TranslateMessage(&msg); 来翻译消息

1
2
3
4
5
6
7
8
9
10
11
12
13
case WM_CHAR: //字符消息,由用户间接产生
switch (wParam)
{
case 'A':
MessageBox(0, _T("A"), 0, 0);
break;
case 'b':
MessageBox(0, _T("b"), 0, 0);
break;
default:
break;
}
break;

系统按键按下:

1、按下alt键或f10

2、alt加组合键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
case WM_SYSKEYDOWN: // 系统按键按下:
switch (wParam)
{
case VK_MENU:
MessageBox(0, _T("alt"), 0, 0); //会影响alt+a
break;
case VK_F10:
MessageBox(0, _T("f10"), 0, 0);
break;
case 'A':
MessageBox(0, _T("alt +A"), 0, 0);
break;
}
break;

注册热键还可以用RigisterHotKey()函数,在Create窗口的时候

鼠标消息

辅助消息里,鼠标相关的在lParam,其他的在wParam

1
2
3
4
5
6
7
8
9
10
11
12
13
case WM_LBUTTONDOWN:
short x = LOWORD(lParam); //获取低位
short y = HIWORD(lParam); //获取高位
break;
case WM_LBUTTONUP:
break;
case WM_LBUTTONDBLCLK:
break;
case WM_MOUSEMOVE: //鼠标移动
break;
case WM_MOUSEWHEEL:// 鼠标滚轮滚动, 信息在wParam;
short b = HIWORD(wParam); //朝前是120 朝后滚是-120;
break;

计时器消息

1、设置计时器

2、响应计时器

3、销毁计时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
case WM_CREATE:
// 设置计时器
SetTimer(hWnd, 1001, 1000, nullptr);
//第三个参数是计时时间间隔,以毫秒为单位
//第四个参数是计时器处理函数
SetTimer(hWnd, 1002, 1000, (TIMERPROC)aaa);
break;
case WM_TIMER: // 响应计时器
switch (wParam)
{
case 1001: //匹配id号
KillTimer(hWnd,1001); //销毁计时器
break;
}
break;

五、菜单

从编程的角度来分类:静态菜单,动态菜单,快捷菜单

静态菜单:在菜单资源编辑器中预先编辑好的

动态菜单:在程序运行的过程中通过代码生成

快捷菜单:是前两种菜单的组合,预先编辑好,然后在运行时动态显示

静态菜单

对于菜单而言:可以理解为二位数组

菜单里面的每一个元素都是一个菜单项,菜单项包含两个最基本的要素

1、菜单项名字

2、菜单项的唯一id号

每一个元素还可以是嵌套的子菜单数组

1
2
3
4
5
6
7
8
9
case WM_COMMAND: // 命令消息,用于管理菜单
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case ID_my1: //匹配对应菜单项的id名字
MessageBox(0, 0, 0, 0);
break;

设置快捷键

在取名的时候(&C)可以设置快捷键

或者在资源视图里,Accelerator文件夹的IDC_..的那个文件里进行设置

菜单分隔符可以在取名的时候输入-生成

添加新菜单

右键Menu文件夹,添加菜单就好了

然后是加载菜单

1
2
wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_CLASS02);
//wcex.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);

或者在创建窗口的时候改

1
2
3
4
5
6
7
8
9
10
11
12
13
HMENU h1 = LoadMenu(hInstance, MAKEINTRESOURCE(IDC_CLASS02));

// 窗口类名,要和注册时候一致
hWnd = CreateWindow(szWindowClass,
szTitle, //窗口名
WS_OVERLAPPEDWINDOW,//窗口显示风格
CW_USEDEFAULT, 0, //窗口的起始坐标xy,如果x用了宏,那y轴0表示根据默认调整
CW_USEDEFAULT, 0, //窗口宽高,同上
NULL, //父窗口句柄
h1, //窗口菜单句柄,null就是用注册时的菜单
hInstance, //窗口实例句柄
NULL); //保留参数

动态菜单

下拉菜单,动态菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 HMENU h_1 = CreateMenu();
AppendMenu(h_1, 0, 10087, _T("新建(&N)\t Ctrl+N"));
AppendMenu(h_1, MF_SEPARATOR, 10080, _T("-")); //分隔符
AppendMenu(h_1, 0, 10088, _T("打开(&O)\t Ctrl+O"));
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)h_1, _T("文件(&F)"));

HMENU h_2 = CreateMenu();
//第二个参数会受到第三个参数的影响(MF_BYCOMMOND,MF_BYPOSITION)
//MF_BYPOSITION 则是ID号
InsertMenu(h_2, 0, MF_BYPOSITION, 10089, _T("a"));
InsertMenu(h_2, 0, MF_BYPOSITION, 10090, _T("b"));
InsertMenu(h_2, 10089, MF_BYCOMMAND, 10091, _T("c")); // 插入到10089前面
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)h_2, _T("aaa"));

EnableMenuItem(h_1, 10087, MF_GRAYED); //变灰
EnableMenuItem(hMenu, 10087, MF_ENABLED); //使激活
EnableMenuItem(h_1, 0, MF_DISABLED | MF_BYPOSITION); //第0个位置

DeleteMenu(h_1, 0, MF_BYPOSITION); // 习惯在菜单不使用时让其非激活,不删除
//删除了之后快捷方式还可以打开

在createwindow下面加的话,要鼠标移上去才会刷新

1
2
3
4
5
6
7
8
9
10
ShowWindow(hWnd, nCmdShow);  // 显示窗口
UpdateWindow(hWnd); //更新窗口框架

HMENU h_3 = CreateMenu();
InsertMenu(h_3, 0, MF_BYPOSITION, 10189, _T("1a"));
InsertMenu(h_3, 0, MF_BYPOSITION, 10190, _T("1b"));
InsertMenu(h_3, 10189, MF_BYCOMMAND, 10191, _T("1c")); // 插入到10089前面
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)h_3, _T("1aaa"));

SetMenu(hWnd, hMenu); //更新菜单 最后要加这句才可以立即刷新

快捷菜单

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
//全局变量
HMENU g_hRMenu;

//在WinProc里面
case WM_CREATE:
g_hRMenu = LoadMenu(hInst, MAKEINTRESOURCE(IDR_MENU2));
break;
case WM_RBUTTONDOWN:
{
POINT pt;
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);

//把客户区坐标转换为屏幕坐标
ClientToScreen(hWnd, &pt);

HMENU tempMenu = GetSubMenu(g_hRMenu, 0); //把0列菜单给到tempMenu

TrackPopupMenu(tempMenu, //哪一个菜单
TPM_LEFTALIGN|TPM_TOPALIGN, //对其对齐,TPM_CENTERALIGN在中间
//TPM_LEFTALIGN|TPM_TOPALIGN 左上对齐
pt.x, pt.y, //哪个坐标开始显示
0,
hWnd, //窗口句柄
nullptr);
}
break;

GDI图形设备接口

实现了图形硬件和应用程序的分离

简化了开发人员的难度

GDI可以操作的图形对象

1、画点和画线

2、填充区域

3、文字

4、位图

在窗口绘制一个图形的步骤

1、得到设备环境句柄

2、修改设备属性

3、绘图

4、释放设备环境句柄

在windows里得到设备环境句柄的两种方法:

1、在WM_PAINT里去得到设备环境句柄

1
2
3
4
5
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
EndPaint(hWnd, &ps);
break;

响应WM_PAINT有两个情况:1、窗口初创建 2、窗口出现无效区

窗口出现无效区的几种情况:1)窗口移动或大小改变

​ 2)窗口隐藏后重新可见,或被其它窗口遮挡后重新可见

​ 3)调用InvalidateRect或InvalidateRgn函数,使出现无效区

​ 4)调用ScrollDC或者ScrollWindow函数,滚动客户区

2、在WM_PAINT消息外得到设备环境句柄

GetDC得到设备句柄, ReleaseDC释放设备环境句柄

1
2
3
4
5
case WM_LBUTTONDOWN:
hdc = GetDC(hWnd);
TextOut(hdc, 0, 0, _T("123"), 3);
ReleaseDC(hWnd, hdc);
break;

实例:绘图软件

1、画笔

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
//全局变量
int posX = 0, posY = 0;
bool isMove = false;

//WndProc里面
case WM_MOUSEMOVE:
if (isMove) {
int x = LOWORD(lParam);
int y = HIWORD(lParam);
hdc = GetDC(hWnd); // 得到设备环境句柄
MoveToEx(hdc, posX, posY, nullptr);
//移动到xy位置,最后个nullptr表示不保存之前移动的旧位置
LineTo(hdc, x, y); //画线到xy位置
ReleaseDC(hWnd, hdc);
posX = x;
posY = y;
}
break;
case WM_LBUTTONUP:
isMove = false;
break;
case WM_LBUTTONDOWN:
isMove = true;
posX = LOWORD(lParam);
posY = HIWORD(lParam);
break;

GDI里面有一些预设的设备属性,预设了画笔,画刷,文字

如果要修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
case WM_LBUTTONDOWN:{
hdc = GetDC(hWnd); //第1步

HPEN hpen = nullptr;
COLORREF c = RGB(255, 0, 0);
hpen = CreatePen(PS_SOLID, // 画笔风格 ,PS_DASHDOTDOT 双点划线
2, // 画笔宽度,以像素为单位,如果大于1,都是实线风格
c); //画笔颜色
SelectObject(hdc, hpen); //第2步
//为hdc选择一个hpen的gdi对象,注意选择完后要加释放

MoveToEx(hdc, 0, 0, nullptr); //第3步
LineTo(
hdc, 100, 100);
DeleteObject(hpen); //第2步结束
ReleaseDC(hWnd, hdc); //第4步
}
break;

画圆弧

1
2
3
4
5
6
7
8

hdc = GetDC(hWnd);
//画圆弧,(逆时针)
Arc(hdc,
100, 100,400, 400, // 外接矩形的起点终点
0, 0, 250, 400); //圆弧的起点和终点,不一定在圆上,做连线和圆交点
//如果在圆心就是坐标x轴正方向的与圆交点
ReleaseDC(hWnd, hdc);

画点

1
2
3
4
5
6
7
8
9
10
hdc = GetDC(hWnd);
for (int i = 1; i <= 100; ++i) {
SetPixel(hdc, i, 100, RGB(255, 0, 0)); // 在x,y坐标画点
}

hdc = GetDC(hWnd);
for (int i = 1; i <= 100; ++i) {
SetPixel(hdc, i, 200, GetPixel(hdc,i,100)); // 在x,y坐标画点
} // GetPixel 拿到像素点的颜色

画彩色的线

在同一时刻,GDI只允许同一属性的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
hdc = GetDC(hWnd);

HPEN hp = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
SelectObject(hdc, hp);
MoveToEx(hdc, 100, 100, nullptr);
LineTo(hdc, 150, 100);
DeleteObject(hp);

hp = CreatePen(PS_SOLID, 2, RGB(0, 255, 0));
SelectObject(hdc, hp);
MoveToEx(hdc, 150, 100, nullptr);
LineTo(hdc, 200, 100);
DeleteObject(hp);

ReleaseDC(hWnd, hdc);

2、画刷

系统默认的画笔是黑色,画刷是白色。边框色是用画笔的颜色描边

1
2
3
4
5
6
7
8
9
10
11
hdc = GetDC(hWnd);
Rectangle(hdc,100,100,200,200); //画矩形
ReleaseDC(hWnd, hdc);

HBRUSH hr = CreateSolidBrush(RGB(255, 0, 0)); //纯色画刷
HPEN hp = CreatePen(PS_SOLID,1, RGB(255, 0, 0));
SelectObject(hdc, hp);
SelectObject(hdc, hr);
Rectangle(hdc,100,100,200,200); //画矩形
DeleteObject(hr);
DeleteObject(hp);

填充矩形区域, 也可以实现无边框

1
2
3
4
5
6
7
8
hdc = GetDC(hWnd);
RECT r = { 100, 100, 400, 400 };
HBRUSH hr = CreateSolidBrush(RGB(255, 0, 0));
FillRect(hdc, &r, hr); //填充矩形区域
ReleaseDC(hWnd, hdc);


HBRUSH hr = CreateHatchBrush(HS_BDIAGONAL, RGB(255, 0, 0)); //阴影线填充