Windows程序和消息机制(一):窗口程序的创建

Windows下的消息机制

Windows系统是基于事件的操作系统,它的消息机制是最主要的事件之一,所有的窗口程序都是通过消息跟系统进行交互的。

在传统的C程序中,使用fopen函数打开文件,这个库函数最终会调用系统函数来打开文件,但是消息机制不仅仅是用户调用系统函数, 系统函数会反过调用用户函数。

举个例子,我们点击了窗口中的“关闭”按钮,系统可以感知到这一事件,然后向用户程序发送一条WM_CLOSE的消息,这条消息会进入用户程序的消息队列中等待处理,用户程序有一个循环用来处理接收到的消息,等到这条消息被处理的时候,这条消息会激活用户程序的窗口过程函数来处理这条消息。

系统发送消息到程序,程序接收到消息后的处理统称为窗口过程,最后调用的处理函数就是窗口过程处理函数。

如果发送消息使用SendMessage函数就叫做不进队消息,这种方式发送的消息不会进入用户程序的消息队列,它会直接发送到目标窗口,然后等待窗口过程函数处理完后返回。

如果发送消息使用PostMessage函数就叫做进队消息,这种消息是会进入到消息队列的。

窗口程序要想接收到系统消息需要进行一系列的设置,知道怎么写一个最基础的窗口程序以后,对消息机制就会有更清晰的了解。

Windows窗口程序的实现

窗口程序的创建很简单,主要分为以下几个步骤:

  1. 注册窗口类
  2. 创建窗口及显示窗口
  3. 创建消息循环
  4. 编写窗口过程函数
    接下来详细说下细节。

注册窗口类

注册窗口要做的事情主要是设定窗口的属性和特征,通过调用RegisterClass(&wndclass)完成注册,wndclass是一个叫作WNDCLASS的结构体,它可以设置窗口的很多属性,在注册之前有若干个字段是必须要赋值的,它的结构体如下:

style:指定窗口的样式

  • CS_HREDRAW:当窗口的水平宽度发生变化时窗口进行重绘
  • CS_VREDRAW:代表什么不用说了吧
  • CS_NOCLOSE:没有关闭按钮
  • CS_DBLCLKS:可以发送双击消息
    去掉某个样式:style = style &~CS_XXXX

lpfnWndProc:窗口过程函数,Windows系统中每个窗口都可以有一个窗口过程函数,它的创建过程如下:

  1. 将窗口过程函数赋值给lpfnWndProc
  2. 注册窗口类,调用RegisterClass函数进行注册
  3. 主循环中调用DispatchMessage进行消息派发
    窗口过程函数的声明如下:
typedef LRESULT (CALLBACK *WNDPROC)(HWND,UINT,WPARAM,WPARAM);

LRESULT实际上是long类型,CALLBACK实际上是__stdcall,在VC++开发环境中,默认的编译选项是__cdecl,这种调用约定适用参数可变的函数,因为它是在函数外进行栈桢平衡。如果需要使用__stdcall需要指定,Windows API都遵循__stdcall。在Windows NT4.0以后程序中要使用回调函数必须遵循__stdcall。
LoadIcon:这个字段用来指定图标,它的第二个参数用来指定一个资源名,Windows SDK的资源命名规范一般是ID+类型,比如说按钮就是IDB_XXX。
hbrBackground:当窗口发生重绘时,系统使用这个字段指定的背景色作为重绘的颜色
贴上一段参考代码:

WNDCLASSEXW wcex;

    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.cbSize = sizeof(WNDCLASSEXW);
    wcex.lpfnWndProc = (WNDPROC)WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInst;
    wcex.hCursor = LoadCursor(nullptr, MAKEINTRESOURCE(IDI_ZZPEANALYZER));
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ZZPEANALYZER));
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    wcex.lpszMenuName = MAKEINTRESOURCE(IDC_ZZPEANALYZER);
    RegisterClassExW(&wcex);

创建窗口及显示窗口

创建窗口没有什么特别需要讲解的地方,下面将要贴出的代码说明了一切,不过还是有需要注意的地方。

  1. 当调用CreateWindows函数时,传递参数hWndParent时,如果指定为WS_CHILD,说明创建的是子窗口,子窗口会被父窗口所影响,具体影响如下:
  2. 调用UpdateWindow函数会发送一个WM_PAINT消息来刷新窗口
