Win32 - 窗口
Win32 - 窗口
前言
对于广大的玩家而言,Windows是一个不错的平台选择。特别是DirectX12展现的卓越性能使得大多大型游戏由其开发。但DX12只能在Win上运行,所以学习Win32API是必要的。
流程图

)
创建项目
VS
Create a new Project > Windows Desktop Application
MinGW
myWin32App:
    g++ win32_window.cpp -o myWin32App -g -Wall -lgdi32 #-mwindow
- 一定要链接 - gdi32
- 如果以main为入口点,不想要黑框框就加上-mwindow选项 
Win32API字符串
| Typedef | 定义 | 
|---|---|
| CHAR | char | 
| PSTR 或 LPSTR | char* | 
| PCSTR 或 LPCSTR | const char* | 
| PWSTR 或 LPWSTR | wchar_t* | 
| PCWSTR 或 LPCWSTR | const wchar_t* | 
Unicode 和 ANSI 函数
当 Microsoft 为Windows引入 Unicode 支持时,它通过提供两组并行 API(一个用于 ANSI 字符串,另 一个用于 Unicode 字符串)来缓解转换。 例如,有两个函数用于设置窗口标题栏的文本:
SetWindowTextW(); // Unicode function with wide-character string.
SetWindowTextA(); // ANSI function.
在内部,ANSI 版本将字符串转换为 Unicode。 Windows标头还定义了一个宏,该宏在定义预处理器符号
UNICODE或 ANSI 版本时解析为 Unicode 版本。----Microsoft Docs
为了适应国际化编程,我们将使用Unicode编码。
#ifdef UNICODE
#define SetWindowText  SetWindowTextW
#else
#define SetWindowText  SetWindowTextA
#endif
定义UNICODE以后,我们就不必为用W还是A担心了。但要注意,入口点仍是wWinMain。
TCHAR
在c++中L“xxx”是定义宽字符,其对应类型是wchar_t。
在<winnt.h>中对其做了定义:
/**<winnt.h>**/
typedef wchar_t WCHAR;
typedef WCHAR TCHAR, *PTCHAR;
#define TEXT(quote) __TEXT(quote)
#define __TEXT(quote) L##quote
| 宏 | Unicode | ANSI | 
|---|---|---|
| TCHAR | wchar_t | char | 
| TEXT (“x”) | L"x" | "x" | 
WinMain:Win32 Application入口点函数
每个完整的C++程序都有自己的入口点函数,例如最常用的C++ Console入口点main。
Win32api 有自己的入口点函数WinMain 或wWinMain,他和C++ Console看起来如下:
int main(int argc,char* argv[]);//Console entry-point function.
int WINAPI wWinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    PWSTR pCmdLine,
    int nCmdShow);//Win32 Application entry-point function.
