0x00前言

文章中的文字可能存在语法错误以及标点错误,请谅解;

如果在文章中发现代码错误或其它问题请告知,感谢!

0x01 概述

最近在学习windows程序设计的创建窗口这方面的知识,有一些收获,现在将我所学到的内容记录下来,供大家参考。

本例将做一个类似txt文档窗口,可以对用户的键盘输入打印到该窗口上,并且有菜单栏可以单击菜单栏中的退出来退出窗口。

0x02 创建一个完整的窗口程序的步骤

要想创建一个窗口程序,需要以下5个步骤:
(1)注册窗口类(RegisterClassEX);
(2)创建窗口(CreateWindowsEX);
(3)在桌面上显示窗口(ShowWindow);
(4)更新窗口客户区(UpdateWindow);
(5)进入消息获取以及处理循环,直到获取的消息为WM_QUIT时,结束消息循环。

下面分节介绍各步骤的代码实现。

0x03 创建Win32工程

我们一般练习时运行的程序都是使用控制台界面来接收和显示我们的运行结果,但是我们要想使用窗口界面与用户交互的话就应该创建Win32工程。

选择“新建项目”,然后选择“已安装模板”和“Visual C++”,最后选择“Win32项目”。在“名称”框中,键入项目名称,例如“窗口程序”,然后单击“确定”:

在接下来的对话框中选择默认选项即可。

进入到工程之后,发现.cpp文件里有写好的demo程序,我们为了练习,将这个demo程序代码全部注释掉或删掉,然后再继续进行。

0x04 注册窗口类

窗口类是系统在创建窗口时作为模板使用的属性集合。注册窗口类的API函数是ATOM RegisterClassEX(CONST WNDCLASSEXA *Ipwcx),其参数是WNDCLASSEXA结构体指针,该结构体定义了窗口的一些主要的属性,比如光标、图标、背景色和要处理的消息窗口函数等。
WNDCLASSEXA结构体定义如下:

typedef struct tagWNDCLASSEXA {UINT        cbSize;           //WNDCLASSEXA结构的大小UINT        style;                //从这个窗口类派生的窗口具有的风格WNDPROC     lpfnWndProc;      //即window procedure, 窗口消息处理函数指针int         cbClsExtra;       //指定紧跟在窗口类结构后的附加字节数int         cbWndExtra;      //指定紧跟在窗口事例后的附加字节数HINSTANCE   hInstance;            //本模块的实例句柄HICON       hIcon;                //窗口左上角图标的句柄HCURSOR     hCursor;            //光标的句柄HBRUSH      hbrBackground;   //背景画刷的句柄LPCSTR      lpszMenuName;      //菜单名LPCSTR      lpszClassName; //该窗口类的名称HICON       hIconSm;           //小图标句柄
} WNDCLASSEXA, *PWNDCLASSEXA, NEAR *NPWNDCLASSEXA, FAR *LPWNDCLASSEXA;

各字段含义在注释中已经说明,其中需要注意的是lpfnWndProc字段(即window procedure窗口程序,重要),该字段存放消息处理函数的指针,当窗口收到消息时windows会自动调用这个函数进行处理,处理内容由程序设计者自定,比如本例中当接收到键盘输入字符后就让它即时显示在窗口上。

本例所填充的WNDCLASSEXA结构体如下:

char szClassName[] = "MainWClass";
WNDCLASSEX wndclass;                         //定义的结构体名wndclass.cbSize = sizeof(wndclass);           //结构的大小
wndclass.style = CS_HREDRAW | CS_VREDRAW;      //指定如果大小改变就重画
wndclass.lpfnWndProc = MainWndProc;            //窗口的函数指针
wndclass.cbClsExtra = 0;                       //没有额外的类内存
wndclass.cbWndExtra = 0;                       //没有额外的窗口内存
wndclass.hInstance = hInstance;                //句柄实例
wndclass.hIcon = ::LoadIcon(hInstance, (LPSTR)IDM_EXIT);   //使用指定的图标
wndclass.hCursor = ::LoadCursorA(NULL, IDC_ARROW); //使用预定义光标
wndclass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);//使用白色背景刷
wndclass.lpszMenuName = (LPSTR)IDC_MY;         //使用指定的菜单
wndclass.lpszClassName = szClassName;              //窗口类的名称
wndclass.hIconSm = NULL;                           //没有类的小图标

