SDK: 软件开发工具包(Software Development Kit),一般都是一些被软件工程师用于为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。
API函数: Windows操作系统提供给应用程序编程的接口(Application Programming Interface)C语言实现,一般在 Windows.h 文件中进行的声明。
窗口:
句柄: 句柄( HANDLE) 是 Windows 程序中一个重要的概念。 在 Windows 程序中, 有各种各样的资源( 窗口、 图标、光标,画刷等), 系统在创建这些资源时会为它们分配内存, 并返回标识这些资源的标识号, 即句柄,就像C语言中打开文件返回的fd一样。常用的句柄包括:窗口句柄(HWND),图标句柄(HICON),光标句柄( HCURSOR) 和画刷句柄( HBRUSH)。
窗口过程:窗口过程函数是一个回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。
Windows 程序设计是一种事件驱动方式的程序设计模式,主要是基于消息的。每一个 Windows 应用程序开始执行后, 系统都会为该程序创建一个消息队列, 这个消息队列用来存放该程序创建的窗口的消息。 例如, 当我们按下鼠标左键的时候, 将会产生WM_LBUTTONDOWN 消息, 系统会将这个消息放到窗口所属的应用程序的消息队列中,等待应用程序的处理。 Windows 将产生的消息依次放到消息队列中, 而应用程序则通过一个消息循环不断地从消息队列中取出消息, 并进行响应。 这种消息机制, 就是 Windows程序运行的机制。
具体流程如下:
在 Windows 程序中,消息是由 MSG 结构体来表示的:
/* MSG结构具有如下形式:*/
typedef struct tagMSG
{
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
/*
MSG结构中包含了线程的消息队列中的消息信息。
成员:
hwnd 标识了接收的消息的窗口过程所属的窗口的句柄。 一个消息一般都是与某个窗口相关联的。例如,在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。
message 指定了消息号。消息对应的数值通常定义为WM_XXX 宏(WM 是 Window Message 的缩写)的形式
wParam 指定了消息的附加信息。具体的含义与message成员的值有关,实际上就是unsigned int。
lParam 指定了消息的附加信息。具体的含义与message成员的值有关,实际上就是long。
time 指定了发出消息的时间。
pt 指定了发出消息时光标位置的屏幕坐标。
*/
当 Windows 操作系统启动一个程序时,它调用的就是该程序的 WinMain 函数,相当于C语言中的main()函数一样。WinMain函数的声明在window.h中,所以在编程前,需要在文件中新加window.h文件。
WinMain函数的原型声明如下:
/*
WINAPI 以及之后将会遇见的CALLBACK都是 __stdcall的宏定义,表示该函数的函数参数都是由右向左入栈,
且函数返回前自动清空堆栈。
hInstance 表示该程序当前运行的实例的句柄,这是一个数值。
hPrevInstance 表示当前实例的前一个实例的句柄。通常为NULL
lpCmdLine 是一个以空终止的字符串, 指定传递给应用程序的命令行参数.
nCmdShow 指定程序的窗口应该如何显示,例如最大化、最小化、隐藏等。
*/
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPreInstance,
LPSTR lpCmdLine,
int nCmdShow
);
一个完整的Win32程序的实现步骤大概可以分为以下6步,下面,将详细介绍这6步的过程,如若无兴趣,可以直接阅读最下方的示例代码:
1、设计窗口
一个完整的窗口具有许多特征, 包括光标( 鼠标进入该窗口时的形状)、 图标、 背景色等。所以在创建窗口前,我们 也必须对窗口进行设计, 指定窗口的特征。 窗口的特征就是由 WNDCLASS 结构体来定义的。
WNDCLASS 结构体的定义如下:
typedef struct tagWNDCLASSW {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCWSTR lpszMenuName;
LPCWSTR lpszClassName;
} WNDCLASS;
/*
style 指定窗口的样式。常用的选项有:
CS_HREDRAW: 当窗口水平方向上的宽度发生变化时, 将重新绘制整个窗口。 当窗口发生重绘时, 窗口中的文字和图形将被擦除。如果没有指定这一样式,那么在水平方向上调整窗口宽度时,将不会重绘窗口。
CS_VREDRAW:当窗口垂直方向上的高度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,那么在垂直方向上调整窗口高度时,将不会重绘窗口。
CS_NOCLOSE:禁用系统菜单的 Close 命令,这将导致窗口没有关闭按钮。
CS_DBLCLKS:当用户在窗口中双击鼠标时,向窗口过程发送鼠标双击消息。
lpfnWndProc 是一个函数指针,指向窗口过程函数。lpfnWndProc成员变量的类型是 WNDPROC,它的定义如下:
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
cbClsExtra: 用于存储类的附加信息,一般我们将这个参数设置为 0。
cbWndExtra:窗口附加内存,一般我们将这个参数设置为 0。
hInstance 指定包含窗口过程的程序的实例句柄。
hIcon 指定窗口类的图标句柄。这个成员变量必须是一个图标资源的句柄,可以调用 LoadIcon 函数来加载一个图标资源,返回系统分配给该图标的句柄。该函数的原型声明如下所示:
HICON LoadIcon( HINSTANCE hInstance, LPCTSTR lpIconName)
hCursor 指定窗口类的光标句柄。这个成员变量必须是一个光标资源的句柄,可以调用 LoadCursor 函数来加载一个光标资源, 返回系统分配给该光标的句柄。该函数的原型声明如下所示:
HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName);
hbrBackground 指定窗口类的背景画刷句柄。当窗口发生重绘时,系统使用这里指定的画刷来擦除窗口的背景。我们可以调用 GetStockObject 函数来得到系统的标准画刷。 需要进行类型转换。GetStockObject 函数的原型声明如下所示:
HGDIOBJ GetStockObject( int fnObject);
lpszMenuName 是一个以空终止的字符串, 指定菜单资源的名字。
lpszClassName是一个以空终止的字符串,指定窗口类的名字。
*/
2、注册窗口
设计完窗口类( WNDCLASS) 后, 需要调用 RegisterClass 函数对其进行注册,注册成功后, 才可以创建该类型的窗口。 注册函数的原型声明如下:
/* 该函数只有一个参数, 即上一步骤中所设计的窗口类对象的指针。 */
ATOM RegisterClass(CONST WNDCLASS *lpWndClass);
3、创建窗口
设计好窗口类并且将其成功注册之后, 就可以用 CreateWindow 函数产生这种类型的窗口了。 CreateWindow 函数的原型声明如下:
HWND CreateWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x,int y,
int nWidth, int nHeight,HDWN hWndParent,HMENU hMenu,HANDLE hInstance,LPVOID lpParam);
/*
lpClassName 指定窗口类的名称。
lpWindowName 指定窗口的名字。也就是标题
dwStyle 指定创建的窗口的样式,要注意区分 WNDCLASS 中的 style 成员与 CreateWindow 函数的 dwStyle 参数, 前者是指定窗口类的样式, 基于该窗口类创建的窗口都具有这些样式, 后者是指定某个具体的窗口的样式。我们可以给创建的窗口指定WS_OVERLAPPEDWINDOW类型,这是一种多种窗口类型的组合类型。
x,y,nWidth,nHeight 分别指定窗口左上角的 x,y 坐标,窗口的宽度,高度。
hWndParent 指定被创建窗口的父窗口句柄。
hMenu 指定窗口菜单的句柄。
hInstance 指定窗口所属的应用程序实例的句柄。
lpParam:作为 WM_CREATE 消息的附加参数 lParam 传入的数据指针。在创建多文档界面的客户窗口时, lpParam 必须指向 CLIENTCREATESTRUCT 结构体。一般将这个参数设置为 NULL。
返回值:如果窗口创建成功,函数将返回系统为该窗口分配的句柄,否则,返回NULL。
*/
4、显示及更新窗口
窗口创建之后,我们要让它显示出来,这就跟汽车生产出来后要推向市场一样。调用函数 ShowWindow 来显示窗口,该函数的原型声明如下所示:
/*
hWnd:显示的窗口的窗口句柄
nCmdShow 指定了窗口显示的状态:
SW_HIDE: 隐藏窗口并激活其他窗口。
SW_SHOW: 在窗口原来的位置以原来的尺寸激活和显示窗口。
SW_SHOWMAXIMIZED: 激活窗口并将其最大化显示。
SW_SHOWMINIMIZED: 激活窗口并将其最小化显示。
SW_SHOWNORMAL: 激活并显示窗口。如果窗口是最小化或最大化的状态,系统将其恢复到原来的尺寸和大小。
*/
BOOL ShowWindow( HWND hWnd, int nCmdShow );
在调用 ShowWindow 函数之后, 我们紧接着调用 UpdateWindow 来刷新窗口。UpdateWindow 函数的原型声明如下:
/* hWnd 指的是创建成功后的窗口的句柄 */
BOOL UpdateWindow( HWND hWnd );
5、循环获取消息
在创建窗口、显示窗口、更新窗口后,我们需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。要从消息队列中取出消息,我们需要调用 GetMessage()函数,该函数的原型声明如下:
/*
lpMsg 获取到的一个消息( MSG)结构体
hWnd 指定接收属于哪一个窗口的消息。通常我们将其设置为 NULL,用于接收属于调用线程的所有窗口的窗口消息。
wMsgFilterMin 指定要获取的消息的最小值,通常设置为 0。
wMsgFilterMax 指定要获取的消息的最大值。如果 wMsgFilterMin 和 wMsgFilter Max 都设置为 0, 则接收所有消息。
返回值:函数只有在接收到 WM_QUIT 消息时,才返回 0。如果出现了错误,该函数返回-1。
*/
BOOL WINAPI GetMessageW(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax);
在获取到消息后,我们还需要对消息进行一些必要的处理,例如翻译以及分发出去。TranslateMessage 函数用于将虚拟键消息转换为字符消息。例如,它可以 WM_KEYDOWN 和 WM_KEYUP 消息的组合转换为一条 WM_CHAR 消息。它并不会修改原有的消息,它只是产生新的消息并投递到消息队列中。DispachMessage 实际上是将消息回传给操作系统,由操作系统调用窗口过程函数对消息进行处理( 响应)。它们的函数声明如下:
/* lpMsg 翻译的消息的地址 */
BOOL WINAPI TranslateMessage(CONST MSG *lpMsg);
/* lpMsg 翻译的消息的地址 */
LRESULT WINAPI TranslateMessage(CONST MSG *lpMsg);
6、窗口过程函数
在完成上述步骤后,剩下的工作就是编写一个窗口过程函数, 用于处理发送给窗口的消息。 一个 Windows 应用程序的主要代码部分就集中在窗口过程函数中。窗口过程函数的声明形式,如下所示:
窗口过程函数的名字可以随便取, 如 WinSunProc, 但函数定义的形式必须和上述声明的形式相同。系统通过窗口过程函数的地址( 指针) 来调用窗口过程函数, 而不是名字。WindowProc 函数的 4 个参数分别对应消息的窗口句柄、消息代码、消息代码的两个附加参数。一个程序可以有多个窗口,窗口过程函数的第 1 个参数 hwnd 就标识了接收消息的特定窗口。
代码实现部分见链接:https://blog.csdn.net/Chiang2018/article/details/88750962
使用Windows API创建窗口