Windows窗口程序
应用程序分类
-
控制台程序Console
DOS程序,没有窗口,通过DOS窗口执行
入口函数: main
-
窗口程序
拥有自己的窗口,可以与用户交互
入口函数: WinMain
-
库程序
存放代码、数据的程序,执行文件可以从中取出代码执行或获取数据
-
静态库程序:扩展名LIB, 在编译链接程序时,将代码放入到执行文件中
静态库没有入口函数 --> 没法执行 --> 没法进入内存
-
动态库程序:扩展名DLL,在文件执行时从中获取代码
动态库有入口函数–>可以执行,但是不能独立执行( 必须依附其他程序 )
入口函数: DLLMain
-
开发工具和类
开发工具
-
编译器
CL.EXE 将源代码编译成目标文件.obj
-
链接器
LINK.EXE 将目标代码,库链接生成最终文件
-
资源编译器
RC.EXE (.rc) 将资源编译,最终通过链接器存入最终文件
Visual Studio 路径: C:\Projram Files(x86)\Microsoft Visual Studio xx\vc\bin
类
Windows库
-
kernel32.dll
提供核心的API, 例如进程,线程,内存管理等
-
user32.dll
提供了窗口,消息等API
-
gdi32.dll
绘图相关的API
路径: C:\Windows\System32
头文件
-
windows.h
所有windows头文件的集合 --> 包含了其他头文件
-
windef.h
windows数据类型
-
winbase.h
kernel32的API
-
wingdi.h
gdi32的API
-
winuser.h
user32的API
-
winnt.h
UNICODE字符集的支持
相关函数
int WINAPI WinMain(
HINSTANCE hInstance,// 当前程序的实例句柄
HINSTANCE hPrevInstance, // 当前程序前一个实例句柄 --> 已经废弃
LPSTR lpCmdLine, // 命令行参数字符串 char* 类型 --> 只能传递一个命令行参数
int nCmdShow // 窗口的显示方式 最大化显示,最小化显示,原样显示
);
句柄可以找到进程对应的内存 --> 句柄是表的索引
int MessageBox(
HWND hWnd, // 父窗口句柄
LPCTSTR lpText, // 显示在消息框中的文字
LPCTSTR lpCaption, // 显示在标题栏中的文字
UINT uType // 消息框中的按钮,图标显示类型
); // 返回点击的按钮ID
Hxxx --> 大概率是句柄
rc资源文件
后缀: .rc
100 ICON small.ico
100 数字标识 ICON 图标资源 small.ico 文件名称
编译后称为 .res文件
.obj & .res 统称为目标文件
程序编译过程
Demo
#include <windows.h>
int WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine, int nCmdShow){
MessageBox(NULL,"hello world","Information",MB_YESNO);
return 0;
}
第一个windows窗口
步骤
- 定义WinMain函数
- 定义窗口处理函数(自定义,处理消息)
- 注册窗口类(向操作系统写入一些数据)
- 创建窗口(内存中创建窗口)
- 显示窗口(绘制窗口的图像)
- 消息循环(获取/翻译/派发消息)
- 消息处理
#include<windows.h>
// 窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){
return DefWindowProc(hWnd,msgID,wParam,lParam);
}
// 入口函数
int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPerIns,LPSTR lpCmdLine,int nCmdShow){
// 注册窗口类 --> 向系统的内核写入一些数据
WNDCLASS wc={0}; // 结构体变量
wc.cbClsExtra = 0; // 开缓冲区 --> n 字节的缓冲区
wc.cbWndExtra = 0; // 开缓冲区 另一种缓冲区
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); // 背景色
wc.hCursor = NULL; // 默认光标位置
wc.hIcon = NULL; // 默认图标
wc.hInstance = hIns; // 当前程序实例句柄
wc.lpfnWndProc = WndProc; // 窗口处理函数
wc.lpszClassName = "窗口类名字"; // 窗口类的名字
wc.lpszMenuName = NULL; // 没有菜单
wc.style = CS_HREDRAW | CS_VREDRAW ; // 水平或者垂直有变化,重画窗口
// 将上面复制写入操作系统
RegisterClass(&wc);
// 内存中创建窗口
HWND hWnd = CreateWindow("窗口类名字","标题",WS_OVERLAPPEDWINDOW,100,100,100,100,NULL,NULL,hIns,NULL);
//HWND hWnd = CreateWindow("窗口类名字","标题",WS_OVERLAPPEDWINDOW,x,y,w,h,父窗口,菜单,hIns,NULL);
// WS_OVERLAPPEDWINDOW最后一个参数窗口风格
// 父窗口,菜单没有就置位NULL
// 最后一个参数没用,置为NULL
// 显示窗口
ShowWindow(hWnd,SW_SHOW);
// 风格 SW_SHOW 原样显示
UpdateWindow(hWnd);
// 刷新 --> 再画一遍
// 消息循环
MSG nMsg = {0};
while(GetMessage(&nMsg,NULL,0,0)){
TranslateMessage(&nMsg); // 翻译消息
DispatchMessage(&nMsg) ; // 派发消息,交给窗口处理函数来处理
}
return 0;
}
字符编码
历史背景
-
ASC
7位代表一个字符
-
ASCII
8位一个字符
-
DBCS
double byte
单双字节混合编码
-
UNICODE
- linux 一般utf8
- windows 一般 utf16
宽字节字符
-
wchar_t 每个字符占2个字节
char每个字符占1个字节
wchar_t 实际上是 unsigned short 类型,定义时,需要在前面增加"L",通知编译器按照双字节编译字符串,采用UNICODE编译
-
需要使用支持wchar_t函数操作宽字节字符串
wchar_t * pwszText = L"Hello wchar"; wprintf(L"%s\n",pwszText);
-
demo
#include<windows.h> #include<stdio.h> int main(){ wchar_t * pszText = L"Hello wchar"; int len = wcslen(pszText); // 返回字符个数 wprintf(L"%d,%s",len,pszText); return 0; }
-
TCHAR 数据类型
定义在WINNT.h中:
#ifdef UNICODE typedef wchar_t TCHAR #define _TEXT(quote) L##quote #else typedef char TCHAR #define _TEXT(quote) quote #endif
## 是拼接的作用
TCHAR * pszText = _TEXT("Hello,wkk");
定义UNICODE 宏要在window.h头文件前面
-
UNICODE字符打印
wprintf 对UNICODE 字符打印支持不完善
在Windows使用WriteConsole API打印UNICODE 字符 GetStdHandle
//WriteConsole(标准输出句柄,输出的缓冲区,输出长度,实际输出的长度,备用参数); wchar_t *pszText = L"哇咔咔"; WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),pszText,wcslen(pszText),NULL,NULL); // GetStdHandle() 获取标准句柄 // STD_INPUT_HANDLE // STD_OUTPUT_HANDLE // STD_ERROR_HANDLE
-
项目属性设置为UNICODE 字符集
系统会自动增加UNICODE宏的定义
如果是TCHAR * 类型增加了UNICODE宏,字符串字面量前面要加L
-
系统调用函数的参数类型
LPSTR === char * LPCSTR === const char * LPWSTR === wchar_t * LPCWSTR === const wchar_t * LPTSTR === TCHAR * LPCTSTR === const TCHAR*
注册窗口类
窗口类
概念
- 窗口类包含了窗口的各种参数信息的数据结构
- 每个窗口都具有窗口类,基于窗口类创建窗口
- 每个窗口类都具有一个名称,使用前必须注册到系统
分类
-
系统窗口类
系统已经定义好的窗口类,所有应用程序都可以直接使用
-
应用程序全局窗口类
由用户自己定义,当前应用程序所有模块都可以使用
-
应用程序局部窗口类
由用户自己定义,当前应用程序中本模块可以使用
系统窗口类
不需要用户注册,直接使用即可,系统已经注册好了
- 按钮 - BUTTON
- 编辑框 - EDIT
#include<windows.h>
int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPerIns,LPSTR lpCmdLine,int nCmdShow){
HWND hWnd = CreateWindow("Button","按钮",WS_OVERLAPPEDWINDOW,100,100,100,100,NULL,NULL,hIns,NULL);
ShowWindow(hWnd,SW_SHOW);
UpdateWindow(hWnd);
MSG nMsg = {0};
while(GetMessage(&nMsg,NULL,0,0)){
TranslateMessage(&nMsg); // 翻译消息
DispatchMessage(&nMsg) ; // 派发消息,交给窗口处理函数来处理
}
return 0;
}
全局窗口类
ATOM RegisterClass(
CONST WNDCLASS * lpWndClass // 窗口类的数据
);
// 注册成功,返回一个数字标识 非0
// 失败,返回0
// 注册窗口类的结构体
typedef struct _WNDCLASS {
UINT style; // 窗口类的风格
WNDPROC lpfnWndProc; // 窗口处理函数
int cbClsExtra ; // 窗口类的附加数据buff大小
int cbWndExtra; // 窗口的附加数据buff大小
HINSTANCE hInstance; // 当前模块的实例了句柄
HICON hIcon; // 窗口图标句柄
HCURSOR hCursor; // 鼠标句柄
HBRUSH hbrBackground; // 绘制窗口背景的画刷句柄
LPCTSTR lpszMenuName; // 窗口菜单的资源ID字符串
LpCTSTR lpszClassName; // 窗口类的名称
}WNDCLASS,*PWNDCLASS;
-
style 窗口类风格
应用程序全局窗口类的注册,需要在窗口类的风格中增加CS_GLOBALCLASS.
WNDCLASS wce = {0}; wce.style = ... | CS_GLOBALCLASS;
应用程序局部窗口类,在注册窗口类时,不添加CS_GLOBALCLASS风格
CS_HREDRAW 当窗口水平变化时,窗口重新绘制
CS_VERDRAW 当窗口垂直变化时,窗口重新绘制
CS_DBLCLKS 允许窗口接收鼠标双击
CS_NOCLOSE 窗口没有关闭按钮
一般不建议使用全局窗口类
局部窗口类
在注册窗口类时,不添加CS_GLOBALCLASS风格
窗口创建
CreateWindow / CreateWindowEx --> 加强版
加强版增加了扩展风格dwExStyle参数
HWND CrateWindowEx(
DWORD dwExStyle , // 窗口的扩展风格
LPCTSTR lpClassName, // 已经注册的窗口类名称
LPCTSTR lpWindowName, // 窗口标题栏的名字
DWORD dwStyle, // 窗口的基本风格
int x, // 左上角水平坐标
int y, // 左上角垂直坐标
int nWidth,
int nHeight,
HWND hWndParent, // 窗口的父窗口句柄 --> 如果是子窗口要写这个参数
HMENU hMenu, // 窗口菜单句柄
HINSTANCE hInstance, // 应用程序实例句柄 --> WinMain 第一个参数
LPVOID lpParam // 窗口创建时附加参数 --> 一般给NULL
); // 创建成功返回窗口句柄
窗口基本风格
WS_BORDER 有黑色的边界线
WS_CAPTION 有标题栏
WS_CHILD 子窗口
WS_CHILDWINDOW 同上
WS_CLIPCHILDREN 裁剪窗口 --> 不规则窗口
WS_CLIPSIBLINGS
WS_DISABLED 禁用 --> 常用按钮控件
WS_DLGFRAME 对话框
WS_GROUP 分组
WS_HSCROLL 水平滚动条
WS_ICONIC 最初状态最小化状态
WS_MAXIMIZE 最大化状态
WS_MAXIMIZEBOX 最大化按钮
WS_MINIMIZE
WS_MINIMIZEBOX
WS_OVERLAPPED 交叠窗口 --> 标题栏+边框
WS_OVERLAPPEDWINDOW 基本都有 *********
WS_POPUP 弹出式对话框
WS_SIZEBOX 可以改变大小
WS_TABSTOP 支持tap键顺序
WS_SYSMENU 系统菜单
WS_TILED
WS_VISIBLE 可见的 --> 显式子窗口 主窗口 -> showWindow 函数
WS_VSCROLL 垂直滚动条
CreateWindowEx 内部实现
- 函数内部根据传入的窗口类名称,在应用程序局部窗口类中查找,如果找到执行2, 没有找到执行3
- 比较局部窗口类与创建窗口时传入的HINSTANCE变量,如果发现相等,创建和注册的窗口类在同一模块,创建窗口返回。如果不相等执行3
- 在应用程序全局窗口类,如果找到执行4,如果没有找到执行5
- 使用找到的窗口类的信息,创建窗口返回
- 在系统窗口类中查找,如果找到创建窗口返回,否则创建窗口失败
匹配查找窗口类
if(找到窗口类){
申请一大块内存,将窗口的数据信息存入这块内存
return 内存的句柄
}else{
return NULL;
}
基本demo
#include<windows.h>
// 窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){
switch(msgID){
case WM_DESTROY:
PostQuitMessage(0); // 可以使GetMessage 函数返回0
break;
}
return DefWindowProc(hWnd,msgID,wParam,lParam);
}
// 入口函数
int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPerIns,LPSTR lpCmdLine,int nCmdShow){
// 注册窗口类 --> 向系统的内核写入一些数据
WNDCLASS wc={0}; // 结构体变量
wc.cbClsExtra = 0; // 开缓冲区 --> n 字节的缓冲区
wc.cbWndExtra = 0; // 开缓冲区 另一种缓冲区
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); // 背景色
wc.hCursor = NULL; // 默认光标位置
wc.hIcon = NULL; // 默认图标
wc.hInstance = hIns; // 当前程序实例句柄
wc.lpfnWndProc = WndProc; // 窗口处理函数
wc.lpszClassName = "窗口类名字"; // 窗口类的名字
wc.lpszMenuName = NULL; // 没有菜单
wc.style = CS_HREDRAW | CS_VREDRAW ; // 水平或者垂直有变化,重画窗口
// 将上面复制写入操作系统
RegisterClass(&wc);
// 内存中创建窗口
HWND hWnd = CreateWindow("窗口类名字","标题",WS_OVERLAPPEDWINDOW,100,100,100,100,NULL,NULL,hIns,NULL);
//HWND hWnd = CreateWindow("窗口类名字","标题",WS_OVERLAPPEDWINDOW,x,y,w,h,父窗口,菜单,hIns,NULL);
// WS_OVERLAPPEDWINDOW最后一个参数窗口风格
// 父窗口,菜单没有就置位NULL
// 最后一个参数没用,置为NULL
// 显示窗口
ShowWindow(hWnd,SW_SHOW);
// 风格 SW_SHOW 原样显示
UpdateWindow(hWnd);
// 刷新 --> 再画一遍
// 消息循环
MSG nMsg = {0};
while(GetMessage(&nMsg,NULL,0,0)){
TranslateMessage(&nMsg); // 翻译消息
DispatchMessage(&nMsg) ; // 派发消息,交给窗口处理函数来处理
}
return 0;
}
创建子窗口
- 创建时要设置父窗口句柄
- 创建风格要增加WS_CHLID | WS_VISIBLE
WNDCLASS wc={0}; // 结构体变量
wc.cbClsExtra = 0; // 开缓冲区 --> n 字节的缓冲区
wc.cbWndExtra = 0; // 开缓冲区 另一种缓冲区
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); // 背景色
wc.hCursor = NULL; // 默认光标位置
wc.hIcon = NULL; // 默认图标
wc.hInstance = hIns; // 当前程序实例句柄
wc.lpfnWndProc = DefWindowProc; // 系统默认处理函数 **************
wc.lpszClassName = "Child"; // 窗口类的名字
wc.lpszMenuName = NULL; // 没有菜单
wc.style = CS_HREDRAW | CS_VREDRAW ; // 水平或者垂直有变化,重画窗口
// 将上面复制写入操作系统
RegisterClass(&wc);
HWND hChlid1 = CreateWindowEx(0,"Child","child1",WS_CHILD|WS_VISIBLE|WS_OVERLAPPEDWINDOW,0,0,200,200,
hWnd,NULL,hIns,NULL
);
HWND hChlid2 = CreateWindowEx(0,"Child","child2",WS_CHILD|WS_VISIBLE|WS_OVERLAPPEDWINDOW,200,0,200,200,
hWnd,NULL,hIns,NULL
);
显式窗口
ShowWindow(hWnd,SW_SHOW);
// 根据窗口句柄,找到内存,绘制窗口
UpdateWindow(hWnd);
消息基础
消息
-
消息组成(windows下)
- 窗口句柄
- 消息ID
- 消息的两个参数(两个附带信息)
- 消息的产生的时间
- 消息产生时的鼠标位置
typedef struct tagMSG{ HWND hwnd; UINT message; WPARAM wparam; LPARAM lParam; DWORD time; POINT pt; }MSG;
-
消息的作用
当系统通知窗口工作时,采用消息的方法派发给窗口的处理函数
每个窗口都有窗口处理函数
MSG nMsg = {0};
while(GetMessage(&nMsg,NULL,0,0)){
TranslateMessage(&nMsg); // 翻译消息
DispatchMessage(&nMsg) ; // 派发消息,交给窗口处理函数来处理
}
DispatchMessage函数
DispatchMessage(&nMsg){
nMsg.hwnd --> 保存窗口数据的内存 --> WndProc
WndProc(...){
// 处理消息
}
}
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam);
/**
* hWnd
* msgID
* wParam
* lParam
*/
消息的最后两个参数 消息的时间,消息时的鼠标位置没有传递
窗口处理函数
-
每个窗口都必须有窗口处理函数
LRESULT CALLBACK WindowProc( HWND hwnd,// 窗口句柄 UINT uMsg, // 消息ID WPARAM wParam, // 消息函数 LPARAM lParam // 消息参数 );
-
当系统通知窗口时,会调用窗口处理函数,同时将消息ID和消息参数传递给窗口处理函数。 在窗口处理函数中,不处理的消息,使用缺省窗口处理函数,DefWindowProc
-
DefWindowProc 给各种消息做默认处理
消息相关函数
-
GetMessage - 获取本进程的消息
BOOL GetMessage( LPMSG lpMsg,// 存放获取到的消息BUFF HWND hWnd, // 窗口句柄 --> 抓指明的句柄的消息 NULL--> 抓所有的 // 消息的范围 (0,0)本进程的消息都抓 UINT wMsgFilterMin, // 获取消息的最小ID UINT wMsgFilterMax // 获取消息的最大ID );
lpMsg 当获取到消息后,将消息的参数存放到MSG结构中
hWnd 获取到hWnd所指定窗口的消息
wMsgFilterMin 和 wMsgFilterMax 只能获取到由它们指定的消息范围内的消息,如果都为0,表示没有范围
返回值:
- 如果message 为 WM_QUIT返回0, 其余消息返回非0
-
TranslateMessage 翻译消息
将按键消息,翻译成字符消息 --> 可见字符按键
BOOL TranslateMessage( CONST MSG*lpMsg // 要翻译的消息地址 );
检查消息是否是按键的消息,如果不是按键消息,不做任何处理,继续执行
-
DispatchMessage 派发消息
LRESULT DispatchMessage( CONST MSG * lpmsg // 要派发的消息 );
将消息派发到该消息所属窗口的窗口处理函数上
常见消息
产生时间,附带的两个参数,一般用来做什么
-
WM_DESTROY
-
产生时间 窗口被销毁时
-
附带消息:
wParam: 0
lParam: 0
-
常用于在窗口被销毁之前,做相应的善后处理,例如:资源,内存等
-
-
WM_SYSCOMMAND
-
产生时间: 点击窗口的最大化,最小化,关闭等
-
附带消息
wParam: 具体点击的位置 例如关闭: SC_CLOSE
lParam: 鼠标光标的位置 LOWORD(lParam) 水平位置 HIWORD(lParam) 垂直位置
-
常用在窗口关闭时,提示用户处理
-
-
WM_CREATE
-
产生时间: 窗口创建成功,但还没显式时,CreateWindow 和 ShowWindow之间
-
附带信息
wParam 为0
lParam 为CREATESTRUCT 类型的指针,通过这个指针可以获取CreateWindowEx中的全部12个参数的信息
-
一般用于初始化窗口的参数、资源等等,包含创建子窗口
-
-
WM_SIZE
-
产生时间 在窗口的大小发生变化后( 第一次显示,从无到有也会触发 )
-
附带消息
wParam: 窗口大小变化的原因
lParam: 窗口变化后的大小
-
LOWORD(lParam) 变化后的宽度
-
HIWORD(lParam) 变化后的高度
-
-
常用于窗口大小变化后,调整窗口内各个部分的布局
-
-
WM_QUIT
-
产生时间: 用户发送
-
附带消息
wParam : PostQuitMessage 函数传递的参数
lParam : 0
-
用于结束消息循环,当GetMessage收到这个消息后,会返回FALSE结束while处理,退出消息循环
不需要手动处理
-
消息循环
消息循环的阻塞
-
GetMessage 从系统获取消息,将消息从系统移除,阻塞函数。当系统无消息时,会等候下一条消息
-
PeekMessage 以查看的方式从系统获取消息,可以不将消息从系统中移除,非阻塞函数。当系统没有消息时,返回FALSE。
BOOL PeekMessage( LPMSG lpMsg, // 消息信息 HWND hWnd, // 窗口句柄 UINT wMsgFilterMin, // first message UINT wMsgFilterMax, // last message UINT wRemoveMsg // 是否移除标识 PM_REMOVE / PM_NOREMOVE );
while(1){
if(PeekMessage(&nMsg,NULL,0,0,PM_NOREMOVE)){
// 有消息
if(GetMessage(&nMsg,NULL,0,0)){
TranslateMessage(&nMsg);
DispatchMessage(&nMsg);
}else{
break;
}
}else{
// 无消息
// 空闲处理
}
}
发送消息
-
SendMessage 发送消息,会等待消息处理的结果 ( 等到消息处理完 , 会阻塞)
-
PostMessage 投递消息,消息发出后立刻返回,不等待消息执行结果
将消息放到系统消息队列中
BOOL SendMessage / PostMessage (
HWND hWnd , // 消息发送的目的窗口
UINT Msg, // 消息ID
WPARAM wParam , // 消息参数
LPARAM lParam // 消息参数
);
消息分类
-
系统消息 ID范围 0-0x03ff (0-1024)
系统定义好的消息,可以在程序中直接使用
-
用户自定义消息 ID范围 0x0400 - 0x7fff ( 一共31743)
用户自定义,满足用户自己的需求,由用户自己发出消息,并相应处理
自定义消息宏: WM_USER
消息队列
概念
- 消息队列用于存放消息的队列
- 先进先出
- 所有窗口程序都具有消息队列
- 程序可以从队列中获取消息
分类
- 系统消息队列,由系统维护的消息队列。存放系统产生的消息,例如:鼠标,键盘等
- 程序消息队列 属于每一个应用程序(线程)的消息队列,由应用程序维护
消息和消息队列的关系
-
消息和消息队列的关系
- 产生消息时,会将消息存放到系统消息队列中
- 系统根据存放的消息,找到对应程序的消息队列
- 将消息投递到程序的消息队列中
-
根据消息和消息队列之间使用关系,消息分为两类
-
队列消息
消息的发送和获取,都是通过消息队列完成
-
非队列消息
消息的发送和获取,是直接调用消息的窗口处理完成
-
-
队列消息
消息发送后,首先放入队列,然后通过消息循环,从队列中获取
GetMessage 从消息队列获取消息
PostMessage 将消息投递到消息队列
常见队列消息: WM_PAINT, 键盘,鼠标,定时器
WM_QUIT 必须进队列,不然GetMessage的循环不会结束
-
非队列消息
消息发送时,首先查找消息接收窗口的窗口处理函数,直接调用函数,完成消息
SendMessage 直接将消息发送给窗口的处理函数,并等候处理结果
常见消息: WM_CREATE, WM_SIZE等
WM_CREATE 必须不能进队列 --> 此时窗口还没有显示
GetMessage详细解释
-
在程序消息队列中查找信息,如果队列有消息,检查消息是否满足指定条件( HWND, ID范围 ), 不满足条件就不会取出消息,否则取出返回
-
如果程序队列没有消息,向系统消息队列获取数据本程序的消息。如果系统队列的当前消息属于本程序,系统会将消息转发到程序消息队列中
-
如果系统消息队列也没有消息,检查当前进程的所有窗口的需要重新绘制的区域,如果发现有需要绘制的区域,产生WM_PAINt 消息,取得消息返回处理
-
如果没有重新绘制区域,检查定时器如果有到的定时器,产生WM_TIMER返回处理执行
-
如果没有到时的定时器,整理程序的资源,内存等等
-
GetMessage 会继续等候下一条消息。PeekMessage 返回 FALSE, 交出程序的控制权
-
GetMessage 如果获取到是WM_QUIT函数返回FALSE
WM_PAINT消息
- 产生时间: 当窗口需要绘制的时候( 并且系统没有其他消息时 )
- 附带信息: wParam : 0 lParam : 0
- 专职用法: 用于绘图
// paint demo
// 窗口无效区域: 需要重新绘制的区域
BOOL InvalidateRect(
HWND hWnd, // 窗口句柄
CONST RECT * lpRect, // 区域的矩形坐标, NULL -> 整个窗口
BOOL bErase // 重绘前是否先擦除 TRUE / FALSE
);
WM_LBUTTONDOWN 鼠标左键按下消息
绘图
步骤
-
开始绘图
HDC BeginPain( HWND hwnd , // 绘图窗口 LPPAINTSTRUCT lpPaint // 绘图参数的buff ); // 返回绘图设备句柄HDC
-
正式绘图
-
结束绘图
BOOL EndPaint( HWND hWnd, // 绘图窗口 CONST PAINTSTRUCT * lpPaint // 绘图参数的指针 );
PAINTSTRUCT ps = {0};
HDC hdc = BeginPaint(hWnd,&ps);
TextOut(hdc,100,100,"Hello,Wkk",strlen(Hello,Wkk));
EndPaint(hWnd,&ps);
// 以上绘图的代码,必须放在处理WM_PAINT消息中
键盘消息
键盘消息分类
- WM_KEYDOWN 按键按下产生
- WM_KEYUP 按键被放开时产生
// 附带信息
WPARAM 按键的Virtual Key --> 虚拟键码值不能区分大小写
LPARAM 按键的参数,例如按下次数 --> 不重要
- WM_SYSKEYDWON 系统键按下时产生 比如: ALT, F10
- WM_SYSKEYUP 系统键放开时产生
字符消息( WM_CHAR )
-
TranslateMessage 在转换WM_KEYDOWN消息时,对于可见字符会产生WM_CHAR,不可见字符无此消息。
TranslateMessage(&nMsg){ // 只翻译可见字符的消息 if(nMsg.message != WM_KEYDWON) return ; 根据nMsg.wParam键码值可以获知哪个按键被按下 if(不可见字符的按键) return ; 查看CapsLock是否处于打开状态 if(打开) PostMessage(nMsg.hwnd,WM_CHAR,大写字符,...); else PostMessage(nMsg.hwnd,WM_CHAR,小写字符,...); }
-
附带信息
WPARAM - 输入的字符的ASCII字符编码值 LPARAM - 按键的相关参数 --> 不重要
鼠标消息
鼠标消息分类
-
基本鼠标消息
WM_LBUTTONDOWN 鼠标左键按下
WM_LBUTTONUP 左键抬起
WM_RBUTTONDWON
WM_RBUTTONUP
WM_MOUSEMOVE 鼠标移动消息
-
附带信息
wPARAM: 其他按键的状态,例如: Ctrl / Shift等
鼠标左键:1, Ctrl: 8 ,Shift: 4
IPARAM: 鼠标的位置,窗口客户区坐标系
- LOWORD x坐标
- HIWORD y坐标
-
一般情况鼠标按下/ 抬起是成对出现的。
在鼠标移动过程中,会根据移动速度产生一系列的WM_MOUSEMOVE消息
移动慢产生消息多,移动快产生消息少
-
-
双击消息
WM_LBUTTONDBLCLK 鼠标右键双击
WM_RBUTTONDBLCLK 左键双击
-
附带消息
同基本鼠标消息
-
消息产生顺序
以左键双击为例:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBLCLK
WM_LBUTTONUP
使用时需要在注册窗口类的时候添加CS_DBLCLKS 风格, 加上才会出现双击事件
-
-
滚轮消息
WM_MOUSEWHEEL 鼠标滚轮消息
-
附带信息
wPARAM
-
LOWORD 其他按键的状态
-
HIWORD 滚轮的偏移量,通过正负值表示滚动方向
正:向前滚动, 负:向后滚动
IPARAM: 鼠标当前的位置,屏幕坐标系
- LOWORD x坐标
- HIWORD y坐标
-
-
使用
通过偏移量,获取滚动的方向和距离
一般的滚动偏移都是120的倍数
-
定时器消息
定时器消息介绍
-
产生时间
在程序中创建定时器,当到达时间间隔时,定时器( 实际是GetMessage )会向程序发送一个WM_TIMER消息
定时器的精度是毫秒,但是准确度低
可以设置时间间隔为1000ms, 但是会在非1000毫秒到达消息
-
附带信息
wPARAM: 定时器ID
IPARAM: 定时器处理函数的指针 --> 一般没用
-
用途
周期性做,时间要求不严
创建销毁定时器
-
创建定时器
UINT_PTR SetTimeer( HWND hWnd,// 定时器窗口句柄 UINT_PTR nIDEvent, // 定时器ID UINT uElapse, // 时间间隔 ms为单位 TIMERPROC lpTimerFunc // 定时器处理函数指针(一般不使用,为NULL) ); // 创建成功,返回非0
-
关闭定时器
BOOL KillTimer( HWND hWnd, // 定时器窗口句柄 UINT_PTR uIDEvent // 定时器ID );
菜单资源
菜单分类
-
窗口的顶层菜单
-
弹出式菜单
例如:鼠标右键菜单
-
系统菜单
HMENU 类型表示菜单 ID 表示菜单项 --> 每一个菜单项都有一个ID
资源相关
-
资源脚本文件: *.rc文件
描绘图片、菜单资源
-
编译器: RC.EXE
菜单资源的使用
-
添加菜单资源
- 增加一个.rc文件 --> 会自动增加一个resource.h文件
- 选中rc文件 --> Add Resource
- 添加菜单资源
- 编辑菜单,下拉项 右键可以查看&修改属性
- 分割线 Separator -> true
- 菜单ID
-
加载菜单资源
-
注册窗口类时设置菜单
wc.lpszMenuName = (LPCTSTR)IDR_MENU1; // 强转一下路径
-
创建窗口时传递参数
倒数第三个参数 传递菜单句柄 ( 不是上面的菜单ID )
HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,LoadMenu(hIns,(LPCTSTR)IDR_MENU1),hIns,NULL)
加载菜单资源:
HMENU LoadMenu ( HINSTANCE hInstance, // handle to module --> 找本进程的内存 LPCTSTR lpMenuName // menu name or resource identifier );
-
在主窗口WM_CREATE消息中利用SetMenu函数设置菜单
HMENU hMenu = LoadMenu(hIns,(LPCTSTR)IDR_MENU1)); SetMenu(hWnd,hMenu);
-
命令消息处理
单击菜单项发出WM_COMMAND消息
-
附带信息
wPARAM : HIWORD 对于菜单为0 LOWORD 菜单项的ID lPARAM : 对于菜单为0
图标资源
以.ico结尾的图片
-
添加资源
注意图标的大小,一个图标文件中,可以有多个不同大小的图标
-
加载
HICON LoadIcon( HINSTANCE hInstance, // 当前程序实例句柄 LPCTSTR lpIconName // 图标资源id );// 成功返回HICON句柄
-
设置
注册窗口类
wc.hIcon = LoadIcon(hIns,(LPCTSTR)IDI_ICON1);
光标资源
-
添加光标的资源
关标的大小默认是32x32像素,每个光标有HotSpot( 热点 ), 是当前鼠标的热点
-
加载资源
HCURSOR LoadCursor( HINSTANCE hInstance, // 程序实例 LPCTSTR lpCursorName // 资源ID ); // hInstance 可以为NULL, 获取系统默认的Cursor
-
设置资源
-
在注册窗口时,设置光标
wc.hCursor = LoadCursor(hIns,(char*)IDC_CURSOR1);
-
使用SetCursor设置光标
HCURSOR SetCursor( HCURSOR hCursor // 光标 ); // 返回原来的光标
必须放在消息的处理中设置
WM_SETCURSOR 消息参数 wPARAM 当前使用的光标句柄 IPARAM - LOWORD 当前区域的代码 在哪个区域活动 HICLIENT / HTCAPTION 客户区/标题区 - HIWORD 当前鼠标消息ID,有没有点右键左键等等
DefWindowPro() 默认处理,会将光标重新改成注册窗口类的关标 所以SetCursor后要直接返回,不能经过DefWindowPro函数
-
字符串资源
-
添加字符串资源
添加字符串表,在表中添加字符串
-
字符串资源的使用
int LoadString( HINSTANCE hInstance, //程序实例 UINT uID, // 字符串ID LPTSTR lpBuffer, // 存放字符串的BUFF int nBufferMax // 字符串BUFF长度 ); // 成功返回字符串长度,失败0
快捷键资源
-
添加 资源添加快捷键表,增加命令ID对应的快捷键 Accelerator
让快捷键的ID 和 命令的ID一样 就是绑定
-
使用
// 加载加速键表 --> 表 HACCEL LoadAccelerators( HINSTANce hInstance, // 程序实例句柄 LPCTSTR lpTableName // 快捷键表名 ); // 返回快捷键表句柄 // 翻译快捷键 int TranslateAccelerator( HWND hWnd, // 处理消息的窗口句柄 HACCEL hAccTable, // 快捷键表句柄 LPMSG lpMsg// 消息 ); // 如果快捷键,返回非零
TranslateAccelerator(hWnd,hAccel,&nMsg){ if(nMsg.message != WM_KEYDOWN ) return 0; // 没有按键按下,一定不是快捷键 根据nMsg.wParam(键码值),获知哪些按键按下 到快捷键表中去匹配查找 if(找不到) return 0; else{ SendMessage(hWnd,WM_COMMAND,(HI)1(LO)快捷键对应的ID,...); return 1; } }
// 调用 while( GetMessage(&nMsg,NULL,0,0)){ if( !TranslateAccelerator(hWnd,hAccel,&nMsg)){ TranslateMessage(&nMsg); DispatchMessage(&nMsg); } }
-
在WM_COMMAND中相应消息,消息参数
wPARAM : HWORD 为1表示加速键,为0表示菜单
LOWORD 为命令ID
lParam: 为0
绘图编程
绘图基础
-
绘图设备 DC (Device Context), 绘图上下文/绘图描述表
-
HDC-DC 句柄,表示绘图设备
-
GDI - Windows graphics device interface ( Win32 提供的绘图API )
-
颜色 RGB
32位: 8,8,8,8 --> 透明度
-
颜色的使用
COLORREF–> DWORD
COLORREF nColor = 0;
-
赋值使用RGB宏
nColor = RGB(0,0,255);
-
获取RGB值
GetRValue / GetGValue / GetBValue BYTE nRed = GetRValue( nColor );
HDC hdc = BeginPaint(hWnd,...); //--> 抓绘图设备
TextOut(hdc,x,y,"hello",...);
...
EndPaint(hwnd,...);
基本图形绘制
-
SetPixel 设置指定点的颜色
COLORREF SetPixel( HDC hdc, // DC句柄 int X, int Y, COLORREF crColor );// 返回原来的颜色
PAINTSTRUCT ps = {0}; HDC hdc = BeginPaint(hWnd,&ps); SetPixel(hdc,100,100,RGB(255,0,0)); EndPaint(hWnd,&ps);
-
线得使用(直线,弧线)
MoveToEx – 指明窗口当前点
LineTo 从窗口当前点到指定点绘制一条直线
当前点: 上一次绘制时的最后一点,初始为(0,0)点
MoveToEx(hdc,100,100,NULL); // 最后一个参数返回上一次的当前点 LineTo(hdc,300,300);
-
封闭图形:能够用画刷填充的图形
Rectangle / Ellipse
Rectangle(hdc,x,y,x+w,y+h);
Ellipse(hdc,100,100,x+w,y+h); // 外接矩形
GDI绘图对象
画笔
-
画笔的作用
线的颜色,线型(虚线画笔、点线画笔…),线粗
HPEN : 画笔句柄
-
画笔的使用
-
创建画笔
HPEN CreatePen( int fnPenStyle, // 画笔的样式 int nWidth, // 画笔的粗细 COLORREF crColor // 画笔的颜色 ); // 创建成功返回句柄
-
PS_SOILD - 实心线,可以支持多个像素宽,其他线型只能是一个像素宽
-
PS_DASH - 虚线画笔
-
-
将画笔应用到DC中
HGDIOBJ SelectObject( HDC hdc, // 绘图设备句柄 HGDIOBJ hgdiobj // GDI绘图对象句柄 ); // 返回原来的GDI绘图对象句柄 --> 注意保存原来DC当中画笔
-
绘图
-
取出DC中的画笔,将原来的画笔,使用SelectObject函数,放入到设备DC中
-
释放画笔
BOOL DeleteObject( HGDIOBJ hObject // GUI绘图对象句柄 );
只能删除不被DC使用的画笔,所以在释放前,必须将画笔从DC中取出
-
画刷
封闭图形的填充的颜色、图案
HBRUSH 画刷句柄
-
创建画刷
HBRUSH hBursh = CreateSolidBrush(RGB(0,255,0)); //CreateSolidBrush 创建实心画刷 HBRUSH hBurshhat = CreateHatchBrush(HS_CROSS,RGB(0,255,0)); // HS_CROSS 经纬线 //CreateHatchBrush 创建纹理画刷
-
将画刷应用到DC中
HBRUSH oldBrush = SelectObject(hdc,hBursh); // 原本的刷子是白色的
-
绘图
-
将画刷从DC中取出
SelectObject(hdc,oldBrush);
-
删除画刷
DeleteObject(hPen);
用一条条的单独直线围起来的图形,不是封闭图形,不能填充
系统GDI对象
使用GetStockObject 函数获取系统维护的画刷,画笔等
如果不使用画刷填充,需要使用NULL_BRUSH 参数,获取透明画刷。
GetStockObject 返回的画刷不需要DeleteObject
// 透明画刷
HGDIOBJ hBursh = GetStockObject(NULL_BRUSH);
// 应用刷子
// 恢复刷子
位图
位图绘制
-
位图相关
光栅图形: 记录图像中每一点的颜色等信息
矢量图形: 记录图像算法,绘图指令等等
HBITMAP 位图句柄
-
位图的使用
-
在资源中添加位图资源 --> 资源ID
-
从资源中加载位图LoadBitmap
HBITMAP hBmp = LoadBitmap(g_hInstance,(char*)IDB_BITMAP1);
-
创建一个与当前DC相匹配的DC(内存DC) --> 在内存中绘图
HDC CreateCompatibleDC( HDC hdc // 当前DC句柄,可以为NULL, 使用屏幕DC ); // 返回创建好的DC句柄
-
将位图放入匹配的内存DC中 --> 在虚拟区域中绘制图形
SelectObject
-
成像 (1:1)
BOOL BitBlt( HDC hdcDest; // 目的DC int nXDest, int nYDest, int nWidth, int nHeight, HDC hdcSrc, // 源DC --> 内存DC int nXSrc, // 源左上X int nYSrc, // 源左上Y DWORD dwRop // 成像方法,SRCCOPY );
缩放成像
BOOL StretchBlt( HDC hdcDest, int nXOriginDest, int nYOriginDest, int nWidthDest, int nHeightDest, HDC hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, // 源DC宽 int nHeightSrc, // 源DC高 DWORD dwRop ); // 目的区域比原始图像小 --> 缩小 // 目的区域比原始图像大 --> 放大
-
取出位图
SelectObject 从内存DC取数据
-
释放位图
DeleteObject(hBmp);
-
释放匹配的内存DC
DeleteDC(hMemdc);
-
文本绘制
-
文字绘制
TextOut -> 将文字绘制在指定坐标位置,只能单行绘制
TextOut(hdc,x,y,strdata,nCount);
RECT rc; rc.left = 100; // (left,top) (right,bottom) rc.top= 150; rc.right = 200; rc.bottom= 200; int DrawText( HDC hDC, // DC句柄 LPCTSTR lpString, // 字符串 int nCount, // 字符数量 LPRECT lpRect, // 绘制文字的矩形框 UINT uFormat // 绘制的方式 // DT_LEFT|DT_TOP 水平靠左|垂直靠上 DT_WORDBREAK 多行绘制 ); // DT_NOCLIP 打破矩形区域的限制,不裁剪 // DT_CENTER DT_VCENTER --> DT_VCENTER、DT_BOTTOM只适用于单行DT_SINGLELINE,和DT_WORDBREAK冲突
-
文本颜色和背景
文字颜色: SetTextColor(hdc,RGB(…));
文字背景色: SetBkColor(hdc,RGB(…)); --> 仅适用于不透明模式
文字背景模式: SetBkMode(QPAQUE / TRANSPARENT );
- QPAQUE 不透明,默认
- TRANSPARENT 透明
-
字体
Windows常用的字体为 TrueType格式的字体文件 --> 保存点阵字型
字体名 - 标识字体类型 --> 第一行有字体名称
HFONT - 字体句柄
-
创建字体
HFONT CreateFont( int nHeight, // 字体高度 int nWidth, // 字体宽度 一般给一个高度,宽度为0,系统字节匹配一个合适的宽度 int nEscapement, // 字符串倾斜角度(以0.1度为单位) --> 和斜体不同 int nOrientation, // 字符旋转角度 二维看不出效果 int fnWeight, // 字体的粗细 ,不是以像素为单位 , 900是细体 DWORD fdwItalic, // 斜体 DWORD fdwUnderline, // 下划线 DWORD fdwStrikeOut, // 删除线 1/0 DWORD fdwCharSet, // 字符集 --> GB2312_CHARSET 涵盖基本所有的汉字 DWORD fdwOutputPrecision, // 输出精度 , 废弃 DWORD fdwClipPrecision, // 剪切精度 , 废弃 DWORD fdwQuality, // 输出质量 , 废弃 DWORD fdwPitchAndFamily,// 匹配字体 , 废弃 LPCTSRT lpszFace // 字体名称 ---> 字体文件内容第一行 );
-
应用字体到DC
HGDIOBJ oldfont = SelectObject(hdc,hFont);
-
绘制文字
DrawText / TextOut
-
取出字体
SelectObject(hdc,oldfont);
-
删除字体
DeleteObject(hFont);
-
对话框
-
普通窗口: 自定义函数调用缺省函数
-
对话框窗口: 缺省函数调用自定义函数
缺省函数(...){ ... 自定义函数(...) }
对话框原理
-
对话框分类
模态对话框:当对话框显式时,会禁止其他窗口和用户的交互操作
非模态对话框:在对话框显示后,其他窗口仍然可以和用户交互操作
-
对话框基本使用
- 对话框窗口处理函数
- 注册窗口类( 不使用 ), 操作系统帮忙注册, 名字–>Dialog
- 创建对话框
- 对话框的关闭
-
对话框窗口处理函数 --> 并非真正的对话框窗口处理函数
INT_PTR CALLBACK DialogProc( HWND hwndDlg , // 窗口句柄 UINT uMsg, // 消息ID WPARAM wParam , // 消息参数 LPARAM lParam // 消息参数 ); // 返回TRUE 缺省处理函数不需要处理 // 返回FALSE 交给缺省处理函数处理 // 一般不需要调用缺省对话框窗口处理函数
模态对话框
-
创建对话框
INT DialogBox( HINSTANCE hInstance, // 应用程序实例句柄 LPCTSTR lpTemplate, // 对话框资源ID HWND hWndParent, // 对话框父窗口 DLGPROC lpDialogFunc // 自定义函数 );
DialogBox事一个阻塞函数,只有当对话框关闭后,才会返回,继续执行后续代码
返回值通过EndDialog设置 --> 通过EndDialog结束对话框
点击关闭按钮消息: WS_SYSCOMMAND
-
对话框处理函数
INT_PTR CALLBACK DlgProc(HWND hwndlg,UINT msgID,WPARAM wParam,LPARAM lParam){ switch(msgID){ case WM_SYSCOMMAND: if(wParam == SC_CLOSE){ // 点击关闭按钮 EndDialog(hwndlg,100); } break; } return FALSE; // 将消息交给真正的对话框处理函数的后续处理部分处理 }
-
对话框的关闭
BOOL EndDialog( HWND hDlg, // 关闭的对话框窗口句柄 INT_PTR nResult // 关闭的返回值 );// 消除模式对话框 & 解除模式对话框的阻塞状态
关闭模式对话框,只能使用EndDialog,不能使用DestroyWindow(销毁窗口)等函数。
DestroyWindow 可以销毁对话框,但是不能消除阻塞
nResult 是DialogBox函数退出时的返回值
-
对话框的消息
WM_INITDIALOG 对话框创建之后显示之前,通知对话框窗口处理函数,可以完成自己的初始化相关的操作。
其他消息和窗口的消息一致
非模态对话框
-
创建对话框
HWND CreateDialog( HINSTANCE hInstance , // 应用程序实例句柄 LPCTSTR lpTemplate, // 模板资源ID HWND hWndParent, // 父窗口 DLGPROC lpDialogFunc // 自定义函数 );
非阻塞函数,创建成功后返回窗口句柄**,需要使用ShowWindow函数显示对话框**
-
对话框的关闭
关闭时使用DestroyWindow销毁窗口,不能使用EndDialog关闭对话框
其他
增加DOS窗口
用于调试
HANDLE g_hOutput = 0 ;// 接收标准输出句柄
WinMain(...){
AllocConsole(); //增加入口函数
g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
// FreeConsole(); // 释放控制台
WriteConsole(g_hOutput,text,strlen(text),NULL,NULL);
}
参考
- https://www.bilibili.com/video/BV1Qb4y1o7u9