其中wndclass.lpfnWndProc、wndclass.hIcon和wndclass.lpszMenuName,这三个字段我们不使用默认字段而是自己定义。三个字段含义分别是:窗口消息处理、指定窗口图标、指定窗口菜单。

填充完该结构体之后,就可以使用RegisterClassEX函数进行注册:

::RegisterClassEX(&wndclass);

0x05创建窗口

创建窗口,调用CreateWindowEx()函数:

 HWND hwnd = ::CreateWindowExA(0,                   //dwExStyle,扩展样式szClassName,     //lpClassName,类名NULL,                //lpWindowName,标题 WS_OVERLAPPEDWINDOW,//dwstyle,窗口风格CW_USEDEFAULT,        //X,初始x坐标CW_USEDEFAULT,      //Y,初始y坐标CW_USEDEFAULT,      //nWidth,宽度CW_USEDEFAULT,        //nHeight,高度NULL,                //hWndParent,父窗口句柄NULL,              //hMenu,菜单句柄hInstance,           //hInstance,程序实例句柄NULL);             //lpParam,用户数据
// lpParam,用户数据          

该函数成功调用返回窗口句柄,若失败返回NULL。第4个参数指定了窗口标题栏、系统菜单、可以改变边框大小等。该参数也可以使用“|”来构建窗口的风格 ,例如将使用“WS_OVERLAPPEDWINDOW | WS_VSCROLL”该窗口将带有最大最小化以及垂直滚动条等风格。

0x06 在桌面上显示

在桌面上显示,则使用BOOL ShowWindow(HWND hWnd, int nCmdShow)函数。该函数用于指定窗口的显示状态,参数nCmdShow是系统传给WinMain函数的参数。如果窗口之前可见,则返回值为非零。如果窗口之前被隐藏,则返回值为零。
本例中调用的ShowWindow():
ShowWindow(hwnd, nCmdShow);

0x07更新窗口客户区

更新窗口客户区使用BOOL UpdateWindow(HWND hWnd );如果函数调用成功,返回值为非零值。如果函数调用不成功,返回值为零。如果指定的窗口更新区域不为空的话,则该函数会发送一个VM_PAINT消息(刷新窗口客户区)到窗口函数(Window Procedure)处理 。注意,所有消息都在WINUSER.H中被标识为数字:

0x08 进入无限消息循环

经过前几个步骤之后,下面就应该进入无限循环等待用户的键盘鼠标输入。Windows中,每个线程都会被分配一个消息队列,当有一个输入发生后,Windows就会把用户的输入翻译成消息存放在消息队列中。使用GetMessage()函数可以从线程的消息队列中取出一个消息来填充MSG结构,例如:

::GetMessage(&msg, NULL, 0,0);

MSG结构体定义:

typedef struct tagMSG {HWND        hwnd;     //消息要发送的窗口句柄UINT        message;    //消息标识符,以WM_靠头的预定义值WPARAM      wParam;   //消息参数一LPARAM      lParam;  //消息参数二DWORD       time;        //消息放入消息队列的时间POINT       pt;        //这是一个POINT数据结构,标识消息放入消息队列时鼠标位置
#ifdef _MACDWORD       lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

GetMessage()函数从消息队列中取得的消息,如果不是WM_QUIT,则返回非0值,否则返回0。一般将GetMessage()函数加入while()中判断是否为FALSE,以确定是否结束消息循环:

while(::GetMessage(&msg, NULL, 0, 0))
{
//处理逻辑
}

本例中需要把键盘输入的虚拟键消息转换为字符消息(WM_CHAR消息),所以还要使用::TranlateMessage()函数把上面填充后的MSG结构体“翻译”一下:

::TranslateMessage(&msg);

然后使用DispatchMessage()函数将翻译后的消息分发到对应的窗口函数中(0x04小节中WNDCLASSEXA结构体实例中的MainWndProc):

::DispatchMessage(&msg);

0x09 使用窗口函数进行消息处理

前面的小节完成了窗口类注册、创建窗口、显示在屏幕上,整个程序已经进入消息循环,开始从消息队列中取消息,但是真正的工作是在窗口函数(本例的MainWndProc)中完成,该函数的作用就是对传进来的消息按照设计者的意愿进行处理,例如本例中将接收到键盘输入字符后让它即时显示在窗口上。
通常在窗口函数中使用switch和case结构决定怎么处理消息。另外,窗口函数接收到的消息有很多,对于不关心的消息将它们传送至窗口函数中的DefWindowProc()函数处理。一般情况下,窗口函数中消息处理的结构如下:

switch(uMsg)
{case WM_PAINT://处理WM_PAINT消息逻辑return 0;case WM_DESTROY;//处理WM_DESTROY消息逻辑return 0;......
}
return ::DefWindowProc(hwnd, message, wParam, lParam);//交由windows默认处理

注意,必须要把不处理的消息交给DefWindowProc()处理,也要把该函数的返回值交给windows,否则windows失去了和应用程序通信的途径,系统不能再控制窗口行为。

0x07小节中说明了VM_PAINT消息作用是刷新客户区,除了使用UpdateWindow()发送该消息之外,当出现窗口第一次创建、窗口大小改变、最小化窗口时,窗口函数等也会收到VM_PAINT消息。处理VM_PAINT消息时总以调用BeginPaint()函数开始,以ENDPaint()函数结束:

hdc = ::BeginPaint(hwnd, &ps);
//处理逻辑
::EndPaint(hwnd, &ps);

当用户关闭窗口,窗口函数会受到一个VM_DESTROY消息,一般收到这个消息之后,设计者都会调用PostQuitMessage()函数响应此消息,该函数会向消息队列发送VM_QUIT消息。在0x08小节提过,当主函数中的GetMessage()函数如果从消息队列中收到VM_QUIT,则返回0值,从跳出while循环,执行接下的代码。

同样在0x08小节中,键盘输入键码后会被TranslateMessage()函数转化为WM_CHAR消息,然后发送到窗口函数,当窗口函数收到WM_CHAR消息后,会把对应的字符添加到字符串变量中,然后使用InvalidateRect()函数使客户区无效,迫使windows再次发送WM_PAINT消息,这样字符就能显示在客户区中了。

0x10 创建窗口的完整代码

创建窗口的完整代码如下:

#include "stdafx.h"
#include<string.h>
#include "第一个窗口程序.h"
#include<string>//窗口函数的函数原型
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);int APIENTRY _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR    lpCmdLine,int       nCmdShow)
{char szClassName[] = "MianWClass";WNDCLASSEX wndclass;//用描述主窗口的参数填充WNDCLASSEX结构wndclass.cbSize = sizeof(wndclass);         //结构的大小wndclass.style = CS_HREDRAW | CS_VREDRAW;       //指定如果大小改变就重画wndclass.lpfnWndProc = MainWndProc;           //窗口的函数指针wndclass.cbClsExtra = 0;                      //没有额外的类内存wndclass.cbWndExtra = 0;                     //没有额外的窗口内存wndclass.hInstance = hInstance;             //句柄实例//wndclass.hIcon = ::LoadIcon(NULL, IDI_APPLICATION);//使用预定义图标wndclass.hIcon = ::LoadIcon(hInstance, (LPSTR)IDI_ICON2);wndclass.hCursor = ::LoadCursorA(NULL, IDC_ARROW);//使用预定义光标wndclass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);//使用白色背景刷wndclass.lpszMenuName = (LPSTR)IDC_MY;         wndclass.lpszClassName = szClassName;              //窗口类的名称wndclass.hIconSm = NULL;                           //没有类的小图标//注册这个窗口类::RegisterClassEx(&wndclass);//创建主窗口HWND hwnd = ::CreateWindowExA(0,             //dwExStyle,扩展样式szClassName, //lpClassName,类名NULL,//lpWindowName,标题 WS_OVERLAPPEDWINDOW | WS_VSCROLL,//dwstyle,窗口风格CW_USEDEFAULT,       //X,初始x坐标CW_USEDEFAULT,      //Y,初始y坐标CW_USEDEFAULT,      //nWidth,宽度CW_USEDEFAULT,        //nHeight,高度NULL,                //hWndParent,父窗口句柄NULL,              //hMenu,菜单句柄hInstance,           //hInstance,程序实例句柄NULL);             //lpParam,用户数据if(NULL == hwnd){::MessageBoxA(NULL, "创建窗口出错", "error", MB_OK);return -1;}//显示窗口,刷新窗口客户区::ShowWindow(hwnd,nCmdShow);::UpdateWindow(hwnd);//从消息对列中取出消息,交给窗口函数处理,直到getmessage返回FALSE,结束消息循环MSG msg;while(::GetMessage(&msg, NULL, 0, 0)){//转化键盘消息::TranslateMessage(&msg);//将消息发送到相应的窗口函数::DispatchMessageA(&msg);}//当GetMessage返回FALSE时结束return msg.wParam;
}LRESULT CALLBACK MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{//str对象用于保存窗口客户区显示的字符串static std::string str;switch(message){ case WM_CREATE:{//设置窗口标题::SetWindowTextA(hwnd, "最简陋的打字程序");return 0;}case WM_COMMAND:{switch(LOWORD(wParam)){case IDM_EXIT:{   //向hwnd指定的窗口发送一个WM_CLOSE消息::SendMessage(hwnd, WM_CLOSE, 0, 0);break;}}case WM_PAINT://窗口客户区需要重画{PAINTSTRUCT ps;//使无效的客户区变的有效,并取得设备环境句柄HDC hdc = ::BeginPaint(hwnd, &ps);// 设置输出文本的背景颜色和文字颜色::SetTextColor(hdc, RGB(255, 0, 0));  ::SetBkColor(hdc, ::GetSysColor(COLOR_3DFACE));//显示文字::TextOutA(hdc, 0, 0, str.c_str(), str.length());::EndPaint(hwnd, &ps);return 0;}case WM_CHAR:{// 保存ansi码str = str + char(wParam);// 使整个客户区无效::InvalidateRect(hwnd,NULL,0);return 0;}case WM_DESTROY://正在销毁窗口{  //向消息队列投递一个WM_QUIT消息,初始GetMessage函数返回0,结束消息循环::PostQuitMessage(0);return 0;}}}//将我们不处理的消息交给系统做默认处理return ::DefWindowProcA(hwnd, message, wParam, lParam);
}