HWND hWnd = CreateWindowEx(WS_EX_ACCEPTFILES,szWindowClass,szTitle,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,700,500,NULL,NULL,hInstance,NULL);
    if (!hWnd)
    {
        return FALSE;
    }
    ShowWindow(hWnd, nShowCmd);
    UpdateWindow(hWnd);

创建消息循环

每个程序都有一个消息队列,可以通过GetMessage函数获取队列中的消息。
GetMessage原型如下:

BOOL GetMessage(LPMSG lpMsg,//消息结构体
                HWND hWnd,//接收指定窗口的消息
                UINT wMsgFilterMin,//获取的消息最小值
                UINT wMsgFilterMax);//获取的消息最大值

GetMessage只有在接收到WM_QUIT时才会返回0,如果出现错误会返回-1,主循环一般如下:

MSG msg;
while (GetMessage(&msg,nullptr,0,0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

GetMessage获取到消息队列中的消息以后,会执行TranslateMessage(),这个函数是用来将虚拟按键转换成字符的,比如说用户按下某个按键会产生WM_KEYDOWN和WM_KEYUP两个虚拟键代码,TranslateMessage()会将这两个虚拟键码转为WM_CHAR,并将这个message放到消息队列中,当下次调用GetMessage时这个消息会被调用,TranslateMessage()并不会修改消息,只是会新增一个消息。
DispatchMessage()用来将消息回传给系统,系统会调用窗口过程的回调函数。

PeekMessage
和GetMessage()类似的函数还有PeekMessage,这个函数跟GetMessage()唯一的不同是有一个wRemoveMsg字段,当它是PM_REMOVE的时候在获取到消息后会将消息从消息队列中删除,跟GetMessage一致,当它是PM_NOREMOVE时不会将消息从消息队列中移除。

编写窗口过程函数

还记得在注册窗口时绑定的回调函数吗?它就是窗口过程函数的入口。

wcex.lpfnWndProc = (WNDPROC)WndProc;

这个回调函数的声明如下:

参数在上面的注册窗口部分讲解过,就不赘述了。这个回调函数内部的实现主要是通过switch的方式来分类不同的消息处理函数,下面的图可供参考:

WM_CHAR:当用户按下键盘上的一个字符键,这个分支会被调用
WM_PAINT:当窗口的一部分或全部变为无效时就会调用这个分支,具体有以下几种情况触发:

  • 窗口刚创建时
  • 调用UpdateWindow时
  • 窗口大小变化时(前提需要注册窗口时设置了CS_HREDRAW和CS_VREDRAW标志)
  • 窗口被遮盖再显示时
    注意,只有在WM_PAINT分支内部才可以使用BeginPaint(对应EndPaint),在外部只能通过GetDC函数来获取DC(对应ReleaseDC)。

DC全称Device Context,它包含了显示器、图形设备驱动器的一些信息,要在窗口上显示文字或者显示图形都需要用到DC,如果没有DC,我们就需要了解图形设备和它的驱动程序,通过调用驱动程序的接口来完成图形的显示,而图形设备有很多种,每一种都是不一样的,如果真要去了解这些驱动程序再作画,那工作量也太大了,因此微软提供了DC,由它去跟图形设备驱动程序打交道,我们只要使用它就可以直接画图了

WM_CLOSE:当用户单击窗口上的关闭按钮时,系统会发送一条WM_CLOSE消息。
DestroyWindow函数会向窗口过程发送WM_DESTROY消息,DestroyWindow函数执行完窗口就已经被销毁了,但是程序没还没有退出,因此需要在WM_DESTROY分支里面进行最后的处理。
如果程序没有响应WM_CLOSE消息,系统就会调用DefWindowProc函数,这个函数会调用DestroyWindow函数来响应这条WM_CLOSE消息。

WM_DESTROY:在这个分支里调用了PostQuitMessage,它会向消息队列中发送一条WM_QUIT消息,之前我们说过GetMessage在收到WM_QUIT会返回0,当它返回0时,主循环就停止了。传递给PostQuitMessage的参数会作为WM_QUIT消息的wParam参数,这个值通常作为WinMain函数的返回值。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值