我们下面来解释下参数:
- hInstance 称为“实例句柄”。用于标识可执行文件。 某些Windows函数需要实例句柄。
- hPrevInstance 没有意义。 它在 16 位Windows中使用,但现在始终为零。我们不管他。
- pCmdLine 包含命令行参数。就像Console中的argv一样.
- nCmdShow 是一个标志,指示主应用程序窗口是最小化、最大化还是正常显示。
Console下创建窗口
如果你不想用WinMain作为入口点而用main其实也可以,其实很多库,比如GLFW就是可以让你在console下创建win32窗口的。
我们可以通过获取Console的窗口句柄然后通过GetWindowLong获得实例句柄创建Win32窗口,具体代码看起来如下:
HWND hConWnd = GetConsoleWindow();
HINSTANCE hInstance = (HINSTANCE)GetWindowLong(hConWnd, GWLP_HINSTANCE);
窗口类
窗口类是一个属性集,是Windows编程中用于创建窗口的模板。每一个窗口类都有一个WndProc,负责处理发送该类窗口的所有消息。
下面让我们来看一个例子:
// Register the window class.
const wchar_t CLASS_NAME[]  = L"Sample Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc   = WindowProc;
wc.hInstance     = hInstance;
wc.lpszClassName = CLASS_NAME;
- lpfnWndProc 是指向名为 窗口过程 或“window proc”的应用程序定义函数的指针。窗口过程定义窗口的大部分行为。 稍后我们将详细检查窗口过程。 目前,只需将此视为转发引用。
- hInstance是应用程序实例的句柄。 从 wWinMain 的 hInstance参数获取此值。
- lpszClassName 是标识窗口类的字符串。
类名是当前进程的本地名称,因此该名称仅在进程内唯一。 但是,标准Windows控件也有类。 如果使用这些控件中的任何一个,则必须选取与控件类名不冲突的类名。 例如,按钮控件的窗口类名为“Button”。
以上3个成员是必须要有的,还有一些其他的成员:
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, NEAR *NPWNDCLASSW, FAR *LPWNDCLASSW;
#ifdef UNICODE
typedef WNDCLASSW WNDCLASS;
//...
#endif
这些我们以后再讲,有兴趣可以参见
WNDCLASSW (winuser.h) - Win32 apps | Microsoft Docs
注册窗口类
将WNDCLASS结构的地址传递给RegisterClass函数。 此函数向操作系统注册窗口类。
RegisterClass(&wc);
创建窗口
创建窗口我们可以调用CreateWindow或CreateWindowEx第二个会多一个扩展参数。
我们一般用第二个:
HWND hwnd = CreateWindowEx(
    0,                              // Optional window styles.
    CLASS_NAME,                     // Window class
    L"My Application",    // Window text
    WS_OVERLAPPEDWINDOW,            // Window style
    // Size and position
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    NULL,       // Parent window
    NULL,       // Menu
    hInstance,  // Instance handle
    NULL        // Additional application data
    );
if (hwnd == NULL)
{
    return 0;
}
- 第一个参数允许为窗口指定一些可选行为 (例如透明窗口) 。 对于默认行为,此参数设置为零。CreateWindowEx也是多于此处。
- CLASS_NAME是窗口类的名称。我们已经定义过了。
- 标题。 如果窗口具有标题栏,则文本将显示在标题栏中。
- 窗口样式是一组标志,用于定义窗口的一些外观。 常量WS_OVERLAPPEDWINDOW实际上是几个标志与按位 OR 组合。 这些标志一起为窗口提供标题栏、边框、系统菜单,以及 最小化 和 最大化 按钮。 此标志集是顶级应用程序窗口最常见的样式。
- 对于位置和大小,常量CW_USEDEFAULT表示使用默认值。
- 下一个参数设置新窗口的父窗口或所有者窗口。 如果要创建子窗口,请设置父窗口。 对于顶级窗口,请将此窗口设置为 NULL。
- 对于应用程序窗口,下一个参数定义窗口的菜单。 此示例不使用菜单,因此该值为 NULL。
- hInstance 是前面所述的实例句柄。
- 最后一个参数是指向 void 类型任意数据的指针 *。 可以使用此值将数据结构传递给窗口过程。 我们先不管他。
最后我们要让窗口可见:
ShowWindow(hwnd, nCmdShow);
这样窗口就完成创建了。
窗口消息
一个程序有交互,我们需要获取这些交互:
MSG msg;
GetMessage(&msg, NULL, 0, 0);
GetMessage可以从消息队列中拉取这些消息,我们并不用担心窗口会无响应。消息队列是另一线程中的。
GetMessage 的第一个参数是 MSG结构的地址。 如果函数成功,它将用有关消息的信息填充 MSG 结构。 这包括目标窗口和消息代码。 其他三个参数允许筛选从队列获取的消息。 几乎所有情况下,都将这些参数设置为零。
尽管MSG结构包含有关消息的信息,但几乎永远不会直接检查此结构。 而是将它直接传递给另外两个函数。
TranslateMessage(&msg); DispatchMessage(&msg);
TranslateMessage函数与键盘输入相关。
DispatchMessage 函数告知操作系统调用窗口过程,该窗口是消息的目标。 换句话说,操作系统在其窗口表中查找窗口句柄,查找与窗口关联的函数指针,并调用该函数。
例如,假设用户按下鼠标左键。 这会导致事件链:
- 操作系统在消息队列上放置WM_LBUTTONDOWN消息。
- 程序调用GetMessage函数。
- GetMessage从队列中拉取- WM_LBUTTONDOWN消息,并填充- MSG结构。
- 程序调用 TranslateMessage和DispatchMessage函数。
- 在 DispatchMessage中,操作系统调用窗口过程。
- 窗口过程可以响应消息或忽略它。
同时GetMessage又接收着退出消息WM_QUIT,GetMessage就会返回0。代码如下:
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)//Main Loop.
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
如果要中途要退出,我们可以用:
PostQuitMessage(0);
窗口过程
DispatchMessage函数会调用消息目标窗口的窗口过程,窗口过程的签名如下:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
- hwnd 是窗口的句柄。
- uMsg 是消息代码;例如, WM_SIZE消息指示窗口已调整大小。
- wParam 和 lParam 包含与消息相关的其他数据。 确切的含义取决于消息代码。
然后我们用switch语句去处理WM消息:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
  switch (uMsg)
  {
      //...
  }
}
默认消息处理
如果不在窗口过程中处理特定消息,请将消息参数直接传递给DefWindowProc函数。 此函数对消息执行默认操作,该操作因消息类型而异。
return DefWindowProc(hwnd, uMsg, wParam, lParam);
绘制窗口