需要注意的是本例中我们在程序里自定义了窗口的左上角图标,添加方法为
右键“资源管理器栏”的“源文件”,选择“添加”和“资源”,选择“icon”后按“导入”把本地的.icon文件(可自行从网上下载.icon文件)导入到工程中:

选择“资源管理器栏”的“第一个窗口程序.rc”文件,然后选择“Icon”文件夹,删掉以前的icon(因为我们是在demo程序下修改,该icon为demo程序的),将刚导入的icon通过右侧改ID号为“IDI_ICON2”:

最后进入到“第一个窗口文件.cpp”文件中将wndclass.hIcon = ::LoadIcon()第二个参数为IDI_ICON2:

wndclass.hIcon = ::LoadIcon(hInstance, (LPSTR)IDI_ICON2);

这样就将本地图标添加到程序中了。

另外,本例中自定义了菜单,右键“资源管理器栏”的“源文件”,选择“添加”和“资源”,选择“Menu”后新建菜单:

接着,同样选择“资源管理器栏”的“第一个窗口程序.rc”文件,选择“Menu”文件夹,删掉以前的demo留下来的文件,将刚新建的menu通过右侧改ID号为“IDR_MENU1”:

双击“IDR_MENU1”,进入菜单制作场景,然后添加主菜单“文件”和子菜单“退出”,把退出的ID改为“IDM_EXIT”:

