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 有自己的入口点函数WinMainwWinMain,他和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);

创建窗口

创建窗口我们可以调用CreateWindowCreateWindowEx第二个会多一个扩展参数。

我们一般用第二个:

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 函数告知操作系统调用窗口过程,该窗口是消息的目标。 换句话说,操作系统在其窗口表中查找窗口句柄,查找与窗口关联的函数指针,并调用该函数。

例如,假设用户按下鼠标左键。 这会导致事件链:

  1. 操作系统在消息队列上放置WM_LBUTTONDOWN消息。
  2. 程序调用GetMessage函数。
  3. GetMessage从队列中拉取 WM_LBUTTONDOWN消息,并填充 MSG结构。
  4. 程序调用 TranslateMessageDispatchMessage函数。
  5. 在 DispatchMessage中,操作系统调用窗口过程。
  6. 窗口过程可以响应消息或忽略它。

同时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

CSDN

bilibili

博客园

知乎

Win32 - 窗口的更多相关文章

  1. 第一个手写Win32窗口程序

    第一个手写Win32窗口程序 一 Windows编程基础 1 Win32应用程序的基本类型 1.1 控制台程序 不需要完善的Windows窗口,可以使用DOS窗口 的方式显示. 1.2 Win32窗口 ...

  2. WIN32窗口程序

    // Win32.cpp : 定义应用程序的入口点. // #include "stdafx.h" #include "Win32.h" void TRACE( ...

  3. Win32窗口消息机制 x Android消息机制 x 异步执行

    如果你开发过Win32窗口程序,那么当你看到android代码到处都有的mHandler.sendEmptyMessage和 private final Handler mHandler = new ...

  4. 如何在Console下面生成一个WIN32窗口

    一个小挑战? VS2017里面,新建一个控制台工程,输入名字(你不需要也成,有默认的),得到一个控制台工程. 好了,生成的代码,如下: // Win32InConsole.cpp : This fil ...

  5. Win32窗口框架

    Win32窗口框架 WindowClass 单例,负责窗口初始化注册和取消注册: 负责提供静态方法: 放在Window类内部,方便初始化时,wndProc(HandleMsgSetup)的赋值: cl ...

  6. 解决WIN32窗口不响应WM_LBUTTONDBLCLK消息

    原文链接: http://www.cnblogs.com/xukaixiang/archive/2012/05/27/2520059.html 今天在做一个软件时,发现win32创建的窗体不能响应WM ...

  7. win32窗口映射(部分)

    先理解一下“窗口”与“视区”的概念.“窗口”是逻辑坐标下的矩形区域,“视区”是设备坐标系下的区域.根据“窗口”和“视区”的大小可以确定x方向和y方向的比例因子. 例子如下: VOID OnPaint( ...

  8. win32窗口程序分析

    1.分析消息的附加参数 例如:为了查看程序处理了哪些消息   在回调函数中调用输出函数,在控制台中输出消息的值:

  9. WIN32 窗口类封装 框架实现部分

    上面已经讲了窗口封装部分,内容可点击:http://www.cnblogs.com/mengdejun/p/4010320.html,下面分享框架部分内容,完成WINDOWS消息迭代 CQFrameW ...

随机推荐

  1. 第06组Alpha冲刺(3/6)

    目录 1.1 基本情况 1.2 冲刺概况汇报 1.郝雷明 2.鲍凌函 3.曾丽莉 4. 曹兰英 5. 方梓涵 6.董翔云 7.杜筱 8.黄少丹 9. 詹鑫冰 10.吴沅静 1.3 冲刺成果展示 1.1 ...

  2. 解锁!玩转 HelloGitHub 的新姿势

    本文不会涉及太多技术细节和源码,请放心食用 大家好,我是 HelloGitHub 的老荀,好久不见啊! 我在完成 HelloZooKeeper 系列之后,就很少"露面了".但是我对 ...

  3. 【SpringSecurity系列3】基于Spring Webflux集成SpringSecurity实现前后端分离无状态Rest API的权限控制

    源码传送门: https://github.com/ningzuoxin/zxning-springsecurity-demos/tree/master/02-springsecurity-state ...

  4. Kafka 的稳定性

    一.事务 1. 事务简介 1.1 事务场景 producer发的多条消息组成⼀个事务这些消息需要对consumer同时可⻅或者同时不可⻅ producer可能会给多个topic,多个partition ...

  5. Javaweb-IDEA 中Maven的操作

    1. 在idea中使用Maven 启动idea 创建一个MavenWeb项目 3.等待项目初始化完毕 4. 观察maven仓库中多了哪些东西 5. idea中的maven设置 注意:idea项目创成功 ...

  6. python 连接SAP 代码

    def Main(): sap_app = r"C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe" subproces ...

  7. UiPath Level3讲解

    匠厂出品,必属精品   Uipath中文社区qq交流群:465630324 uipath中文交流社区:https://uipathbbs.com RPA之家qq群:465620839 第一课--UiP ...

  8. VisonPro · 视觉定位工具包示例

    一.概述 视觉定位工具包一般包含: 1.相机取像: 2.图像九点标定: 3.Mark点粗定位: 4.建立粗定位坐标系: 5.Mark点精定位 6.输出Mark点坐标,角度等信息. 二.分类 1.单特征 ...

  9. NC50528 滑动窗口

    NC50528 滑动窗口 题目 题目描述 给一个长度为N的数组,一个长为K的滑动窗体从最左端移至最右端,你只能看到窗口中的K个数,每次窗体向右移动一位,如下图: 你的任务是找出窗体在各个位置时的最大值 ...

  10. 【跟着大佬学JavaScript】之lodash防抖节流合并

    前言 前面已经对防抖和节流有了介绍,这篇主要看lodash是如何将防抖和节流合并成一个函数的. 初衷是深入lodash,学习它内部的好代码并应用,同时也加深节流防抖的理解.这里会先从防抖开始一步步往后 ...