这是一个窗口,它由客户区(工作区) 和 非客户区(非工作区)
- 客户区(Client Area):中间白色的部分 
- 非客户区(Non-client Area): - 标题栏 
- 边框 
 
我们现在只需绘制客户区,下面是将客户区填充白色的一个例子:
case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        FillRect(hdc, &ps.rcPaint,CreateSolidBrush(RGB(255, 255, 255)));
        EndPaint(hwnd, &ps);
    }
    return 0;
如果要深入研究绘图可以学习 图形设备接口 (GDI) 、GDI+ 或 Direct2D 的内容,其中以上的绘制是 GDI 实现的。
此示例,我们不需要过多了解。
关闭窗口
当用户关闭窗口时,该操作将触发窗口消息序列。窗口会接收到WM_CLOSE。
case WM_CLOSE:
    if (MessageBox(hwnd, L"Really quit?", L"My Application", MB_OKCANCEL) == IDOK)
    {
        DestroyWindow(hwnd);
    }
    return 0;
case WM_DESTROY:
    PostQuitMessage(0);
    return 0;
上面的代码我们用MessageBox创建了个包含“确定”和“取消”按钮的对话框。 如果用户单击 “确定”,程序将调用DestroyWindow。 否则,如果用户单击 “取消”,则会跳过对 DestroyWindow的调用,并且窗口保持打开状态。
当窗口即将被销毁时,它将收到 WM_DESTROY消息。 此消息是在从屏幕中删除窗口后发送的。在主应用程序窗口中,通常通过调用PostQuitMessage来响应WM_DESTROY以退出主循环。
完整代码
win32-window.cpp:wWinMain入口点
win32-window-console.cpp:main入口点
更多源码请克隆 https://github.com/OrbitGW/blog-sources.git
Visit me on
Win32 - 窗口的更多相关文章
- 第一个手写Win32窗口程序
		第一个手写Win32窗口程序 一 Windows编程基础 1 Win32应用程序的基本类型 1.1 控制台程序 不需要完善的Windows窗口,可以使用DOS窗口 的方式显示. 1.2 Win32窗口 ... 
- WIN32窗口程序
		// Win32.cpp : 定义应用程序的入口点. // #include "stdafx.h" #include "Win32.h" void TRACE( ... 
- Win32窗口消息机制 x Android消息机制 x 异步执行
		如果你开发过Win32窗口程序,那么当你看到android代码到处都有的mHandler.sendEmptyMessage和 private final Handler mHandler = new ... 
- 如何在Console下面生成一个WIN32窗口
		一个小挑战? VS2017里面,新建一个控制台工程,输入名字(你不需要也成,有默认的),得到一个控制台工程. 好了,生成的代码,如下: // Win32InConsole.cpp : This fil ... 
