初学C++时,控制台会给我们提供一个黑窗口,而这篇文章所做的就是:创建一个属于图形界面窗口,而不是使用控制台黑窗口

如图,这是一个标题为XSZY的窗口
这都是自定义的
一个标题为XSZY的窗口

所有有关视窗的基本函数和类型都定义在头文件 <windows.h> 中
我们只需要引用就好了

//引用头文件 windows.h
#include<windows.h> 

然后定义程序的入口函数main

int __stdcall WinMain(HINSTANCE hInst,
				 HINSTANCE hPrev,
				  LPSTR lpCmdLine,
				   int nCmdShow)
{}

说到这个入口函数,其实使用main()也是可以的

	int main() {}
	int main(int argc, char** argv) {}
	int main(int argc, char* argv[]) {}

其实C++定义了很多程序入口,只要程序中包含其中一个,程序就可以运行了

而WinMain的声明同样也是有依据可寻的

在头文件 <winbase.h> 中, 有这样两句话
	
  int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd);
  int WINAPI wWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd);

它们分别声明了两种程序入口
WinMain 和 wWinMain 

wWinMain 是 WinMain 的宽字符版本, 形参列表中的LPWSTR说明了这个事实
顺带说一下吧 基本所有的函数、类型、结构体、宏等都带有A 和 W,
比如 WNDCLASSA 和 WNDCLASSW
 一般A是窄字符, W是宽字符
 LPSTR 其实就是 char*  ; 而LPWSTR 是 wchar_t*
 表示宽字符时需要标记L 
 比如定义宽字符串HelloWorld应该写为 L"HelloWorld!" ,系统就会识别为宽字符了

说了这么多,该定义窗口实例句柄了
这是用来指向一个实例( instance ) 的类型 HINSTANCE
前缀H就是Handle句柄的意思, instance就是实例的意思

这个句柄的作用就是:指定一个窗口实例

如果你使用的时WinMain,那么可以选择直接使用形参表中的第一个或第二个参数,但是如果你使用的时int main(),那么就需要定义一个新的实例句柄

int WINAPI WinMain(HINSTANCE hInst,  //可以使用这个
				HINSTANCE hPrev,   //也可以使用这个(习惯上使用上面的)
				LPSTR lpCmdLine,
				int nShowCmd)
{}
int main()
{
	HINSTANCE hInst;  //定一个新的实例句柄
}

定义来有什么用呢?
我们需要做一个窗口,就需要创建它,而定义它的标准,是由结构体WNDCLASS来完成的,能够让窗口和窗口结构体链接上的,需要实例句柄一致,并且和窗口类名一致

窗口类在 <winuser.h> 中的定义

typedef struct tagWNDCLASSA {
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HINSTANCE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCSTR lpszMenuName;
    LPCSTR lpszClassName;
  } WNDCLASSA,*PWNDCLASSA,*NPWNDCLASSA,*LPWNDCLASSA;

  typedef struct tagWNDCLASSW {
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HINSTANCE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCWSTR lpszMenuName;
    LPCWSTR lpszClassName;
  } WNDCLASSW,*PWNDCLASSW,*NPWNDCLASSW,*LPWNDCLASSW;

我们需要对其成员进行填充, 不然编译器无法为它分配内存,就会报错的。

解释:style 是窗口样式
		lpfnWndProc 是回调函数(等会再说)
		cbClsExtra 是窗口类拓展 一般无拓展
		cbWndExtra 是窗口扩展 一般无拓展
		hInstance 就是实例句柄,需要将其定义为我们刚刚定义的实例句柄
		hIcon 窗口的图标

图标示意图

	hCursor是鼠标
	lpszMenuName 菜单名 一般为空
	lpszClassName 窗口类名

现在可以把我们需要的赋值, 不需要的全部memset为0

WNDCLASSA wc;
memset(&wc, 0, sizeof(wc));
wc.lpszClassName = "WindowClassName";
wc.hInstance = hInst;
wc.lpfnWndProc = ; //回调函数

回调函数是用来处理消息的
在消息循环的过程中,消息被分发到回调函数,回调函数内的代码提供操作

