Win32 GUI 编程 1.1 intro
前置知识
- 了解什么是回调函数
- 了解句柄概念
窗口创建的过程
- 定义 WinMain 函数
- 定义窗口处理函数(自定义,消息处理回调)
- 向操作系统注册窗口类(向操作系统写入数据)
- 创建窗口(内存中创建窗口)
- 显示窗口(绘制窗口)
- 消息循环(获取、翻译、分发消息)
- 消息处理
1 |
|
Win32 GUI 编程 1.2 窗口
窗口类是什么
窗口类包含了窗口的各种参数信息的一种结构体。
1
2
3
4
5
6
7
8
9
10
11
12//新建窗口类,或者说是新建一个窗口,开始设定窗口的属性
WNDCLASS mainWindow = { 0 };
mainWindow.cbClsExtra = MYCLASSBUFFER;//给窗口设定 class 缓冲区
mainWindow.cbWndExtra = MYWINDOWBUFFER;//给窗口设定 window 缓冲区
mainWindow.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); //给窗口设定颜色,这里是白色
mainWindow.hCursor = NULL; //光标(Cursor)进入窗体时,使用默认光标,你也可以传进来一个图片
mainWindow.hIcon = NULL; //窗体的Icon使用默认Icon,你也可以传进来一个图片
mainWindow.hInstance = hIns;//窗口的程序句柄
mainWindow.lpfnWndProc = WndProc;//窗口处理函数,以便于回调
mainWindow.lpszClassName = TEXT("Main");//窗口的类的名字
mainWindow.lpszMenuName = NULL; //窗口的菜单的名字,NULL表示没有
mainWindow.style = CS_HREDRAW | CS_VREDRAW; //风格,为什么是常量通过与运算来确定,你点进常量里面看一看就懂了(Hint:二进制)。具体有哪些属性可以查微软API每个窗口都有窗口类,每个窗口基于自己的窗口类创建窗口。
每一个窗口类都有一个名称,使用前必须注册到系统。
1
RegisterClass( &mainWindow );
窗口类分类
系统窗口类
系统已经定义好的窗口类,应用程序拿来就用
应用程序全局窗口类
由用户自己定义,当前应用程序可用
应用程序局部窗口类
由用户自己定义,当前应用程序中本模块可以使用
系统窗口类
example:
按钮 - BUTTON
编辑框 - EDIT
1 |
|
全局窗口类、局部窗口类
注册窗口类的函数原型:
1 | ATOM RegisterClass(CONST WNDCLASS *lpWndClass); //传入窗口类 |
补充:ATOM是原子操作的意思,意味着当程序执行到一个ATOM类型的函数时不可以被操作系统打断,必须一次性执行完毕。
1 | typedef struct WNDCLASS { |
窗口类创建
- CreateWindow / CreateWindowEx
1 | HWND CreateWindowEx( |
这个函数干了什么:你可以认为这个函数疯狂的 malloc 了一大堆信息
关于 dwStyle:https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
CreateWindow 执行过程:
1、函数根据传入的名称,在应用程序局部窗口类中查找,如果找到执行2,未找到执行3.
2、比较局部窗口类与创建窗口时传入的hInstance变量。如果相等,创建和注册的窗口类在同一模块,创建窗口返回。如果不相同,执行3。
3、在应用程序全局窗口类查找,如果找到,执行4,未找到执行5。
4、使用找到的窗口类信息,创建窗口并返回。
5、在系统窗口类中查找,如果找到,创建窗口并返回;否则执行失败。
创建子窗口:
- 创建时,设置父窗口句柄
- 创建时,风格增加:WS_CHILD|WS_VISIBLE
1 |
|
Win32 GUI 编程 1.3 消息
消息的概念与作用
消息的组成:
- 窗口句柄
- 消息id
- 消息的两个参数
- 消息产生时间
- 消息产生时鼠标位置
消息的作用
- 当系统通知窗口工作时,就采用消息的方式派发给窗口
1 | typedef struct tagMSG { |
Dispatch 分发机制
1 | DispatchMessage(&nMsg) { |
- 窗口处理函数(window Procedure Function)的原型(Prototype)
- 只有满足这几个参数,才能称之为窗口处理函数
1 | LRESULT CALLBACK WindowProc( |
窗口处理函数实例:
1 | LRESULT CALLBACK mainWindowProc( |
常见消息
WM_DESTORY
产生时间:窗口被销毁时
附带信息:
- wParam:0
- lParam:0
一般用法:常用于在窗口被销毁之前,做相应的处理(free…)
WM_SYSCOMMAND
- 产生时间:窗口系统区域发生事件时
- 附带信息:
- wParam:点击发生事件,例如:SC_CLOSE
- lParam:鼠标光标的位置。
- LOWORD(lParam) 水平位置
- HIWORD(lParam) 垂直位置
WM_CREATE
产生时间:在窗口创建成功但是还未显示时。
附带信息:
- wParam:为0
- lParam:为 CREATESTRUCT 类型的指针。通过这个指针可以反射CreateWindowEx的12个参数。
一般用法:常用于初始化
WM_SIZE
产生时间:在窗口的大小发生变化后。
附带信息:
- wParam:窗口大小发生变化的原因
- lParam:窗口变化后的大小
- LOWORD(lParam) 变化后的宽度
- HIWORD(lParam)变化后的高度
利用调试技巧:新增Console
1 | HANDLE g_hOutput = 0; //作为static var |
WM_QUIT
- 产生时间:PostQuitMessage( integer );
- 附带信息:
- wParam:PostQuitMessage函数传递的参数
- lParam:0
- 一般用法:用于结束消息循环,当GetMessage收到这个消息后,退出消息循环。
WM_PAINT
消息处理步骤
开始绘图:
1
2
3
4HDC BeginPaint(
HWND hwnd,//绘图所在窗口
LPPAINTSTRUCT lpPaint //绘图参数缓存
);//返回绘图设备句柄HDC正式绘图
结束绘图
1
2
3
4BOOL EndPaint(
HWND hWnd,
CONST PAINTSTRUCT *lpPaint //free pointer
);
消息循环
消息循环的阻塞
- PeekMessage - 以查看的方式从系统获取消息,可以不将消息从系统移除,非阻塞函数。当系统无消息时,返回false,继续执行后续代码。
1 | BOOL PeekMessage( |
- GetMessage - 阻塞函数
1 | while(1) { |
发送消息(emit)
- SendMessage - 发送消息,并阻塞等候消息处理的结果。
- PostMessage - 投递消息,消息发出后立即返回,不等候结果。
1 | BOOL SendMessage( |
- 系统消息 - ID范围 0-0x03ff
- 用户自定义消息 - ID范围 0x0400 - 0x7FFF
1 |
SendMessage :直接内部调用窗口处理函数,不走消息循环
PostMessage:将消息加入消息队列。
键盘消息
键盘按下
- WM_KEYDOWN - 按键被按下时产生
- WM_KETUP - 按键被放开时产生
- WM_SYSKEYDOWN - 系统键被按下时产生(如ALT、F10)
- WM_SYSKEYUP - 系统键放开时产生
附带信息:
- WPARAM - 按键的 Virtual Key
- LPARAM - 按键的参数,例如按下了几次
虚拟键码(Virtual Key)不等于输入内容
字符消息
- TranslateMessage 在转换 WM_KEYDOWN 消息时,对于可见字符产生一个WM_CHAR的事件,如果按下不可见字符则无此消息
- 附带信息:
- WPARAM - 输入的字符的 ASCII 字符编码
- LPARAM - 输入的按键的相关参数
1 | void onKeyDown(HWND hWnd, WPARAM wParam) { |
鼠标消息
基本鼠标消息:
WM_LBUTTONDOWN - 鼠标左键按下
WM_LBUTTONUP - 鼠标左键抬起
WM_RBUTTONDOWN - 鼠标右键按下
WM_RBUTTONUP - 鼠标右键抬起
WM_MOUSEMOVE - 鼠标移动消息
双击消息
WM_LBUTTONDBLCLK - 鼠标左键双击
WM_RBUTTONDBLCLK - 鼠标右键双击
滚轮消息
WM_MOUSEWHEEL - 鼠标滚轮消息
附带消息:
wParam :其他button的状态,例如ctrl/shift
lParam:鼠标的位置
定时器
定义
产生时间:
在程序中创建定时器,当到达时间间隔时,定时器会向程序发送信息。定时器的精度是ms,但是准确度很低。
附带信息:
wParam:定时器ID
lParam:定时器处理函数的指针
1 | UINT_PTR SetTime( |
1 | BOOL KillTimer( |
1 | case WM_CREATE: |
Win32 GUI 编程 2.1 资源
资源
- 资源脚本文件:*.rc 文件
- 编译器:RC.exe
菜单
菜单类型
- 窗口的顶层菜单
- 弹出式菜单
- 系统菜单
HMEMU:菜单句柄
ID:菜单项
菜单的装载
添加菜单资源
visual studio有图形化界面
加载菜单资源
注册窗口类时设置菜单
1
mainWindow.lpszMenuName = (char*)IDR_MENU;
创建窗口传参设置菜单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18HMENU mainWindowMenu = LoadMenu(
hIns, //current thread handle
(LPCTSTR)IDR_MENU // resource identifier
);
HWND mainWindowHandle = CreateWindow(
TEXT("Main"), // 创建哪个窗口,这里显然我们想要创建刚刚注册的那个窗口,这里传入的类名要和前面的一样
TEXT("Hello World!"), // 窗口的标题
WS_OVERLAPPEDWINDOW, // 窗口的风格
100, //窗口的位置数据,x,y,width,height
100,
500,
500,
NULL, //这个窗口有没有父窗口(eg.QQ密码登陆错误,跳出来一个报错消息框,报错消息框是QQ登录窗口的子窗口) 没有就传NULL
mainWindowMenu, //这个窗口有没有菜单,没有传NULL,有传入菜单句柄
hIns, //这个窗口从属于哪个程序,传句柄
NULL //这个窗口有没有别的参数
);在主窗口 WM_CREATE 消息中利用 SetMenu 设置菜单
1
SetMenu(HWND hWnd,HMENU hMenu);
菜单事件监听
事件名称:WM_COMMAND
LOWORD(wParam):点击的菜单项的id
1 | void onCommand(HWND hWnd, WPARAM wParam) { |
图标资源
添加图标资源:visual studio有成型的方案
加载:
1
2
3
4HICON LoadIcon(
HINSTANCE hInstance,
LPCTSTR lpIconName
);设置:注册窗口类
字符串资源
添加字符串资源:添加字符串表
字符串资源引用:
1
2
3
4
5
6int LoadString(
HINSTANCE hInstance,
UINT uID, //字符串id
LPTSTR lpBuffer, //存放字符串的buffer char*
int nBufferMax //字符串buffer长度
);
Win32 GUI 编程 2.2 绘图
概念
绘图设备 - DC(Device Context) ,绘图上下文、绘图描述表
HDC - DC句柄,表示绘图设备
GDI - Windows graphics device interface (Win32 提供的绘图API)
颜色:
- R - red
- G - green
- B - blue
颜色的使用
COLORREF - 实际DWORD
例如:COLORREF nColor = 0;
赋值使用RGB宏
例如:nColor = RGB(0,0,225);
获取RGB值:
GetRValue….
BYTE nRed = GetRValue( nColor );
图形绘制
SetPixel 设置指定点的颜色
1
2
3
4
5
6COLORREF SetPixel(
HDC hdc,
int X,
int Y,
COLORREF crColor
);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
28void drawPit(HDC hdc) {
SetPixel(hdc, 100, 100, RGB(255, 0, 0));
}
void onFirstPaint(HWND hWnd) {
PAINTSTRUCT nowWindowPaint = { 0 };
HDC handlePaint = BeginPaint(hWnd, &nowWindowPaint);
drawPit(handlePaint);
}
//窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WndProc(
HWND hWnd,
UINT msgID,
WPARAM wParam,
LPARAM lParam
) {
switch (msgID) {
case WM_PAINT:
onFirstPaint(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, msgID, wParam, lParam);
}线的使用(Line,Curve)
MoveToEx - 指向窗口当前点
LineTo - 从窗口当前点到指定点绘制一条直线
当前点:上次绘图时的最后一点,初始为(0,0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15void drawLine(HDC hdc) {
MoveToEx(
hdc, // handle DC
100, // X
100, // Y
NULL // getLastPit
);
LineTo(hdc, 300, 300);
}
void onFirstPaint(HWND hWnd) {
PAINTSTRUCT nowWindowPaint = { 0 };
HDC handlePaint = BeginPaint(hWnd, &nowWindowPaint);
//drawPit(handlePaint);
drawLine(handlePaint);
}封闭图形:能够用画刷填充的图形
Rectangle,Eclipse
GDI绘图对象 - 画笔
画笔的作用
线的颜色、线的形状、线的粗细
HPEN - 画笔句柄
画笔的使用
1、创建画笔
1
2
3
4
5HPEN CreatePen(
int fnPenStyle, //画笔的样式
int nWidth, //画笔的粗细
COLORREF crColor // 颜色
); //创建成功返回句柄2、HDC 绑定 HPEN
1
2
3
4HGDIOBJ SelectObject(
HDC hdc, // 绘图设备句柄
HGDIOBJ hgdiobj //GDI绘图对象句柄 - 画笔句柄
); //返回原来的GDI绘图对象句柄 - 原来的画笔句柄HGDIOBJ - Handle of GDI object
GDI - Graphic Device Interface
3、绘图
4、取出画笔(用SelectObject,将原来的画笔还回去)
5、释放画笔
1
2
3BOOL DeleteObject(
HGDIOBJ hObject //GDI绘图对象句柄 - 画笔句柄
);
GDI绘图对象 - 画刷
画刷相关
画刷 - 封闭突匈的填充的颜色、pattern
HBRUSH - 画刷句柄
画刷的使用
1 创建画刷
- CreateSolidBrush - 创建实心画刷
- CreateHatchBrush - 创建纹理画刷
2 将画刷应用到DC中
- SelectObject
3 绘图
4 将画刷取出
- SelectObject
5 删除画刷
- DeleteObject
注意:Device Context既有刷子又有画笔
1 | void onFirstPaint(HWND hWnd) { |
可以用GetStockObject函数获取系统维护的画刷、画笔等。
如果不使用画刷填充,余姚使用NULL_BRUSH参数,获取不填充的画刷。
GetStockObject返回的画刷不需要DeleteObject。
位图
位图绘制
位图概念
光栅图形 - 保存图像中每一个颜色的信息(位图)
矢量图形 - 记录图像算法
HBITMAP - 位图句柄
位图的使用
1 在资源中添加位图
2 从资源中加载位图LoadBitmap
3 创建一个与当前DC相匹配的DC(Memory_DC)
1
2
3HDC CreateCompatibleDC(
HDC hdc //当前DC句柄,可以为NULL(使用屏幕DC)
);//返回创建好的DC注意:当前DC是直接在屏幕上画图,CreateCompatibleDC创建出来的DC是在内存里面画图
4 将位图放入匹配的DC中 SelectObject
5 成像(1:1)
1
2
3
4
5
6
7
8
9
10
11BOOL bitBlt(
HDC hdcDest, //Destination
int nXDest,
int nYDest,
int nWidth,
int nHeight,
HDC hdcSrc, //Source
int nXSrc,
int nYSrc,
DWORD dwRop // 成像方法 如SRCCOPY
);6 取出位图
7 释放位图、Memory_DC
缩放位图:StretchBlt
Win32 GUI 编程 3.1 常用系统窗口类(标准控件)
完整文档,请访问:
https://docs.microsoft.com/en-us/windows/win32/controls/window-controls
提示:找到左边的 Control Library,点进去。
按钮
基本属性
窗口类标签:Button
创建一个按钮:
1 | HWND hwndButton = CreateWindow( |
对于子窗口,HMENU 的值可以被父窗口调用。
事件的监听
按钮向消息队列中发送 Notification ,收信的是父窗口。
父窗口监听 WM_COMMAND 事件,以上面代码生成的按钮为例子,此按钮被点击后,生成 WM_COMMAND事件发送给父窗口;
LOWORD(wParam) = 1001 (对于子组件,hmenu的值作为id让父窗口区分)
HIWORD(wParam) = BN_CLICKED
1 | switch (msgID) { |
复选框
基本属性
窗口类标签:ComboBox
创建一个复选框:
1 | HWND hWndComboBox = CreateWindow( |
添加选择项
ComboBox可以通过:(二选一)
- SendMessage 发送CB_ADDSTRING 消息
- 调用 ComboBox_AddString 函数(需要 #include<windowsx.h> )
这两种方式添加可选择项
获得目前被选择项
1 | int selectionComboBox = SendMessage(g_hWndComboBox, CB_GETCURSEL, 0, 0); |
SendMessage:CB_GETCURSEL。
返回的是目前被选中的项目的数字编号(第一个被选中返回0,第二个被选中返回1,以此类推)
实例
1 |
|