认识Windows窗口
-
图形用户界面
(GUI
)是指采用图形方式显示程序界面。 -
启动一个程序,桌面就显示一块
矩形区域
,被称为窗口
,用户可以在窗口中操作应用程序,进行数据的管理和编辑
窗口包含如下元素
- 标题栏和窗口标题
- 系统菜单按钮
- 菜单栏
- 最小化,最大化和关闭按钮
- 滚动条
- 窗口边框
- 客户区: 窗口
内部
的矩形区域
, 指除了标题栏,菜单栏,工具栏,状态栏,滚动条和边框
之外的区域,程序在这里显示文本,图形,子窗口或其他信息
, - 非客户区:指除了客户区之外的区域,包括
标题栏,菜单栏,工具栏,状态栏,滚动条和边框
等 - 状态栏
一个窗口不一定就是一个程序,它可能是程序的一部分,一个程序
可以创建多个子窗口
,程序的主窗口都是顶级窗口
, 顶级窗口(Top-Level)指父窗口是桌面窗口
的程序窗口
一个程序并不一定必须要有窗口,比如悄悄运行在后台的木马程序就不会显示一个窗口, 如果一个程序不想和用户进行交互,它可以选择不创建窗口
第一个窗口程序
ini
#include <Windows.h>
#include <tchar.h> // _tcslen 函数需要该头文件
#pragma comment(lib, "Winmm.lob") // 播放声音的PlaySound函数需要Winmm库
// 函数声明,窗口过程
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wndclass; // RegisterClassEx函数用的WNDCLASSEX结构
TCHAR szClassName[] = TEXT("MyWindow"); // RegisterClassEx函数注册的窗口类的名称
TCHAR szAppName[] = TEXT("HelloWindows"); // 窗口标题
HWND hwnd; // CreateWindowEx函数创建的窗口的句柄
MSG msg; // 消息循环所用的消息结构体
wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WindowProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szClassName;
wndclass.hIconSm = NULL;
RegisterClassEx(&wndclass);
hwnd = CreateWindowEx(0, szClassName, szAppName, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 300, 180, NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&msg, NULL, 0, 0) != 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
TCHAR szStr[] = TEXT("你好,Windows程序设计");
switch (uMsg)
{
case WM_CREATE:
PlaySound(TEXT("成都(两会版).wav"), NULL, SND_FILENAME | SND_ASYNC/* | SND_LOOP*/);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
TextOut(hdc, 10, 10, szStr, _tcslen(szStr));
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
编译运行程序,计算机响起"春风吹过的时候,我矗立在街头...",
并在客户区左上角显示,你好,window程序设计, 程序界面如图所示
程序有标题栏,系统菜单,最小化,最大化和关闭按钮,拖动窗口边框可以改变窗口大小,这个窗口包含了典型窗口的大部分特征
刚刚接触Windows程序设计的读者可能觉得这个程序太过复杂,但是这个程序是大多数窗口程序的模板,以后写新的程序,只要复制过来修改即可
WinMain 的执行流程
- 注册窗口类(
RegisterClassEX
) - 创建窗口(
CreateWindowEX
) - 显示窗口(
ShowWindow
),刷新
窗口客户区
(UpdateWindow
) - 进入无限的
消息获取,分发
的循环- 获取消息(
GetMessage
),转换消息(TranslateMessage
),将消息分发到回调函数WindowProc
进行处理(DispatchMessage
)
- 获取消息(
RegisterClassEx
注册窗口类
RegisterClassEx,Ex是扩展的意思,它是Win16中RegisterClass 函数的扩展,该函数用于注册
一个窗口类,在下一步调用CreateWindowEx函数创建窗口的时候使用
arduino
ATOM WINAPI RegisterClassEx(const WNDClASSEX *lpwcx);
函数返回值ATOM
是原子
的意思,就是说RegisterClassEx 函数会返回一个独一无二的值,唯一代表我们注册的窗口类,我们通常使用WNDCLASSEX结构中指定的窗口类名(WNDCLASSEX lpszClassName字段)
如果函数执行成功,返回原子值,如果执行失败,通过调用GetLastError函数获取错误代码
参数lpsz是一个指向WNDCLASSEX结构的指针,调用RegisterClassEX函数之前必须初始化结构
arduino
typedef struct tagWNDCLASSEX {
UNIT cbSize; // 该结构的大小,以字节为单位,设置为sizeof(WNDCLASSEX)
UNIT style, // 用这个窗口类创建窗口的具体样式
WINDPROC lpfnWndProc // 指定窗口过程,所以基于这个窗口类创建的窗口都使用这个窗口过程
int cbClsExtra, // 紧跟在WNDCLASSEX结构后面的附加字节数,用来存放自定义数据
int cbWndExtra, //紧跟在窗口实例后面的附加字节数,用来存放自定义数据
HINSTANCE hInstance, // 实例句柄
HICON hIcon,// 窗口类创建的窗口所用的图标资源句柄
HCURSOR hCursor // 光标资源句柄
HBRUSH hbrBackground // 背景画刷句柄
LPCTSTR lpszMenuName // 窗口类的菜单资源名称
LPCTSTR lpszClassName // 窗口类名称,调用CreateWindowEX 函数创建窗口时需要使用窗口类名称
HICON hIconSm//窗口所用的小图标句柄
} WNDCLASSEX, *PWNDCLASSEX;
第二个字段style指定窗口类样式,常见的类别如下
arduino
#define CS_VREDRAW 0x0001 // 当窗口高度发生变化后,重新绘制整个窗口
#define CS_HREDRAW 0x0002 // 当窗口宽度发生变化后,重新绘制整个窗口
#define CS_DBLCLKS 0x0008 //当用户双击鼠标后,向窗口过程发送双击消息WM_LBUTTONDBCLK
#define CS_OWNDC 0x0020 // 为窗口类的每个窗口分配唯一的设备环境
#define CS_CLASSDC 0x0040 // 分配一个设备环境基于窗口类的所有窗口共享
#define CS_PARENTDC 0x0080 // 将子窗口裁剪矩形设置为父窗口的裁剪矩形,以便子窗口可以在父窗口绘制
#define CS_NOCLOSE 0x0200 // 禁用关闭按钮,系统菜单的关闭按钮会消息
#define CS_SAVEBITS 0x0800
#define CS_BYTEALIGNCLIENT 0x1000
#define CS_BYTEALIGNWINDOW 0x2000
#define CS_GLOBALCLASS 0x4000 // 表示窗口类时应用程序全局类
CS_DBLCLKS 样式设置双击事件 窗口过程加上WM_LBUTTONDBLCL 消息的处理
css
case WM_LBUTTONDBCLK:
MessageBox(hwnd, TEXT("客户区被双击"), TEXT("提示"),MB_OK);
return 0;
重新编译运行,双击客户区会弹出消息框
第三个字段lpfnWndProc指定窗口过程,所有基于这个窗口类的窗口都使用这个窗口过程, 窗口过程的概念稍后讲解,只需要知道程序运行以后会发生很多事件,如窗口创建,窗口重绘,窗口尺寸改变,鼠标双击,程序关闭等事件,操作系统会把这些事件通知应用程序,应用程序在lpfnWndProc字段指定的窗口过程(函数)中处理这些事件,WNDPRCO是窗口过程的指针类型
第六个字段 hInstance指定窗口类的窗口过程所属的实例句柄,也就是所述模块. 第7个字段HIcon指定图标句柄,这个句柄用于生成可执行程序文件图标,Widnows已经定义了一些图标,程序可以使用资源文件中自定义的图标,通过LoadIcon 或 LoadImage 函数获取
资源文件在编译后就会被打包到可执行文件中, LoadIcon函数用于从应用程序加载指定的图标资源
scss
HICON LoadIcon(
HINSTANCE hInstance // 程序实例句柄(模块句柄)
LPCSTR lpIconName // 要记在的图标资源名称
)
arduino
#define IDI_APPLICATION MAKEINTRESOURCE(32512) // 默认应用程序图标
#define IDI_HAND MAKEINTRESOURCE(32513) // 错误提示样式图标
#define IDI_QUESTION MAKEINTRESOURCE(32514) //问号图标
#define IDI_EXCLAMATION MAKEINTRESOURCE(32515) // 感叹号图标
#define IDI_ASTERISK MAKEINTRESOURCE(32516) // 信息提示
第8个字段 hCursor 指定窗口所用的光标资源句柄 使用 LoadCursor 函数加载光标资源
scss
HCURSOR WINAPI LoadCursor(
HINSTANCE hInstance,
LPCTSTR lpCursorName // 光标资源名称
)
arduino
#define IDC_ARROW MAKEINTRESOURCE(32512) // 标准箭头
#define IDC_IBEAM MAKEINTRESOURCE(32513) // 工字
#define IDC_WAIT MAKEINTRESOURCE(32514) // 等待(忙碌)
#define IDC_CROSS MAKEINTRESOURCE(32515) // 十字线
#define IDC_UPARROW MAKEINTRESOURCE(32516) // 垂直箭头
第9个字段 hbrBackground 指定窗口类创建的窗口使用的背景画刷句柄, 可以使用标准系统颜色, HBRUSh 是Windows定义的画刷句柄类型 GetStockObject函数用于获取备用画笔,画刷,字体等句柄
arduino
HGDIOBJ GetStockObject(int fnObject);
fnObject 参数指定备用对象的类型,对于画刷来说,可以是下表所列的值
arduino
#define WHITE_BRUSH 0 // 白色画刷
#define LTGRAY_BRUSH 1 // 浅灰色
#define GRAY_BRUSH 2 // 灰色
#define DKGRAY_BRUSH 3 // 深灰色
#define BLACK_BRUSH 4 // 黑色
#define NULL_BRUSH 5 // 空画刷,什么也不画
#define HOLLOW_BRUSH NULL_BRUSH // 空画刷
#define WHITE_PEN 6 // 白色画笔
#define BLACK_PEN 7 // 黑色画笔
如果函数执行成功, 返回指定备用对象的HGDIOBJ 类型句柄,如果函数执行失败,则返回NULL
也可以使用标准系统颜色,下面是可用的部分标准系统颜色
arduino
#define COLOR_SCROLLBAR 0
#define COLOR_BACKGROUND 1
#define COLOR_ACTIVECAPTION 2
#define COLOR_INACTIVECAPTION 3
#define COLOR_MENU 4
#define COLOR_WINDOW 5
#define COLOR_WINDOWFRAME 6
#define COLOR_MENUTEXT 7
#define COLOR_WINDOWTEXT 8
#define COLOR_CAPTIONTEXT 9
#define COLOR_ACTIVEBORDER 10
#define COLOR_INACTIVEBORDER 11
#define COLOR_APPWORKSPACE 12
#define COLOR_HIGHLIGHT 13
#define COLOR_HIGHLIGHTTEXT 14
#define COLOR_BTNFACE 15
#define COLOR_BTNSHADOW 16
#define COLOR_GRAYTEXT 17
#define COLOR_BTNTEXT 18
#define COLOR_INACTIVECAPTIONTEXT 19
#define COLOR_BTNHIGHLIGHT 20
对应的效果如下图
注册后就可以使用注册的窗口类去创建具体的窗口类了
创建窗口(CreateWindowEX)
注册窗口类后,就可以在窗口类的基础上通过调用CreateWindowEX来添加其他的属性来创建窗口了, 可以创建具有扩展样式的重叠窗口,弹出窗口和子窗口
arduino
HWND WINAPI CreateWindowEx(
_In_ DWORD dwExStyle, // 窗口的扩展窗口样式
_In_opt_ LPCSTR lpClassName, // RegisterClassEx 函数注册的函数雷鸣
_In_opt_ LPCSTR lpWindowName, // 窗口标题
_In_ DWORD dwStyle, // 窗口的窗口样式
_In_ int X, // 窗口的初始水平位置,像素单位
_In_ int Y, // 窗口的初始垂直位置,像素单位
_In_ int nWidth, // 窗口的初始宽度,像素单位
_In_ int nHeight, // 窗口的初始高度,像素单位
_In_opt_ HWND hWndParent, // 窗口的父窗口
_In_opt_ HMENU hMenu, // 菜单句柄
_In_opt_ HINSTANCE hInstance, // 与窗口关联的实例句柄(模块句柄)
_In_opt_ LPVOID lpParam); // 额外参数
}
第一个参数 dwExStyle
指定窗口的扩展样式, dwExStyle , WS 表示 Windows Style 缩写,下面是部分窗口样式
makefile
WS_EX_WINDOWEDGE: 窗口边框带有凸起的边缘
WS_EX_CLIENTEDGE: 凹陷的边缘
WS_EX_OVERLAPPEDWINDOW: 窗口是一个重叠窗口
WS_EX_ACCEPTFILES: 窗口接受拖放文件
WS_EX_TOPMOST: 窗口始终保持在最顶层
WS_EX_LAYERED: 分层或透明窗口,该样式可以实现一些混合特效
WS_EX_TOOLWINDOW: 创建一个工具窗口,通用用作浮动工具条
第二个参数lpClassName
指定为RegisterClassEx函数注册的窗口类型 第3个参数lpWindowName
指定窗口标题,通常窗口类型和窗口标题可以使用相同的名称 第4个参数dwStyle指定窗口样式,窗口样式是一些以WS_开头的预定义值,如下列出分窗口样式
arduino
#define WS_OVERLAPPED 0x00000000L // 窗口是重叠窗口,重叠窗口具有标题栏和边框
#define WS_POPUP 0x80000000L // 窗口是弹出窗口,该样式不能与WS_CHILD样式一起使用
#define WS_CHILD 0x40000000L // 窗口是子窗口,具有该样式的窗口不能有菜单栏,该样式不能与WS_POPUP 样式一起使用
#define WS_MINIMIZE 0x20000000L // 窗口最初最小化
#define WS_VISIBLE 0x10000000L // 窗口最初可见,如果不设置这个样式,窗口将不可见
#define WS_DISABLED 0x08000000L // 窗口最初被禁用,禁用的窗口无法接受用户的输入
#define WS_CLIPSIBLINGS 0x04000000L // 不对兄弟窗口(属于同一个父窗口的多个子窗口)进行绘制,以后可能会用到
#define WS_CLIPCHILDREN 0x02000000L // 父窗口不对子窗口区域进行绘制
#define WS_MAXIMIZE 0x01000000L // 窗口最初被最大化
#define WS_CAPTION 0x00C00000L // 窗口有标题栏(WS_BORDER)
#define WS_BORDER 0x00800000L // 窗口有细线边框
#define WS_DLGFRAME 0x00400000L // 创建一个带有对话框边框的窗口,这种样式的窗口没有标题栏
#define WS_VSCROLL 0x00200000L // 垂直滚动条
#define WS_HSCROLL 0x00100000L // 水平滚动条
#define WS_SYSMENU 0x00080000L // 窗口标题栏上有一个系统菜单,还必须同时指定WS_CAPTION 样式
#define WS_MINIMIZEBOX 0x00020000L // 窗口有最小化按钮
#define WS_MAXIMIZEBOX 0x00010000L // 窗口有最大化按钮
#define WS_TILED WS_OVERLAPPED
#define WS_ICONIC WS_MINIMIZE // 窗口最初被最小化
#define WS_SIZEBOX WS_THICKFRAME // 通过边框调整窗口大小
#define WS_TILEDWINDOW WS_OVERLAPPEDWINDOW
CreateWindowEx 函数可以创建具有扩展窗口样式的重叠窗口,弹出窗口和子窗口
,
如果需要创建一个常见的重叠窗口,则使用 WS_OVERLAPPEDWINDOW
样式即可,具有标题栏,系统菜单,可调边框,最小化,最大化和关闭按钮,
如果需要创建一个弹出窗口,则使用 WS_POPUPWINDOW | WS_CAPTION 样式即可,
如果需要创建一个子窗口,则使用WS_CHILD样式
第5个和第6个参数x ,y
分别指定窗口左上角相对于屏幕左上角初始水平和垂直位置
,以像素为单位,CW_userdefault表示使用Windows设置的默认值
hWndParent
指定窗口的父窗口,如果创建子窗口,则需要指定父窗口的句柄,以便父子窗口之间进行通信,本程序是顶级窗口程序,设置为NULL即可
顶级窗口的父窗口是桌面窗口
, 重叠窗口和弹出窗口都可以是顶级窗口, 他们的坐标定位相对于屏幕左上角,顶级窗口作为程序的主窗口,
子窗口必须有父窗口,父窗口可以是层叠窗口,弹出窗口,甚至是其他子窗口,子窗口从父窗口的客户区左上角定位
,而不是从屏幕左上角定位,可以为子窗口设置标题栏,最小化,最大化按钮,边框和滚动条,但不能设置菜单
第10个参数hMenu指定菜单句柄,本程序没有菜单,设置为NULL,如果创建的是子窗口,则该参数设置为子窗口ID
第11个参数hInstance 指定与窗口关联的实例句柄,也就是窗口所属的模块
第12个参数lParam可以指定为指向某些数据结构的指针
CreateWindowEX 函数执行成功,则返回新创建的窗口的窗口句柄
,如果函数执行失败,则返回为NULL,Windows系统中,每一个窗口都有一个句柄,在程序中可以使用句柄对窗口进行引用,许多Windows API 都以窗口句柄为参数,通过窗口句柄Windows就可以知道该函数要对哪个窗口进行操作,如果一个程序创建了多个窗口,那么每个窗口都具有不同的窗口句柄
显示窗口 (ShowWindow) 和刷新窗口客户区(UpdateWindow)
光标定位到ShowWindow(hwnd, nCmdShow)一行,F5 开始调试,程序窗口还没有显示出来,但如果打开任务管理器,则会发现HelloWindows.exe进程已经存在,窗口已经在Windows内部被创建了,ShowWindow函数用于设置指定窗口的显示状态
objectivec
BOOL WINAPI ShowWindow(
HWND hwnd // 窗口句柄
int nCmdShow // 窗口的显示方式
)
第一次调用ShowWindow 时,nCndShow参数可以指定为WinMain函数的值,SW_SHOWDEFAULT
,表示激活并显示窗口
ShowWindow 函数作用是设置指定窗口的显示状态,如果程序以后需要设置窗口的显示状态,有如下的值
arduino
#define SW_HIDE 0 // 隐藏窗口
#define SW_SHOWNORMAL 1 // 激活并显示一个窗口,如果窗口最小化或最大化,则系统会将其还原到原始大小
#define SW_SHOWMINIMIZED 2 // 激活窗口并将其最小化
#define SW_SHOWMAXIMIZED 3 // 激活窗口并显示为最大化窗口
#define SW_MAXIMIZE 3 // 激活窗口并显示为最大化
#define SW_SHOWNOACTIVATE 4 // 显示一个窗口,如果窗口最小化或最大化,则系统会还原到原始大小和位置,不同之处就是未激活窗口
#define SW_SHOW 5 // 激活窗口并以当前大小和位置显示窗口
#define SW_MINIMIZE 6 // 最小化窗口
#define SW_SHOWMINNOACTIVE 7 // 最小化窗口,未激活状态
#define SW_SHOWNA 8 // 以当前大小和位置显示窗口,类似于SW_Show,不同之处在于未激活窗口
#define SW_RESTORE 9 // 激活并恢复显示窗口,如果窗口最小化或最大化,则系统会将其还原到原始大小
#define SW_SHOWDEFAULT 10 // 根据传递给CreateProcess 函数的STARUUPINFO 结构中指定的SW_值显示状态
#define SW_FORCEMINIMIZE 11 // 最小化窗口(即使拥有该窗口的线程没有响应),仅当最小化其他线程的窗口时才使用该标志
#define SW_MAX 11
如果窗口以前可见,则返回值为非0,如果窗口以前隐藏,则返回值为0
UpdateWindow
函数通过向窗口发送WM_PAINT
消息来更新窗口的客户区,该函数将WM_PAINT 消息直接发送到窗口的窗口过程,绕过
应用程序的消息队列
,即UpdateWindow 函数导致窗口过程 WindowProc 执行 case WM_PAINT 逻辑,调用 TextOut 函数输出文字, 消息机制将在下一节详细介绍 BOOL updateWindow(HWND hwnd); // 更新窗口 如果函数执行成功,则返回TRUE,如果失败,返回FALSE
消息循环
程序运行以后会发生很多事件
,比如窗口创建,窗口重绘,窗口尺寸改变, 鼠标双击, 程序关闭等事件, Windows 会把这些事件通知应用程序, Windows 为每个应用程序
维护消息队列
事件发生以后, Windows 会自动将其转换成消息, 并放到消息队列中, 应用程序通过调用 GetMessage
函数从消息队列中获取消息
, 调用TranslateMessage
函数转换消息
, 调用 DispatchMessage
函数分发消息到窗口过程
, 窗口过程处理完一个消息后, 将控制权返回给 Windows, 然后 DispatchMessage 函数返回, 这一轮操作完成后,又会进行下一轮的消息获取,转换和分发
GetMessage 函数用于从调用线程的消息队列中获取消息:
objectivec
BOOL WINAPI GetMessage (
LPMSG lpMsg, // MSG 结构用于存放消息的具体信息
HWND hWnd, // 要获取哪个窗口的消息
UNIT uMsgFilterMin, // 要获取的消息的最小值
UINT wMsgFilterMax, // 要获取的消息最大值
)
第一个参数 lpMsg 指向 MSG 结构的指针, 用于存放消息的具体信息, 即函数在获取一个消息以后,会把这个消息的具体信息存放在这个结构中
arduino
typedef struct tagMSG {
HWND hwnd, // 哪个窗口发生的消息
UNIT message, // 消息类型, 以WM_开头(Windows Message)
WPARAM wParam // 消息参数, 取决于具体的消息类型
LPARAM lParam,// 消息参数, 其含义取决于具体的消息类型
DWORD time // 消息发生的事件
POINT pt // 消息发生时的光标位置,屏幕坐标
} MSG *PMSG, NEAR *NPMSG, FAR *LPMSG;
pt 字段时一个 POINT 结构,表示消息发生时的光标位置, 该结构在windef.h头文件中定义
arduino
typedef struct tagPOINT {
LONG x;
LONG y
} POINT, **PPOINT, NEAR *NPPOINT, FAR *LPPOINT
TranslateMessage
函数将按键消息
转换成字符消息
,然后将字符消息发送到调用线程中,下次线程调用GetMessage函数时即可获取这个字符消息 BOOL WINAPI translateMessage(const MSG *lpMsg) // 从GetMessage 函数获取到的MSG
如果消息被转换,即一个字符消息被发送到线程的消息队列,则返回值为0,如果消息没有转换,即没有字符消息被发送到线程的消息队列,则返回值为0
窗口显示后,程序需要处理来自用户的键盘输入
和鼠标输入
等,用户按下一个按键时会产生WM_KEYDOWN
消息,松开按键的时候会产生WM_CHAR
消息,这样一来我们就可以在窗口过程中处理WM_CHAR消息来判断用户按下了哪一个字符按键,即有了TranslateMessage函数的帮助
对于字符按键我们不需要处理WM_KEYDOWN
和WM_KEYUP
消息,只需处理WM_CHAR
消息即可,在窗口过程中添加如下代码
vbnet
case WM_CHAR:
{
TCHAR szChar[16] = {0};
wsprintf(szChar,TEXT("用户按下了字符"),wParam);
MessageBOX(hwnd, szChar, Text("提示"),MB_OK);
return 0;
}
编译运行程序,输入法切换为英文状态,每当用户按下一个可显示字符,就会弹出消息框,如果按下的是Ctrl, Shift Alt
一类的按键,则不会产生WM_CHAR消息,如果把 TranslateMessage 注释掉,则不会产生WM_CHAR消息,关于键盘和鼠标输入情况,以后还会详细介绍
DispatchMessage 函数用于把GetMessage
函数获取到的消息分发到窗口过程
scss
LRESULT WINAPI DispatchMessage(const MSG *lpmsg) // 从GetMessage 函数获取到的MSG结构
窗口过程
消息即可以是队列消息
,也可以是非队列消息
队列消息是指那些由Window放入程序的消息队列中的消息,在程序的消息循环中,队列消息被获取,并投递到窗口过程中, 非队列消息则是由Windows对窗口过程的直接调用而产生的,
队列消息被POST到消息队列中,而非队列消息则被发送到Send到窗口过程,无论那种情况下,窗口过程都会为程序处理所有消息(无论是消息队列还是非队列消息),窗口过程是程序的消息处理中心
队列消息主要由用户的输入
产生,主要为按键消息(WM_KEYDOWN 和 WM_KEYUP)消息,由按键产生的字符消息(WM_CHAR
)鼠标移动(WM_MOUSEMOVE) 鼠标单击(MW_LBUTTON0DOWN), 此外,队列消息还包括计时器消息(WM_TIMER),重绘消息(WM_PAINT)和退出消息(WM_QUIT)等
非队列消息则包括队列消息以外的其他所有消息, 通常由调用
特定的windows函数
引起,例如,当WinMain调用CreateWindowEx函数时,Windows会创建函数,并在创建过程中向窗口过程发送一条WM_CREATE
消息,当WinMain函数调用ShowWindow
时,Windows又会发送WM_SIZE
和 WM_SHOWWindow
消息到窗口过程,接下来,WinMain又对UpdateWindow 函数进行调用,这便促使Windows向窗口过程发送一条WM_PAINT
消息
接下来,WinMain又对UpdateWindow函数调用,促使Windows 向窗口过程发送一条WM_PAINT 消息,另外,一些表示键盘和鼠标输入的队列消息也能产生非队列消息, 例如,键盘和鼠标选择某个菜单项
,键盘和鼠标消息会进入消息队列,而最终表明有菜单项WM_COMMAND
消息却是一个非队列消息
对于各种类型的消息,后面会详细介绍
窗口过程是Windows 回调函数(Windows 进行调用), 它是通过注册窗口类时使用WMDCLASSEX 结构的lpfnWndProc
字段指定, 窗口过程如下:
scss
LRESULT CALLBACK WindowProc(hwnd, uMsg, uParam, lParam);
窗口过程
的名称可以任意命名
,只要不和其他函数名称冲突就可,WNDPROC是窗口过程指定的类型
scss
typedef LRESULT(CALLBACK *WNDPROC)(HWND,UNIT, WPARAM, LPARAM);
一个Windows程序可以包含多个窗口过程
,但是一个窗口过程总是与一个通过调用ResigerClassEx函数注册的窗口类关联,窗口过程的4个参数与MSG结构的前四个字段一一对应的
WM_CREATE 函数
当WinMain 调用CreateWindowEX
函数时,Windows就会创建窗口,并在创建过程中向窗口过程发送一条WM_CREATE消息,WM_CREATE消息即是窗口过程较早收到的消息之一,程序通常会在这做一些初始化工作,本程序为了增加趣味性,在WM_CREATE 消息中调用PlaySound函数播放一首音乐
scss
BOOL playSound(LPCSATR pszSound, HMOUDULE hmod, DWORD fdwSound);
WM_CREATE 是窗口过程中较早收到的消息之一,应该先播放音乐,再执行WM_PAINT消息显示字符串,是因为PlaySound指定了SND_ASYNC
,表示以异步形式
播放音乐
程序处理完WM_CREATE消息以后,应该返回0,表示继续创建窗口,如果返回-1,则窗口被销毁,程序退出
窗口关闭过程
用户单击程序右上角关闭按钮,窗口过程收到WM_CLOSE
消息,DefWindowProc函数会对WM_CLOSE 消息进行处理,调用DestroyWindow函数,完成窗口的清理工作 然后调用PostQuitMessage函数发送WM_QUIT给程序的消息队列,GetMessage函数获取WM_QUIT消息返回0,从而结束循环,程序退出
ini
case WM_CLOSE:
{
int nClose = MessageBox(hwnd,TEXT("关闭程序");
if (nclose == IDYES) {
DestoryWindow(Hwnd);
}
return 0;
}
其他消息处理
Windows 是基于消息驱动
的系统,在Windows中发生的一切都可以用消息
来表示,消息告诉系统发生了什么事件,消息类型数目巨大,一个程序在运行过程中会发生数不尽的消息,实际在窗口过程WindowProc
中我们仅处理了极少数感兴趣的消息
而已
DefWindowProc 函数用于调用Windows提供的默认函数过程,提供了默认处理,调用DefWindowProc
函数用于确保程序的每个消息都得到处理
,比如按住标题栏拖动窗口,单击最小化/最大化按钮,改变窗口大小等,都是默认窗口过程进行处理的,调用DefWindowProc函数时使用窗口过程收到的相同参数即可
markdown
LRESULT WINAPI DefWindowProc {
HWND hwnd,UINT msg,WPARM wparam, LPARAM lparam)
}
WM_PAINT 重绘
消息
调用UpdateWindow
函数,会使Windows向窗口过程发送WM_PAINT消息
WM_PAINT 是Window编程中非常重要的一条消息,当窗口客户区的部分
或全部
变成无效且必须更新的时候,窗口过程会收到这条消息,这时候窗口客户区必须重绘,什么时候窗口客户区会变成无效
呢
- 当程序
窗口``首次
被创建
后,整个窗口客户区都是无效
的,因为这时应用程序尚未在窗口客户区上绘制任何东西,在WinMain中调用UpdateWindow 函数时发送第一条WM_PAINT消息,并指示窗口过程在窗口客户区中进行绘制 - 在
调整程序窗口的大小
时,窗口客户区会变为无效,我们把WNSCLASSEX结构的style设置CS_HREDRAW | CS_VREDRAW
,表示当窗口大小
发生变化
后,整个窗口都变成无效
,窗口过程会收到一条WM_PAINT 消息 - 如果
最小化程序窗口
,再讲窗口恢复
到原先的尺寸,Windows并不会保存原先窗口客户区的内容,因为假设由系统负责保存窗口客户区内容,在图形用户界面下需要保存的数据太多了,Window采取的策略是先宣布窗口无效
,窗口过程收到WM_PAINT 消息
后需要自行恢复
窗口客户区的内容 - 在屏幕
拖动
程序窗口的全部或一部分到屏幕外,然后在拖回屏幕中的时候,窗口被标记为无效,窗口过程会收到一条WM_PAINT消息,并对窗口的客户区进行重绘
收到 WM_PAINT 消息以后,对于该消息的处理通常是调用BeginPaint
函数来获取窗口客户区
的设备环境
(DC
, Device Context, 设备上下文)句柄,然后调用GDI绘图函数
来执行更新窗口客户区所需的绘图操作,完成绘图操作后,应该调用EndPaint
函数来释放显示设备DC,在绘制之前,必须首先获取显示设备的DC句柄,显示设备DC定义了一组图形对象及其关联属性,以及影响输出的图形模式,系统提供与程序窗口相关联的显示设备DC,应用程序使用显示设备(DC)将其输出定向到指定的窗口,以后会慢慢理解设备环境(DC)
的概念,调用GDI绘图函数来显示文本和图形通常需要DC句柄
BeginPaint 函数会将窗口的更新区域设置为空
,就是说无效区域变成有效,如果应用程序处理WM_PAINT 消息但不调用BeginPaint 或以其他方式清除无效区域,只要无效区域不为空,应用程序会继续接受WM_PAINT消息,如果一个窗口过程不对WM_PAINT消息进行处理,那么应该交给DefWindowProc函数执行默认处理,DefWindowProc函数一次调用BeginPaint 和 EndPaint函数,以使客户区无效区域有效
BeginPaint 指定窗口进行绘图工作的准备,并把绘图有关的信息填充到 PAINTSTRUCT 结构中
HDC BeginPaint(HWND hwnd, LPPAINTSTRUCT lpPaint)
应用程序响应WM_PAINT 消息以外,不应该调用BeginPaint函数,如果函数执行成功,则返回窗口的显示设备DC句柄,如果函数执行失败,返回NULL lpPaint 函数是一个指向PAINTSTRUCT结构的指针,该结构包含绘制窗口客户区所需要的信息
objectivec
typedef struct tagPAINTSTRUCT {
HDC hdc,
BOOL fErase,
RECT rcPaint
BOOL fResource
BOOL fIncUpdate
Byte rgbReserved[32];
} PAINTSTRUCT *PPAINTSTRUCT
窗口背景绘图操作开始, 窗口客户区填充的颜色和图案, 如果在注册窗口类的WNDCLASSEX 结构中的 hbrBackground 中指定了画刷, 则在BeginPaint 函数调用之前系统会发送WM_ERASEBKGND
消息到窗口过程中,默认窗口过程DefWindowProc会处理这个消息
处理完WM_ERASEBKGND
消息以后返回TRUE,如果默认窗口过程已经处理了消息,那么fErase字段会被设置为0
擦除背景为了应用程序的新输出与不相关的信息混合,把背景画刷改为wndclass.hbrBackground = NULL,然后重新编译运行,程序窗口尺寸是300*180.会发现窗口客户区出现了混乱啊,因为没有擦除背景,
窗口过程收到了WM_PAINT消息,并不代表整个窗口客户区都要重绘,可能需要重绘的区域
只有一小块
,这个区域就成为无效区域
,程序只需要更新改区域就可以了,Windows为每个窗口维护了一个绘图信息结构PAINTSTRUCT,无效区域的坐标就在其中,用的rcPaint字段,使用由BeginPaint函数返回的DC句柄无法在无效区域外进行绘制的,rcPaint 字段是一个RECT结构
windef.h 头文件中定义如下;
scss
typedef strcut tagRECT {
LONG left; // 矩形左上角的X坐标
LONG TOP // 矩形左上角的Y坐标
Long right // 矩形右下角的x坐标
long bottom //矩形右下角的y坐标
} RECT,*PRECT,NEAR *NPRECT, FAR *LPRECT;
这四个字段都是以像素
为单位的,相对与客户区的主客户区左上角(0,0),rcPaint 字段表示的无效区域就是程序需要进行重绘的区域,每次调用 BeginPaint 函数完成相关的绘制操作以后,必须要调用EndPaint函数释放相关资源
scss
BOOL ENDPAINT(HWND Hwnd, PAINTSTRUCT *LPpAINT)
函数返回值始终为非0,即不会失败
arduino
TextOut(HDC hdc, int nXStrar, int nYStart, LPCTSTR lpString, int cchString)
逻辑单位的概念稍后再讲,暂时理解为逻辑单位就是像素单位
ShowWindows 创建之后窗口只是在内存中,在屏幕上显示窗口
UpdateWindow 刷新窗口布局 通过向窗口发送WM_PAINT 消息来更新窗口