定义回调函数

long long WINAPI WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
	return DefWindowProc(hwnd, msg, wp, lp);
}

此时给lpfnWndProc复制只用函数名即可

wc.lpfnWndProc = WndProc;

定义窗口结构体也可以使用WNDCLASSEX(拓展过的窗口类)

WNDCLASSEXA wc;
wc.cbSize = sizeof(WNDCLASSEXA);
wc.hInstance = hinst;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = "WindowClassName";

使用拓展类可以给成员cbSize赋值,就可以正确分配内存了

定义完窗口类,应该向Windows注册窗口,让它授权创建一个带有此窗口类的窗口
RegisterClassA(&wc);

注册完之后,就要将窗口创建到窗口句柄中了
窗口句柄类型 HWND
创建窗口的函数很长,如果你没有使用EX的结构体,创建窗口的函数大体如下

CreateWindow(窗口类名,窗口标题,窗口风格,窗口的位置x, y, 窗口宽, 窗口长,父窗口,菜单,实例句柄,附加函数);

窗口类名需要和窗口结构体的类名lpszClassName一致,实例句柄也必须引用同一个
CreateWindowA 和 CreateWindowW 的区别不用我重申了吧

如果你使用了WNDCLASSEX的结构体
那么创建窗口相应地得加上EX
CreateWindowEx(窗口拓展风格,...);后面和CreateWindow一样,只是在CreateWindow的基础上在前面添加了一个拓展风格的形参,一般我会使用客户区的拓展(WS_EX_CLIENTEDGE)

代码:

HWND hwnd = CreateWindowA("WindowClassName", 
						"XSZY",
						WS_OVERLAPPEDWINDOW,
						100, 100,
						640, 480,
						NULL, NULL, hInst, NULL);

其他的比较好理解,就是窗口风格是不是看不懂?
它是一个宏定义
一共有这些宏定义表示窗口的风格,要添加多个就使用按位OR(|)运算符就可以了

#define WS_OVERLAPPED __MSABI_LONG(0x00000000) //窗口的基本样式
#define WS_POPUP __MSABI_LONG(0x80000000) //弹出式窗口
#define WS_CHILD __MSABI_LONG(0x40000000)  //子窗口(指嵌在主窗口内部的子窗口)
#define WS_MINIMIZE __MSABI_LONG(0x20000000) //最小化
#define WS_VISIBLE __MSABI_LONG(0x10000000) //可见
#define WS_DISABLED __MSABI_LONG(0x08000000)
#define WS_CLIPSIBLINGS __MSABI_LONG(0x04000000)  
#define WS_CLIPCHILDREN __MSABI_LONG(0x02000000)
#define WS_MAXIMIZE __MSABI_LONG(0x01000000) //最大化
#define WS_CAPTION __MSABI_LONG(0x00C00000)  //标题栏
#define WS_BORDER __MSABI_LONG(0x00800000)   //边框
#define WS_DLGFRAME __MSABI_LONG(0x00400000) //对话框风格(只有一个叉叉)
#define WS_VSCROLL __MSABI_LONG(0x00200000) //竖滚动轴
#define WS_HSCROLL __MSABI_LONG(0x00100000) //横滚动轴
#define WS_SYSMENU __MSABI_LONG(0x00080000) //系统菜单(如果只添加了这个风格就和对话框一样)
#define WS_THICKFRAME __MSABI_LONG(0x00040000) //可变大小
#define WS_GROUP __MSABI_LONG(0x00020000)  
#define WS_TABSTOP __MSABI_LONG(0x00010000)
#define WS_MINIMIZEBOX __MSABI_LONG(0x00020000)  //最小化控件框
#define WS_MAXIMIZEBOX __MSABI_LONG(0x00010000)  //最大化控件框
#define WS_TILED WS_OVERLAPPED
#define WS_ICONIC WS_MINIMIZE
#define WS_SIZEBOX WS_THICKFRAME
#define WS_TILEDWINDOW WS_OVERLAPPEDWINDOW

