• 【Windows编程】windows窗口创建过程详解


    前言

    搞windows开发,必须熟悉vs自己的开发工具,这会提高你的开发效率;我用的是vs2013;感觉不错;

    1 应用程序的分类

    在windows平台上大致有一下三类:

    在这里插入图片描述


    其中,dos程序本身没有窗口,其实它的意思是:控制台程序自己并不会创建窗口,而DOS窗口仅仅是dos程序本身向OS借过来的一个窗口,也就是说,dos窗口是OS创建的;

    窗口程序:自己拥有窗口的意思就是你自己程序本身就是可以创建一个窗口,比如你的QQ界面啥的这样


    2 应用程序分类的对比

    在这里插入图片描述


    注意:动态库不能虽然有入口函数,但是不能够独立的执行;


    3 编译工具

    在这里插入图片描述


    其中rc.exe是转呗编译资源文件的,这是在windows独有的,一般我们的窗口资源,图片资源,菜单资源,画饼画刷等资源都是在资源文件中,然后也需要进行编译;最后链接到可执行文件里;

    这些编译工具都是可以找到的,在你的开发工具VS的路径中:
    我的就在这里,可能你们会有 12.0 是11.0 13.0等不一样的差别,并且我存放在C盘,你们要看开发环境在哪个盘了;

    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin

    在这里插入图片描述


    4 windows库文件和头文件

    windows的源码API是不公开的,只提供动态库让我们去使用,只要我们有windows的库文件头文件就可以使用windowsAPI函数了;
    在这里插入图片描述


    在这里插入图片描述


    其实我们开发时候,使用windows.h头文件就可以直接调用windowsAPI,并不需要专门包含其他windef.h winbase.h 。。。。。等头文件;


    5 WinMain函数和MessageBox函数初始

    在这里插入图片描述


    WinMain函数:windows编程时候的入口函数
    hInstance :当前实例句柄,可以理解:可以找到一块内存的变量,找到哪块内存呢?找到你的进程那一块内存;
    可能还有朋友理解不了,想一想WinMain函数被调用之前,是不是进程已经形成了?那从时间角度上来说,WinMain函数就可以被传参进程的内存信息了;这完全没有毛病;
    第二个参数:被废弃;
    第四个参数:就是窗口最大化,最下化,还是正常 显示;


    MessageBox函数功能:就是弹出一个提示框;
    第一个参数:父窗口句柄,也就是希望你的弹出窗口显示在哪个窗口的中呗;如果填NULL,表示弹出的窗口的父窗口是桌面;
    与此同时:HWND是窗口句柄,作用就是找到一块内存,什么内存?就是存放窗口的数据结构的内存;窗口数据结构?是什么?就是你的窗口大小,颜色,背景,菜单等信息组成的数据结构咯;


    6 窗口类


    7 窗口类的分类

    在这里插入图片描述


    系统窗口类:OS创建的窗口就是系统窗口类;比如我们的dos窗口,比如我们的桌面;
    在这里插入图片描述


    如果你在win32项目中,要使用系统窗口类,直接调用CreateWindows函数就可以创建成功,不需要注册窗口类的步骤;
    比如这里创建一个编辑框的窗口类:(连RegisterWindows函数都没有调用)

    在这里插入图片描述


    8 注册窗口类函数

    在这里插入图片描述
    该函数,失败返回0,可以用来检测是否注册窗口类成功,注册窗口类:说直白就是向OS写入一个变类型为WNDXLASS的结构体变量,并且初始化他而已;

    9 注册窗口类的结构体

    在这里插入图片描述


    10 注册全局和局部窗口类

    在这里插入图片描述


    不建议用全局窗口类,微软官方手册说的;
    CS_DBLCLKS:该参数如果没有,那么你的窗口无论点多块的双击都是两次单击;


    11 创建窗口的函数

    在这里插入图片描述


    后缀有Ex的函数,和没有Ex的函数,区别就是Ex的函数多路第一个参数,但是这个参数没有实际的用途,所以也不必要关心;


    先关注,第二个和倒数第二个参数:
    第二个参数:表示注册窗口类的名称;
    倒数第二个参数:表示该窗口和哪个进程相关联;
    在这里插入图片描述


    CreateWindow内部的执行过程:

    在这里插入图片描述


    在这里插入图片描述

    一般来说:我们调用CreateWindow都是为了创建局部窗口了类:
    大概执行步骤就是拿到传入的窗口类名字去内核查找窗口类名字是否匹配(内核的窗口类,是你提前注册好的);
    找到就继续匹配,你传入的instance 局部是否匹配内核的instance, 如果匹配表示你的窗口就是创建在本进程;
    成功后返回一个窗口句柄,供你使用;


    12 创建一个windows的过程步骤

    在这里插入图片描述


    基本基本代码框架:

    #include 
    
    LRESULT CALLBACK WndPro(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
    {
    	switch (msgID)
    	{
    	case WM_DESTROY: //点击关闭按钮
    		PostQuitMessage(0); //使得GetMessage返回0
    		break;
    	default:
    		break;
    	}
    	return DefWindowProc(hWnd,msgID,wParam,lParam);
    }
    int WINAPI WinMain(HINSTANCE hinstance,HINSTANCE pHinstance,LPSTR lpCmdLine,int nShowLine)
    {
    	//注册主窗口类
    	WNDCLASS wc;
    	wc.cbClsExtra = 0;
    	wc.cbWndExtra = 0;
    	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    	wc.hCursor = NULL;						//默认光标
    	wc.hIcon = NULL;						//默认图标 图标就是标题栏左上角那个东西
    	wc.hInstance = hinstance;
    	wc.lpfnWndProc = WndPro;				//窗口过程函数
    	wc.lpszClassName = "WinBase";			//窗口类名字,随便起也行
    	wc.lpszMenuName = NULL;					//无菜单
    	wc.style = CS_HREDRAW | CS_VREDRAW;		//窗口垂直水平变化时候,窗口自动重画
    	RegisterClass(&wc);						//向OS写入 wc变量
    
    
    	// 在内存中创建一个窗口
    	HWND hWnd = CreateWindow("WinBase", /*窗口的类名*/
    		"Windows",						/*窗口标题*/
    		WS_OVERLAPPEDWINDOW,			/*窗口样式*/
    		100, 100, 100, 100,				/*窗口x,y,width,height*/
    		NULL, NULL, hinstance, NULL);	/*父窗口:桌面 菜单:无 窗口是当前进程的 无附加参数*/
    	//显示窗口
    	ShowWindow(hWnd, SW_SHOW);			//SW_SHOW按原样显示,也就是说Create设置的参数
    	//刷新窗口-->就是在此显示一次
    	UpdateWindow(hWnd);
    	//消息循环
    	MSG msg;
    	while(GetMessage(&msg, NULL, 0, 0))
    	{
    		TranslateMessage(&msg);
    		DispatchMessage(&msg);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    TranslateMessage(&msg)的大致执行过程,当我们按下大写字母键盘a时候;

    在这里插入图片描述


    13 创建一个子窗口的方法

    和创建一个Windows一样,只不过,需要一点区别:
    首先肯定也要注册一个子窗口类,当然,这个类只要有一个就可以,一个窗口类是可以被创建为多个窗口的;也就是说,我们又一个子窗口类,就可以调用多次CreateWindow来进行创建窗口了;


    对于CreateWindows函数。必要设置的参数是:
    在这里插入图片描述


    这是创建主窗口,和子窗口的基本框架:

    #include 
    
    LRESULT CALLBACK WndPro(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam)
    {
    	switch (msgID)
    	{
    	case WM_DESTROY: //点击关闭按钮
    		PostQuitMessage(0); //使得GetMessage返回0
    		break;
    	default:
    		break;
    	}
    	return DefWindowProc(hWnd,msgID,wParam,lParam);
    }
    int WINAPI WinMain(HINSTANCE hinstance,HINSTANCE pHinstance,LPSTR lpCmdLine,int nShowLine)
    {
    	//注册主窗口类
    	WNDCLASS wc;
    	wc.cbClsExtra = 0;
    	wc.cbWndExtra = 0;
    	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    	wc.hCursor = NULL;						//默认光标
    	wc.hIcon = NULL;						//默认图标 图标就是标题栏左上角那个东西
    	wc.hInstance = hinstance;
    	wc.lpfnWndProc = WndPro;				//窗口过程函数
    	wc.lpszClassName = "WinBase";			//窗口类名字,随便起也行
    	wc.lpszMenuName = NULL;					//无菜单
    	wc.style = CS_HREDRAW | CS_VREDRAW;		//窗口垂直水平变化时候,窗口自动重画
    	RegisterClass(&wc);						//向OS写入 wc变量
    
    	//注册子窗口类
    	WNDCLASS wc_child;
    	wc_child.cbClsExtra = 0;
    	wc_child.cbWndExtra = 0;
    	wc_child.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    	wc_child.hCursor = NULL;						//默认光标
    	wc_child.hIcon = NULL;						//默认图标 图标就是标题栏左上角那个东西
    	wc_child.hInstance = hinstance;
    	wc_child.lpfnWndProc = DefWindowProc;				//窗口过程函数
    	wc_child.lpszClassName = "Child";			//窗口类名字,随便起也行
    	wc_child.lpszMenuName = NULL;					//无菜单
    	wc_child.style = CS_HREDRAW | CS_VREDRAW;		//窗口垂直水平变化时候,窗口自动重画
    	RegisterClass(&wc_child);
    
    
    	// 在内存中创建一个窗口
    	HWND hWnd = CreateWindow("WinBase", /*窗口的类名*/
    		"Windows",						/*窗口标题*/
    		WS_OVERLAPPEDWINDOW,			/*窗口样式*/
    		100, 100, 100, 100,				/*窗口x,y,width,height*/
    		NULL, NULL, hinstance, NULL);	/*父窗口:桌面 菜单:无 窗口是当前进程的 无附加参数*/
    	// 创建一个子窗口
    
    	HWND hWnd_child = CreateWindow("Child",
    		"child___",
    		WS_CHILD | WS_VISIBLE | WS_OVERLAPPEDWINDOW,
    		0, 0, 40, 40, hWnd, NULL, hinstance, NULL);
    
    	//显示窗口
    	ShowWindow(hWnd, SW_SHOW);			//SW_SHOW按原样显示,也就是说Create设置的参数
    	//刷新窗口-->就是在此显示一次
    	UpdateWindow(hWnd);
    	//消息循环
    	MSG msg;
    	while(GetMessage(&msg, NULL, 0, 0))
    	{
    		TranslateMessage(&msg);
    		DispatchMessage(&msg);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    重点关注这里:
    其他样式方面可以按你的场景进行设计;


    在这里插入图片描述


    14 消息的概念及其作用

    在这里插入图片描述


    在Windows下的消息,其实就是一个结构体信息而已,里面包含了上面PPT的一些成员,组成了一个消息;
    在这里插入图片描述


    注意每个窗口都对应一个窗口处理函数:也就是说,每当你调用一个CreateWindow都会对应一个窗口处理函数;
    但是多个窗口是可以复用一个窗口处理函数的;


    要理解:消息是你应用程序产生的,然后由你操作系统抓到,并把它放入消息队列,再由GetMessage从中取出消息,交给DispatchMessage函数进行派发给窗口处理函数进行处理(这个过程就是OS拿到你的消息,找到你对应的处理消息的函数,进行处理,这里使用的机制就是回调函数的机制,DispatchMessage内部就是回调了你窗口过程函数);


    如DispatchMessage大致的工作流程:
    拿到你的消息,就可以拿到你的窗口句柄,又知道每个窗口句柄对应一个窗口处理函数,自然而然,可以找到你的消息对应窗口处理函数进行调用处理消息;
    并且你发现,在函数内部调用你的窗口处理函数进行传参的四个参数,也就是说我们MSG结构体的前4个成员变量,所以说,我们只要有了消息,就可以进行消息处理是完全没有问题的;
    在这里插入图片描述


    在这里插入图片描述


    所以我们要清楚,当消息产生是要要关注消息是哪个窗口产生的,产生的消息是什么,产生的消息交给谁去处理;


    15 浅谈GetMessage 函数

    函数:该函数获取本进程消息队列中的消息;
    在这里插入图片描述


    第二个参数:指定为NULL,抓取本进程所有窗口的消息,如果为指定句柄,那么他只会抓取该句柄的消息;
    返回值:该函数的返回值,只有在GetMessage抓到一个WM_QUIT消息时候,才会返回0;这个消息是PostQuitMessage(0) 函数调用发出的;


    16 浅谈 TranslateMessage 函数

    在这里插入图片描述


    该函数只会翻译键盘消息,并且翻译可见字符消息,也就是在ASCII表中可见字符,比如字母和数字等;如果不是可见字符的按键消息那么就不会做任何事情;


    17 使用消息

    在微软中,提供了大概有1000多个消息,我们可以直接拿来用;使用消息时候,需要关注三点:
    消息的产生时间;
    消息的附带两个参数,告知的信息;
    消息的一般用法;


    使用消息时候,并不是使用消息的变量去使用,而是使用消息的ID去使用;
    也就是说,不是直接去用MSG定义变量使用消息,而是用我们的MSG里面的一个messageID 去使用;


    WM_DESTROY 消息:
    请注意:这个消息并不是你鼠标点击右上角那个叉叉才产生的,而是窗口被销毁前系统自动把你产生的一个消息哦;
    在这里插入图片描述


    WM_SYSCOMMND:
    点击菜单框都会产生,包括你的边界滚动条,所以一般这个消息在处理的时候,还要通过WParam参数进行判断你按的位置是哪个地方,比如wParam == SC_CLOSE 表示你按的是 关闭按钮,那么你就可以对其进行处理,假如你按的是其他菜单的其他位置你就可以不处理;
    在这里插入图片描述


    在这里插入图片描述


    该消息是自己发送,调用PostQuitMessage发送,并且该消息不用自己处理,让系统处理;


    其实消息种类太多,无法一一说明,这里只是举例,需要还是自己查手册啊;


    18 消息循环的基本原理

    在这里插入图片描述


    GetMessage是个阻塞函数,如果没有抓不到消息,那么就阻塞,如果抓到消息就返回;
    阻塞可不是一个好习惯;所以我们经常需要配合非阻塞函数PeekMessage来使用;


    对比:阻塞和非阻塞的写法:

    // 主消息循环: 阻塞
    	while (GetMessage(&msg, NULL, 0, 0))
    	{	
    			TranslateMessage(&msg);
    			DispatchMessage(&msg);
    	}
    	/****************************************************************/
    	/****************************************************************/
    	/****************************************************************/
    	// 主消息循环: 非阻塞
    	while (1)
    	{
    		if (!PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
    		{
    			//没有消息,空闲处理
    		}
    		else
    		{
    			//有消息
    			if (!GetMessage(&msg, NULL, 0, 0))
    			{
    				return 0;
    			}
    			TranslateMessage(&msg);
    			DispatchMessage(&msg);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    19 发送消息

    我们知道,GetMessage可以从消息队列中抓消息进行处理
    在这里插入图片描述


    但是我们并不知道,消息到底是谁发送的;


    在这里插入图片描述


    首先得明白,不管是系统发的消息,还是程序员自己手动发的消息,在windows中发送消息,只能由这两个函数进行发送;
    SendMessage: 阻塞函数,发送消息,消息没被处理完都不会返回;
    PostMessage: 非阻塞函数;
    我们自己只需要拿到消息的四个参数信息即可使用该两个函数,但是我们知道消息的信息是有6个成员信息组成,其实该函数内部会自己通过一些方式拿到剩余的两个信息:也就是说消息的坐标,消息的时间;我们不关心这两个消息;
    我们要知道,PostMessage函数,发送消息,直接发送到消息队列中的,而SendMessage并不是直接发送消息到消息队列,至于发到哪?后面会讲到;


    20 消息的分类

    在这里插入图片描述


    自定义消息的基本使用步骤:

    在这里插入图片描述


    21 消息队列

    在这里插入图片描述


    22 消息队列分类

    在这里插入图片描述
    系统的消息队列:保存所有进程产生的消息,该消息队列很大:
    消息的产生首先交由给系统的消息队列,再由OS每隔一段时间将消息转发到各个进程的消息队列中;
    GetMessage只会抓取本进程的消息;
    在这里插入图片描述


    23 消息和消息队列的关系

    在这里插入图片描述


    队列消息:消息进队列;
    非队列消息:消息不进队列;
    在这里插入图片描述


    有些消息必须进队列,如WM_QUIT,因为GetMessage要返回,必须在队列抓到该消息;有些消息必须不进队列,如WM_CREATE,因为该消息,在窗口处理函数之前就会产生;
    有些消息可进可不进,如WM_SIZE;


    24 GetMessage函数内部执行过程

    在这里插入图片描述

    1. GetMessage 是阻塞函数,GetMessage 只会从进程还是线程中消息队列获取消息;
    2. 该函数若发现消息队列无消息,就会询问系统的消息队列是否有消息;如果有系统就会吧消息刷新到本进程的消息队列中;
    3. 如果本进程消息队列无消息,系统队列无消息,没有绘图消息,没有定时器消息,没有整理资源,释放内存,那么GetMessage就会阻塞;PeekMessage干的事和GetMessage一样,区别就是最后一个步骤他不会阻塞;

    WI_PAINT消息:在窗口从0到1被创建前,系统会发送一个消息,且该消息不进消息队列,直接调用窗口处理函数;


  • 相关阅读:
    matlab中实现画函数图像添加坐标轴
    CAD进阶练习(四)
    Flink-使用流批一体API统计单词数量
    Vue 中 KeepAlive 内置缓存使用
    11 个 Flutter 最佳实践
    前端面试(2)——页面布局
    Qt 实战(4)信号与槽 | 4.3、信号连接信号
    基于PyQt5和OpenCV库的简单的文档对齐扫描应用程序
    Maven插件mybatis-generator,如何让生成的PO类的field上有对应表字段的注释
    Ubuntu上安装Docker的步骤和指南
  • 原文地址:https://blog.csdn.net/m0_46606290/article/details/128026300