- Win32窗口框架
		Win32窗口框架 WindowClass 单例,负责窗口初始化注册和取消注册: 负责提供静态方法: 放在Window类内部,方便初始化时,wndProc(HandleMsgSetup)的赋值: cl ... 
- 解决WIN32窗口不响应WM_LBUTTONDBLCLK消息
		原文链接: http://www.cnblogs.com/xukaixiang/archive/2012/05/27/2520059.html 今天在做一个软件时,发现win32创建的窗体不能响应WM ... 
- win32窗口映射(部分)
		先理解一下“窗口”与“视区”的概念.“窗口”是逻辑坐标下的矩形区域,“视区”是设备坐标系下的区域.根据“窗口”和“视区”的大小可以确定x方向和y方向的比例因子. 例子如下: VOID OnPaint( ... 
- win32窗口程序分析
		1.分析消息的附加参数 例如:为了查看程序处理了哪些消息 在回调函数中调用输出函数,在控制台中输出消息的值: 
- WIN32 窗口类封装 框架实现部分
		上面已经讲了窗口封装部分,内容可点击:http://www.cnblogs.com/mengdejun/p/4010320.html,下面分享框架部分内容,完成WINDOWS消息迭代 CQFrameW ... 
随机推荐
- 一文带你看懂Java中的Lock锁底层AQS到底是如何实现的
			前言 相信大家对Java中的Lock锁应该不会陌生,比如ReentrantLock,锁主要是用来解决解决多线程运行访问共享资源时的线程安全问题.那你是不是很好奇,这些Lock锁api是如何实现的呢?本 ... 
- 参与 2022 第二季度 Flutter 开发者调查
			2022 Google I/O 大会正式落下帷幕,Flutter 作为 14 个开发者产品和平台中的一款,吸引了来自全球的很多开发者们的关注.随着全国很多地方已经进入夏季,Flutter 今年第二季度 ... 
- Java开发学习(二)----IOC、DI入门案例
			一.IOC入门案例 1.1 思路分析 (1)Spring是使用容器来管理bean对象的,那么管什么? 主要管理项目中所使用到的类对象,比如(Service和Dao) (2)如何将被管理的对象告知IOC ... 
- Python使用EasyOCR库对行程码图片进行OCR文字识别介绍与实践
			关注「WeiyiGeek」点我,点我 设为「特别关注」,每天带你在B站玩转网络安全运维.应用开发.物联网IOT学习! 希望各位看友[关注.点赞.评论.收藏.投币],助力每一个梦想. 文章目录 0x00 ... 
- nvm安装与使用及乱码问题
			前端开发工作中经常负责多个项目(新项目.多年的老项目及团队合作项目),经常会遇到npm install安装依赖包或者启动本地服务时依赖报错的情况,大多数是因为NodeJS和npm与依赖之间版本的问题, ... 
- React项目中使用less/scss&全局样式/变量
			使用create-react-app脚手架搭建初始化项目 > npm install -g create-react-app > npx create-react-app my-app c ... 
- 【Java面试】请说一下ReentrantLock的实现原理?
			一个工作了3年的粉丝私信我,在面试的时候遇到了这样一个问题. "请说一下ReentrantLock的实现原理",他当时根据自己的理解零零散散的说了一些. 但是似乎没有说到关键点上, ... 
- Arraylist集合、对象数组
			Arraylist集合 ArrayList是List接口的一个实现类,它是程序中最常见的一种集合. 他的特点:在增加或删除指定位置的元素时,会创建新的数组,效率比较低,因此不适合做大量的增删操作,Ar ... 
- 想学嵌入式?要不一起玩 Arduino 吧
			作者:HelloGitHub-Anthony 这里是 HelloGitHub 推出的<讲解开源项目>系列,本期介绍的是如何用开源硬件开发平台 Arduino,自己动手做一个温湿度显示器. ... 
- WPF中Popup控件的使用
			一.Popup控件的主要属性 Popup表示具有内容的弹出窗口,其主要属性为: Child:获取或设置 Popup控件的内容. IsOpen:获取或设置一个值,该值指示Popup 是否可见 Place ... 