回到“第一个窗口文件.cpp”文件在case WM_COMMAND添加退出逻辑,这样在窗口运行的时候单击文件中的退出按钮窗口就会退出:

case IDM_EXIT:
{   //向hwnd指定的窗口发送一个WM_CLOSE消息::SendMessage(hwnd, WM_CLOSE, 0, 0);break;
}

0x11运行结果

在运行之前,需要修改项目属性的字符集为多字节,这样程序运行时不会出现"LPWSTR" 类型的实参与 “LPCSTR” 类型的形参不兼容问题:
运行结果:

以上。
参考文档:
1.https://blog.csdn.net/u011583927/article/details/54896961
2.https://blog.csdn.net/qq_32823595/article/details/79450832
3.https://blog.csdn.net/NNNNNNNNNNNNY/article/details/46394207
4.张铮,孙宝山,周立天.Windows程序设计(第3版)[M].北京;人民邮电出版社,2018.7.

Windows程序设计:使用VS2010创建窗口程序相关推荐

  1. QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数

    版权声明 请尊重原创作品.转载请保持文章完整性,并以超链接形式注明原始作者"tingsking18"和主站点地址,方便其他朋友提问和指正. QT源码解析(一) QT创建窗口程序.消 ...

  2. MFC Windows 程序设计[六十]之窗口分屏(附源码)

    MFC Windows 程序设计[六十]之窗口分屏 程序之美 前言 主体 运行效果 核心代码 逻辑分析 结束语 程序之美 前言 MFC是微软公司提供的一个类库(class libraries),以C+ ...

  3. MFC Windows 程序设计[319]之渐变标题栏程序(附源码)

    MFC Windows 程序设计[319]之渐变标题栏程序 程序之美 前言 主体 运行效果 核心代码 逻辑分析 结束语 程序之美 前言 MFC是微软公司提供的一个类库(class libraries) ...

  4. 中年人学C语言Windows程序设计,20子窗口的创建

    今天, 我们要学习的就是 子窗口控件 的使用. 在其他一些 Windows应用软件上我们经常能够看到一些大致相同的按钮.复选框.组合框.列表框等控件, 这些控件很有可能就是使用 标准子窗口控件 来实现 ...

  5. 用java创建窗口程序,创建窗口的应用程序

    创建窗口的应用程序 通过前面的基础知识的学习,已经对ctypes库的基本功能学会了使用,为了加强这种知识训练,以及运用刚学习的知识的快乐,我们来创建一个稍稍复杂一点的应用程序.这个应用程序是所有后面应 ...

  6. Windows程序设计设计第一个窗口

    窗口调用的函数的简短解释: 来自小甲鱼的窗口模板: /* -------------------------------------------------------------------MyWi ...

  7. 在中国Windows Azure服务中创建应用程序的一些不同之处

    Azure 中的托管服务由一个应用程序(用于在托管服务中运行)和 XML 配置文件(定义托管服务的运行方式)组成.托管服务同时使用服务定义文件 (.csdef) 和配置文件 (.cscfg).有关详细 ...

  8. 基于Win32Api创建窗口程序

    最近在整理资料, 记得上学期间写得第一个程序是用汇编语言写了个类似HD-COPY的程序, 那个时候真是废寝忘食,程序正常运行后真得让人非常激动, 可惜太久远了找不到代码了, 那个时候有张3寸盘就很不错 ...

  9. 学习笔记(01):Qt视频教程-基础实例进阶-嵌入windows桌面图标下的窗口程序

    立即学习:https://edu.csdn.net/course/play/5252/95608?utm_source=blogtoedu 1.Windows的所有窗口都有一个描述符,被称为句柄. 2 ...

  10. 4.2第一个窗口程序

    创建Win32工程和MessageBox函数 前面讲的程序都是使用控制台界面来接受输入.显示输出的.要想使用窗口界面与用户交互必须首先创建一个Win32工程. (1)运行VC++6.0,选择菜单命令& ...

最新文章

  1. luogu 3806
  2. java批量删除接口怎么定义_教你在Java接口中定义方法
  3. html中如何写平方根等,平方根的符号怎么打出来 电脑打字,平方根的符号怎样打出来?...
  4. 即时通讯软件 的无锁数组操作
  5. 学C++你绝不能错过的干货!
  6. Oracle中查看所有表和字段
  7. python打包及支持pip安装
  8. R语言的常用函数速查
  9. 基于SSM的实验室预约系统
  10. vue x 兼容iphone_【前端vue系列】初始化一个vue工程
  11. win7 usb功能被禁用的解决方法
  12. Taylor Swift looks so much like a supermodel
  13. Expressive Body Capture: 3D Hands, Face, and Body from a Single Image
  14. 优化无止境,爱奇艺中后台 Web 应用性能优化实践
  15. html img图片不变形等比例缩放,兼容ie6
  16. react18中使用react-hook-form
  17. 基于mAppWidget实现手绘地图(八)–获取用户地理位置
  18. 《程序员的自我修养-Ch7_动态链接》
  19. 企企通助力浙江哈尔斯,构建智慧供应链管理企业
  20. 关于Java自己记不住和不知道的知识点

热门文章

  1. 最快的 java 图像_java – 最快的性能过滤图像
  2. c语言识别按了esc键_憋了三年,史上最全的 F1~F12 键用法整理出来了
  3. linux下解除端口防火墙,Linux下防火墙配置、端口的开启和关闭
  4. java 字面值_Java基础之字面值
  5. 使用angrutils生成控制流图出错的解决过程
  6. 仿得微博字符限制效果
  7. 创建用户, 使用crontab定时运行程序
  8. 命令提示符操作及Java的特点
  9. Leetcode804.Unique Morse Code Words唯一摩尔斯密码词
  10. [bzoj1855][Scoi2010]股票交易_动态规划_单调队列