#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)
#define WS_POPUPWINDOW (WS_POPUP | WS_BORDER | WS_SYSMENU)
#define WS_CHILDWINDOW (WS_CHILD)

这里有可能会失败,所以添加一条判断:

if(hwnd == NULL) 
	return -1;

如果你刚刚已经添加WS_VISIBLE 那么这一步可以省略
因为我这一步要设置窗口可见
ShowWindow函数就能够提供此功能

ShowWindow(hwnd, SW_SHOW);

SW_SHOW是一个宏,表示显示窗口
可用的还有SW_HIDE等宏,这里就不详细说了,可以在头文件 <winuser.h>中找到

最后是消息循环
消息结构体是MSG 消息循环的三个函数都要用到MSG类
所以定义一个先

MSG msg;

消息循环的过程应该是获得消息–>处理消息–>执行操作三步,分别对应三个函数

获得消息GetMessage(LPMSG, HWND,UINT,UINT)
处理消息(编码处理)TranslateMessage(LPMSG)
执行操作要在回调函数里执行,但是我们需要将消息发送给回调函数,这个函数是DispatchMessage(LPMSG)

LPMSG 理解为MSG的指针

为了不断处理消息,应该设置一个循环while(true)
在它的循环体内,写一个判断:如果收到消息,处理并分发消息
代码如下
while(true) if(GetMessage(&msg, NULL, 0, 0)
{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
}

HWND之所以没有锁定为hwnd,是因为别的消息它也要接受的

消息发到回调函数了,但是回调函数现在还是这样的:

long long WINAPI WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
	return DefWindowProc(hwnd, msg, wp, lp);
}

并不能处理消息。
消息传过去后,msg,wp,lp三个参数负责接收消息,而msg是区分各个消息的
现在先让我们处理窗口关闭的消息,不然关不了
做法很简单,在return上面加上一个switch来分发消息(当然你用if else if 也行)

switch(msg)
{
	
}

窗口关闭、破坏、退出分别是一个消息,都由宏来定义
WM_CLOSE 关闭
WM_DESTROY 破坏
WM_QUIT 退出

我们只要让发生时退出就行了

switch(msg)
{
	case WM_CLOSE:case WM_DESTROY:case WM_QUIT:exit(0);break;
}

然后放到回调函数中

long long WINAPI WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
	switch(msg)
	{
		case WM_CLOSE:case WM_DESTROY:case WM_QUIT:exit(0);break;
	}
	return DefWindowProc(hwnd, msg, wp, lp);
}

好了
完整代码(直接复制就好)

#include <windows.h>

LPCSTR classname = " "; //设置你的窗口类名
LPCSTR caption = " "; //设置你的窗口标题
int x = 0,y = 0; //设置你的窗口位置
int width = 640, height = 480; //设置你的窗口宽高

//回调函数
long long WINAPI WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
    switch(msg)
    {
        case WM_CLOSE:case WM_DESTROY:case WM_QUIT:  //窗口关闭的消息
         exit(0);
        break;
    }
    return DefWindowProc(hwnd, msg, wp, lp);  //定义回调函数的返回值
}

int WINAPI WinMain(HINSTANCE hInst,  //实例句柄
            HINSTANCE hPrev, 
            LPSTR lpCmdLine, 
            int nCmdShow)
{
    WNDCLASSA wc; //窗口结构体
    memset(&wc, 0, sizeof(wc));
    wc.lpszClassName = classname;
    wc.hInstance = hInst; //实例句柄
    wc.lpfnWndProc = WndProc;
    RegisterClassA(&wc); //注册
    HWND hwnd = CreateWindowA(classname, caption, WS_VISIBLE|WS_OVERLAPPEDWINDOW, x, y, width, height, NULL, NULL, hInst, NULL);  //创建窗口
    if(hwnd == NULL) return -1;  
    MSG msg;  //消息类
    while(true) if(GetMessage(&msg, NULL, 0, 0))  //消息循环  获得消息
    {
        TranslateMessage(&msg); //处理消息
        DispatchMessageA(&msg); //分发消息
    }
}

感谢观看。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