HELLOWIN详解2

上节对 WndProc 已经讲解了,不过后面的部分应该有点难懂,因为 句柄  还是没谈到。

下面将来了解一下什么是 句柄 以及 句柄的作用。

一个句柄是指使用的一个唯一的整数值,即一个4字节(64位程序中为8字节)长的数值,来标识应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。应用程序能够通过句柄访问相应的对象的信息,但是句柄不是指针,程序不能利用句柄来直接阅读文件中的信息。如果句柄不在I/O文件中,它是毫无用处的。句柄是Windows用来标志应用程序中建立的或是使用的唯一整数,Windows大量使用了句柄来标识对象。

句柄与普通指针的区别在于,指针包含的是引用对象的内存地址,而句柄则是由系统所管理的引用标识,该标识可以被系统重新定位到一个内存地址上。这种间接访问对象的模式增强了系统对引用对象的控制。

WINDOWS程序中并不是用物理地址来标识一个内存块,文件,任务或动态装入模块的。相反,WINDOWS API给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。

一个句柄,只有当唯一地确定了一个项目的时候,它才开始有意义。句柄对应着项目表中的一项,而只有WINDOWS本身才能直接存取这个表,应用程序只能通过API函数来处理不同的句柄

总而言之:句柄就是对窗口及窗口的控件进行标识的,如果要改变窗口或它的控件的话,就需要指定窗口或控件对它进行改变。

这样说应该明白了吧。

让我们再来看一看之前的代码吧!

