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_RBUTTONDWONWM_RBUTTONUPWM_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); }