01 Windows编程模型

1、名称解释

SDK: 软件开发工具包(Software Development Kit),一般都是一些被软件工程师用于为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。

API函数: Windows操作系统提供给应用程序编程的接口(Application Programming Interface)C语言实现,一般在 Windows.h 文件中进行的声明。

窗口:

01 Windows编程模型_第1张图片

句柄: 句柄( HANDLE) 是 Windows 程序中一个重要的概念。 在 Windows 程序中, 有各种各样的资源( 窗口、 图标、光标,画刷等), 系统在创建这些资源时会为它们分配内存, 并返回标识这些资源的标识号, 即句柄,就像C语言中打开文件返回的fd一样。常用的句柄包括:窗口句柄(HWND),图标句柄(HICON),光标句柄( HCURSOR) 和画刷句柄( HBRUSH)。

窗口过程:窗口过程函数是一个回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。

 

2、Window编程模型

01 Windows编程模型_第2张图片

1、消息机制

Windows 程序设计是一种事件驱动方式的程序设计模式,主要是基于消息的。每一个 Windows 应用程序开始执行后, 系统都会为该程序创建一个消息队列, 这个消息队列用来存放该程序创建的窗口的消息。 例如, 当我们按下鼠标左键的时候, 将会产生WM_LBUTTONDOWN 消息, 系统会将这个消息放到窗口所属的应用程序的消息队列中,等待应用程序的处理。 Windows 将产生的消息依次放到消息队列中, 而应用程序则通过一个消息循环不断地从消息队列中取出消息, 并进行响应。 这种消息机制, 就是 Windows程序运行的机制。

01 Windows编程模型_第3张图片

具体流程如下:

  1. 操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中。
  2. 应用程序在消息循环中调用 GetMessage 函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理,例如,放弃对某些消息的响应,或者调用 TranslateMessage 产生新的消息。
  3. 应用程序调用 DispatchMessage,将消息回传给操作系统。消息是由 MSG 结构体对象来表示的,其中就包含了接收消息的窗口的句柄。因此, DispatchMessage 函数总能进行正确的传递。
  4. 系统利用 WNDCLASS 结构体的 lpfnWndProc 成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理( 即“ 系统给应用程序发送了消息”)。

 

2、消息

在 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 指定了发出消息时光标位置的屏幕坐标。  
*/

3、Window编程模型

当 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类)
  2. 注册窗口(RegisterClass()函数)
  3. 创建窗口(CreateWindow()函数)
  4. 显示及更新窗口(ShowWindow()、updateWindow())
  5. 通过循环取消息(GetMessage())
  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 应用程序的主要代码部分就集中在窗口过程函数中。窗口过程函数的声明形式,如下所示:

01 Windows编程模型_第4张图片

窗口过程函数的名字可以随便取, 如 WinSunProc, 但函数定义的形式必须和上述声明的形式相同。系统通过窗口过程函数的地址( 指针) 来调用窗口过程函数, 而不是名字。WindowProc 函数的 4 个参数分别对应消息的窗口句柄、消息代码、消息代码的两个附加参数。一个程序可以有多个窗口,窗口过程函数的第 1 个参数 hwnd 就标识了接收消息的特定窗口。

代码实现部分见链接:https://blog.csdn.net/Chiang2018/article/details/88750962

使用Windows API创建窗口

你可能感兴趣的:(MFC入门教程)