#include<windows.h>  
#include<mmsystem.h>  
#pragma comment(lib,"WINMM.LIB")  
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);  
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)  
{  
     static TCHAR szAppName[] = TEXT("HelloWin");  
     HWND hwnd;  
     MSG msg;  
     WNDCLASS wndclass;  
     wndclass.style = CS_HREDRAW | CS_VREDRAW;  
     wndclass.lpfnWndProc = WndProc;  
     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 = szAppName;  
     if (!RegisterClass(&wndclass))  
     {  
         MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);  
         return 0;  
     }  
    hwnd = CreateWindow(szAppName, TEXT("The Hello Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_U SEDEFAULT, NULL, NULL, hInstance, NULL);  
    ShowWindow(hwnd, iCmdShow);  
    UpdateWindow(hwnd);  
    while (GetMessage(&msg, NULL, 0, 0))  
    {  
      TranslateMessage(&msg);  
      DispatchMessage(&msg);   
    }  
    system("pause");  
    return msg.wParam;  
}  
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)  
{  
    HDC hdc;  
    PAINTSTRUCT ps;  
    RECT rect;  
    switch (message)  
   {  
      case WM_CREATE:  
          PlaySound(TEXT("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC);       // 播放声音文件
          return 0;  
      case WM_PAINT:  
          hdc = BeginPaint(hwnd, &ps);        // 标明窗口绘制开始
          GetClientRect(hwnd, &rect);         // 获取窗口客户区尺寸
          DrawText(hdc, TEXT("Hello,Windows 98"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);   // 显示一个文本字符串
          EndPaint(hwnd, &ps);               // 结束窗口绘制
          return 0;  
      case WM_DESTROY:  
          PostQuitMessage(0);                // 将 “退出” 消息插入消息队列
          return 0;  
   }  
      return DefWindowProc(hwnd, message, wParam, lParam);   // 返回 默认的消息处理的结果
}  

或许你早已运行该程序了,发现没有声音!其实是声音文件的位置错了。

你可以把TEXT("hellowin.wav")         改成     TEXT("C:\\Windows\\media\\Ring03.wav")

因为在  C:\\Windows\\media 这个目录下有许多的  wav 型的文件。这样就会有声音了。

上一节的 DrawText() 函数也没讲到,这个地方读者可以自己查找一下资料。

现在我们再来对这个WndProc 进行一个整体的认识:

     这个函数有一个句柄,一个绘制客户区信息的结构体,还有两个附加信息的结构体变量。

     在 switch 语句中总共也就处理了 3 种消息,分别是 窗口创建消息,窗口重绘消息,窗口摧毁消息。

    这三种消息时所用 Windows 程序所不可缺少的。而在处理窗口重绘消息时,几乎总是必不可少的伴随着

    BeginPaint() 和 EndPaint() ,而且这两个函数总是成对存在的。

    窗口如果要摧毁的话,必须调用 PostQuitMessage() 来终止线程。

    最后窗口过程把一些不关心的消息都交给默认的窗口过程函数处理,也就是 DefWindProc() 。

 那再来看一下主体部分的代码吧!

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)  
{  
     static TCHAR szAppName[] = TEXT("HelloWin");  
     HWND hwnd;  
     MSG msg;  
     WNDCLASS wndclass;  
     wndclass.style = CS_HREDRAW | CS_VREDRAW;  
     wndclass.lpfnWndProc = WndProc;  
     wndclass.cbClsExtra = 0;  
     wndclass.cbWndExtra = 0;  
     wndclass.hInstance = hInstance;  
     wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);                   // LoadIcon() 加载图标
     wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);                     // LoadCursor() 加载鼠标
     wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);       // GetStockObject() 获取一个图形对象
     wndclass.lpszMenuName = NULL;  
     wndclass.lpszClassName = szAppName;  
     if (!RegisterClass(&wndclass))                                      // RegisterClass() 为应用程序的窗口注册一个窗口类
     {  
         MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);   //显示消息框
         return 0;  
     }  
    hwnd = CreateWindow(szAppName, TEXT("The Hello Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_U SEDEFAULT, NULL, NULL, hInstance, NULL);            // 基于窗口类创建一个窗口
    ShowWindow(hwnd, iCmdShow);                      // 显示窗口
    UpdateWindow(hwnd);                              // 指示窗口对其自身进行重绘
    while (GetMessage(&msg, NULL, 0, 0))             // GetMessage() 从消息队列获取消息
    {  
      TranslateMessage(&msg);                        // 翻译键盘消息
      DispatchMessage(&msg);                         // 将消息发送给窗口过程
    }  
    system("pause");                         
    return msg.wParam;  

这下我猜你可能看不懂 MSG msg;

MSG是Windows程序中的结构体。在Windows程序中,消息是由MSG结构体来表示的。成员变量含义:

typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
成员变量含义:
第一个 成员变量 hwnd 表示消息所属的窗口。我们通常开发的程序都是窗口应用程序,一个消息一般都是与某个窗口相关联的。例如,在某个 活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。在Windows程序中,用HWND类型的变量来标识窗口。
第二个 成员变量message指定了消息的 标识符。在Windows中,消息是由一个数值来表示的,不同的消息对应不同的数值。但是由于数值不便于记忆,所以Windows将消息对应的数值定义为WM_XXX宏(WM是Window Message的缩写)的形式,XXX对应某种消息的英文拼写的大写形式。例如,鼠标左键按下消息是WM_LBUTTONDOWN,键盘按下消息是WM_KEYDOWN,字符消息是WM_CHAR,等等。在程序中我们通常都是以WM_XXX宏的形式来使用消息的。
提示:如果想知道WM_XXX消息对应的具体数值,可以在 VC 开发环境中选中WM_XXX,然后单击鼠标右键,在弹出菜单中选择goto definition,即可看到该宏的具体定义。跟踪或查看某个 变量的定义,都可以使用这个方法。
第三、第四个 成员变量wParam和lParam,用于指定消息的附加信息。例如,当我们收到一个 字符消息的时候,message 成员变量的值就是WM_CHAR,但用户到底输入的是什么字符,那么就由wParam和lParam来说明。wParam、lParam表示的信息随消息的不同而不同。如果想知道这两个 成员变量具体表示的信息,可以在MSDN中关于某个具体消息的说明文档查看到。读者可以在VC++的开发环境中通过goto definition查看一下WPARAM和LPARAM这两种类型的定义,可以发现这两种类型实际上就是unsigned int和long。
最后两个 变量分别表示消息投递到 消息队列中的时间和鼠标的当前位置。
那么 WNDCLASS 又是什么呢?

 从名字上看它感觉是个类,但是C语言里是没有类的,其实他也是个结构体!

WNDCLASS是一个由系统支持的结构,用来储存某一类窗口的信息,如ClassStyle,消息处理函数,Icon,Cursor,背景Brush等。也就是说,CreateWindow只是将某个WNDCLASS定义的窗体变成实例。

构WNDCLASS包含一个窗口类的全部信息,也是Windows编程中使用的基本数据结构之一,应用程序通过定义一个窗口类确定窗口的属性

我们再来看一下它的结构体成分:

typedef struct _WNDCLASS {
UINT style;// 窗口类型
WNDPROC lpfnWndProc;//窗口处理函数
int cbClsExtra;//窗口扩展
int cbWndExtra;//窗口实例扩展
HINSTANCE hInstance;//实例句柄
HICON hIcon;//窗口的最小化图标
HCURSOR hCursor;//窗口鼠标光标
HBRUSH hbrBackground;//窗口背景色
LPCTSTR lpszMenuName;//窗口菜单
LPCTSTR lpszClassName;// 窗口类名
} WNDCLASS, *LPWNDCLASS;

看到这个结构体,那么它下面的一部分内容是不是也能理解了呢!

后面的是它的一些值,由于本节侧重点不在这里,这里就不介绍了,请大家自己动手了解一下!!! 

下面请大家再看到 RegisterClass()

RegisterClass注册后再调用CreateWindow函数中使用的窗口类

该函数的定义如下:

ATOM  RegisterClass(__inCONST WNDCLASS *lpWndClass);

lpWndClass:指向一个WNDCLASS结构的指针。在将它传递给函数之前,必须在该结构中填充适当的类属性

ATOM类型为Windows中定义的新数据类型,其即unsigned short类型。

如果函数成功,返回值是唯一标识已注册的类的一个原子;如果函数失败,返回值为0。若想获得更多错误信息,请调用GetLastError函数。

可以看到如果没有注册成功的话,MessageBox() 函数就会给出提示!

至于为啥会注册失败,那是因为很久以前Windous98 不支持宽字符啦!

再来看函数余下部分,现在发现就只剩下几个函数不清楚的了!

hwnd = CreateWindow(szAppName, TEXT("The Hello Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_U SEDEFAULT, NULL, NULL, hInstance, NULL);  
    ShowWindow(hwnd, iCmdShow);  
    UpdateWindow(hwnd);  
    while (GetMessage(&msg, NULL, 0, 0))  
    {  
      TranslateMessage(&msg);  
      DispatchMessage(&msg);   
    }  
    system("pause");  
    return msg.wParam;  

来看一下CreatWindow() 函数:

windowsAPI函数,在WinUser.h中根据是否已定义Unicode被分别定义为CreateWindowW和CreateWindowA,然后两者又被分别定义为对CreateWindowExW和CreateWindowExA函数的调用

来看一下函数原型:

HWND CreateWindow(
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HANDLE hlnstance,
LPVOID lpParam);

主要参数:

lpClassName

指向一个空结束的字符串或整型数atom。如果该参数是一个 整型量,它是由此前调用theGlobalAddAtom函数产生的全局量。这个小于0xC000的16位数必须是lpClassName参数字的低16位,该参数的高位必须是0。
如果lpClassName是一个 字符串,它指定了窗口的类名。这个类名可以是任何用函数RegisterClass注册的类名,或是任何预定义的控制类名。请看说明部分的列表。
LPWindowName
指向一个指定窗口名的空结束的字符串 指针
如果窗口风格指定了标题条,由lpWindowName指向的窗口标题将显示在标题条上。当使用Createwindow函数来创建控制例如按钮,选择框和静态控制时,可使用lpWindowName来指定控制文本。
dwStyle
指定创建窗口的风格。该参数可以是下列窗口风格的组合再加上说明部分的控制风格。
X
指定窗口的初始水平位置。对一个层叠或弹出式窗口,X参数是屏幕坐标系的窗口的左上角的初始X坐标。对于子窗口,x是子窗口左上角相对父窗口客户区左上角的初始X坐标。如果该参数被设为CW_USEDEFAULT则系统为窗口选择缺省的左上角坐标并忽略Y参数。CW_USEDEFAULT只对层叠窗口有效,如果为弹出式窗口或子窗口设定,则X和y参数被设为零。
Y
指定窗口的初始垂直位置。对一个层叠或弹出式窗口,y参数是屏幕坐标系的窗口的左上角的初始y坐标。对于子窗口,y是子窗口左上角相对父窗口客户区左上角的初始y坐标。对于列表框,y是列表框客户区左上角相对父窗口客户区左上角的初始y坐标。如果层叠窗口是使用WS_VISIBLE风格位创建的并且X参数被设为CW_USEDEFAULT,则系统将忽略y参数。
nWidth
以设备单元指明窗口的宽度。对于层叠窗口,nWidth或是屏幕坐标的窗口宽度或是CW_USEDEFAULT。若nWidth是CW_USEDEFAULT,则系统为窗口选择一个缺省的高度和宽度:缺省宽度为从初始X坐标开始到屏幕的右边界,缺省高度为从初始Y坐标开始到目标区域的顶部。CW_USEDEFAULT只对层叠窗口有效;如果为弹出式窗口和子窗口设定CW_USEDEFAULT标志则nWidth和nHeight被设为零。
nHeight
以设备单元指明窗口的高度。对于层叠窗口,nHeight是屏幕坐标的窗口宽度。若nWidth被设为CW_USEDEFAULT,则系统忽略nHeight参数。
hWndParent
指向被创建窗口的父窗口或所有者窗口的 句柄。若要创建一个子窗口或一个被属窗口,需提供一个有效的 窗口句柄。这个参数对弹出式窗口是可选的。Windows NT 5.0;创建一个消息窗口,可以提供HWND_MESSAGE或提供一个己存在的消息窗口的句柄。
hMenu
菜单句柄,或依据窗口风格指明一个子窗口标识。对于层叠或弹出式窗口,hMenu指定窗口使用的菜单:如果使用了菜单类,则hMenu可以为NULL。对于子窗口,hMenu指定了该子窗口标识(一个 整型量),一个对话框使用这个整型值将事件通知父类。应用程序确定子窗口标识,这个值对于相同父窗口的所有子窗口必须是唯一的。
hlnstance
与窗口相关联的模块实例的 句柄
lpParam
指向一个值的 指针,该值传递给窗口 WM_CREATE消息。该值通过在IParam参数中的 CREATESTRUCT结构传递。如果应用程序调用CreateWindow创建一个MDI客户窗口,则lpParam必须指向一个CLIENTCREATESTRUCT结构。
返回值:如果函数成功,返回值为新窗口的句柄:如果函数失败,返回值为NULL。若想获得更多错误信息,请调用GetLastError函数。

备注

在返回前,CreateWindow给窗口过程发送一个WM_CREATE消息。对于层叠,弹出式和子窗口,CreateWindow给窗口发送WM_CREATE,WM_GETMINMAXINFO和WM_NCCREATE消息。消息WM_CREATE的IParam参数包含一个指向CREATESTRUCT结构的指针。如果指定了WS_VISIBLE风格,CreateWindow向窗口发送所有需要激活和显示窗口的消息。

接下来就看ShowWindow() 函数了。

函数功能:该函数设置指定窗口的显示状态。

函数原型:BOOL ShowWindow(HWND hWnd, int nCmdShow);

hWnd:指窗口句柄。

nCmdShow:指定窗口如何显示。如果发送应用程序的程序提供了STARTUPINFO结构,则应用程序第一次调用ShowWindow时该参数被忽略。否则,在第一次调用ShowWindow函数时,该值应为在函数WinMain中nCmdShow参数。在随后的调用中,该参数可以为下列值之一:

返回值:

如果窗口之前可见,则返回值为非零。如果窗口之前被隐藏,则返回值为零。

备注:

用程序第一次调用ShowWindow时,应该使用WinMain函数的nCmdshow参数作为它的nCmdShow参数。在随后调用ShowWindow函数时,必须使用列表中的一个给定值,而不是由WinMain函数的nCmdSHow参数指定的值。

同样这两个函数关于值的讲解请自行百度。

大家再看 UpdateWindow() 函数。从名字上就知道是个更新窗口的函数!

如果窗口更新的区域不为空,UpdateWindow函数就发送一个WM_PAINT消息来更新指定窗口的客户区。函数绕过应用程序的消息队列,直接发送WM_PAINT消息给指定窗口的窗口过程,如果更新区域为空,则不发送消息。

函数原型:    

BOOL UpdateWindow(
HWND hWnd // 窗口的句柄

);

如果函数调用成功,返回值为非零值。

如果函数调用不成功,返回值为零。

现在就只剩下这几个函数了:

 while (GetMessage(&msg, NULL, 0, 0))  
    {  
      TranslateMessage(&msg);  
      DispatchMessage(&msg);   
    }  
    system("pause");  
    return msg.wParam;

GetMessage() :

GetMessage是从调用线程的消息队列里取得一个消息并将其放于指定的结构。此函数可取得与指定窗口联系的消息和由PostThreadMessage寄送的线程消息。此函数接收一定范围的消息值。GetMessage不接收属于其他线程或应用程序的消息。获取消息成功后,线程将从消息队列中删除该消息。函数会一直等待直到有消息到来才有返回值。

函数声明:

GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax)

参数:
lpMsg:指向MSG结构的 指针,该结构从 线程消息队列里接收消息信息。
hWnd:取得其消息的窗口的句柄。当其值取NULL时,GetMessage为任何属于调用线程的窗口检索消息,线程消息通过PostThreadMessage寄送给调用线程。
wMsgFilterMin:指定被检索的最小消息值的整数。
wMsgFilterMax:指定被检索的最大消息值的整数。
返回值:如果函数取得WM_QUIT之外的其他消息,返回非零值。如果函数取得WM_QUIT消息,返回值是零。如果出现了错误,返回值是-1。例如,当hWnd是无效的 窗口句柄或lpMsg是无效的 指针时。若想获得更多的错误信息,请调用GetLastError函数。

备注:

应用程序通常用返回值来确定是否终止主 消息循环并退出程序。
GetMesssge只接收与参数hWnd标识的窗口或子窗口相联系的消息,子窗口由函数 IsChild决定,消息值的范围由参数wMsgFilterMin和wMsgFilterMax给出。如果hWnd为NULL,则GetMessage接收属于调用线程的窗口的消息,线程消息由函数PostThreadMessage寄送给调用线程。GetMessage不接收属于其他线程或其他线程的窗口的消息,即使hWnd为NULL。由PostThreadMessage寄送的线程消息,其消息hWnd值为NULL。如果wMsgFilterMin和wMsgFilterMax都为零,GetMessage返回所有可得的消息(即,无范围过滤)。
常数 WM_KEYFIRST和WM_KEYLAST可作为过滤值取得与键盘输入相关的所有消息:常数WM_MOUSEFIRST和WM_MOUSELST可用来接收所有的鼠标消息。如果wMsgFilterMin和wMsgFilterMax都为零,GetMessage返回所有可得的消息(即,无范围过滤)。
GetMessage不从队列里清除WM.PAINT消息。该消息将保留在队列里直到处理完毕。
注意,此函数的返回值可非零、零或-1,应避免如下代码出现:
while(GetMessage(IpMsg,hwnd,0,0))…
返回-1时处理错误而不是继续循环

循环里的函数:

TranslateMessage函数用于将虚拟键消息转换为字符消息。字符消息被寄送到调用线程的消息队列里,当下一次线程调用函数GetMessagePeekMessage时被读出

函数原型:

BOOL TranslateMessage( CONST MSG*lpMsg );
IpMsg:指向含有消息的MSG结构的 指针,该结构里含有用函数GetMessage或PeekMessage从调用线程的 消息队列里取得的消息信息。
返回值:如果消息被转换(即,字符消息被寄送到调用线程的消息队列里),返回非零值。如果消息是WM_KEYDOWN,WM_KEYUP WM_SYSKEYDOWN或WM_SYSKEYUP,返回非零值,不考虑转换。如果消息没被转换(即,字符消息没被寄送到调用线程的消息队列里),返回值是零。
备注:此函数不修改由参数IpMsg指向的消息。

   DispatchMessage():

函数功能:该函数分发一个消息给窗口程序。通常消息从GetMessage函数获得或者TranslateMessage函数传递的。消息被分发到回调函数(过程函数),作用是消息传递给操作系统,然后操作系统去调用我们的回调函数,也就是说我们在窗体的过程函数中处理消息。

函数原型:LONG DispatchMessage(CONST MSG*lpmsg);

参数:
lpmsg:指向含有消息的MSG结构的 指针
返回值:返回值是窗口程序返回的值。尽管返回值的含义依赖于被调度的消息,但返回值通常被忽略。
备注:MSG结构必须包含有效的消息值。如果参数lpmsg指向一个WM_TIMER消息,并且WM_TIMER消息的参数IParam不为NULL,则调用IParam指向的函数,而不是调用窗口程序。

  下节内容将对整个程序剖析,下次再见啦!


编辑器还不太会用,又出现了这个。。。








猜你喜欢

转载自blog.csdn.net/qq_41413835/article/details/80541932