Windows窗口程序

Windows窗口程序

应用程序分类
  • 控制台程序Console

    DOS程序,没有窗口,通过DOS窗口执行

    入口函数: main

  • 窗口程序

    拥有自己的窗口,可以与用户交互

    入口函数: WinMain

  • 库程序

    存放代码、数据的程序,执行文件可以从中取出代码执行或获取数据

    • 静态库程序:扩展名LIB, 在编译链接程序时,将代码放入到执行文件中

      静态库没有入口函数 --> 没法执行 --> 没法进入内存

    • 动态库程序:扩展名DLL,在文件执行时从中获取代码

      动态库有入口函数–>可以执行,但是不能独立执行( 必须依附其他程序 )

      入口函数: DLLMain

开发工具和类
开发工具
  • 编译器

    CL.EXE 将源代码编译成目标文件.obj

  • 链接器

    LINK.EXE 将目标代码,库链接生成最终文件

  • 资源编译器

    RC.EXE (.rc) 将资源编译,最终通过链接器存入最终文件

Visual Studio 路径: C:\Projram Files(x86)\Microsoft Visual Studio xx\vc\bin

Windows库

  • kernel32.dll

    提供核心的API, 例如进程,线程,内存管理等

  • user32.dll

    提供了窗口,消息等API

  • gdi32.dll

    绘图相关的API

路径: C:\Windows\System32

头文件
  • windows.h

    所有windows头文件的集合 --> 包含了其他头文件

  • windef.h

    windows数据类型

  • winbase.h

    kernel32的API

  • wingdi.h

    gdi32的API

  • winuser.h

    user32的API

  • winnt.h

    UNICODE字符集的支持

相关函数
int WINAPI WinMain(
	HINSTANCE hInstance,// 当前程序的实例句柄
    HINSTANCE hPrevInstance, // 当前程序前一个实例句柄  --> 已经废弃
    LPSTR lpCmdLine, // 命令行参数字符串  char* 类型  --> 只能传递一个命令行参数
    int nCmdShow // 窗口的显示方式 最大化显示,最小化显示,原样显示
);

句柄可以找到进程对应的内存 --> 句柄是表的索引

int MessageBox(
	HWND hWnd, // 父窗口句柄   
    LPCTSTR lpText, // 显示在消息框中的文字
    LPCTSTR lpCaption, // 显示在标题栏中的文字
    UINT uType // 消息框中的按钮,图标显示类型
); // 返回点击的按钮ID

Hxxx --> 大概率是句柄

rc资源文件

后缀: .rc

100 ICON small.ico

100 数字标识 ICON 图标资源 small.ico 文件名称

编译后称为 .res文件

.obj & .res 统称为目标文件

程序编译过程

Windows窗口程序_第1张图片

Demo
#include 
int WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine, int nCmdShow){
	MessageBox(NULL,"hello world","Information",MB_YESNO);
	return 0;
} 

Windows窗口程序_第2张图片

第一个windows窗口
步骤
  1. 定义WinMain函数
  2. 定义窗口处理函数(自定义,处理消息)
  3. 注册窗口类(向操作系统写入一些数据)
  4. 创建窗口(内存中创建窗口)
  5. 显示窗口(绘制窗口的图像)
  6. 消息循环(获取/翻译/派发消息)
  7. 消息处理
#include

// 窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){
    return DefWindowProc(hWnd,msgID,wParam,lParam);
}
// 入口函数
int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPerIns,LPSTR lpCmdLine,int nCmdShow){
    // 注册窗口类 --> 向系统的内核写入一些数据
    WNDCLASS wc={0}; // 结构体变量
    wc.cbClsExtra = 0; // 开缓冲区  --> n 字节的缓冲区
    wc.cbWndExtra = 0; // 开缓冲区  另一种缓冲区
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); // 背景色
    wc.hCursor = NULL; // 默认光标位置
    wc.hIcon = NULL;  // 默认图标
    wc.hInstance = hIns; // 当前程序实例句柄
    wc.lpfnWndProc = WndProc; // 窗口处理函数
    wc.lpszClassName = "窗口类名字"; // 窗口类的名字
    wc.lpszMenuName = NULL; // 没有菜单
    wc.style = CS_HREDRAW | CS_VREDRAW ; // 水平或者垂直有变化,重画窗口
    // 将上面复制写入操作系统
    RegisterClass(&wc);
    // 内存中创建窗口
    HWND hWnd = CreateWindow("窗口类名字","标题",WS_OVERLAPPEDWINDOW,100,100,100,100,NULL,NULL,hIns,NULL);
    //HWND hWnd = CreateWindow("窗口类名字","标题",WS_OVERLAPPEDWINDOW,x,y,w,h,父窗口,菜单,hIns,NULL);
    // WS_OVERLAPPEDWINDOW最后一个参数窗口风格
    // 父窗口,菜单没有就置位NULL
    // 最后一个参数没用,置为NULL
    // 显示窗口
    ShowWindow(hWnd,SW_SHOW);
    // 风格 SW_SHOW 原样显示
    UpdateWindow(hWnd);
    // 刷新 --> 再画一遍
    // 消息循环
    MSG nMsg = {0};
    while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg); // 翻译消息
        DispatchMessage(&nMsg) ; // 派发消息,交给窗口处理函数来处理
    }
    return 0;
}
字符编码
历史背景
  • ASC

    7位代表一个字符

  • ASCII

    8位一个字符

  • DBCS

    double byte

    单双字节混合编码

  • UNICODE

    • linux 一般utf8
    • windows 一般 utf16
宽字节字符
  • wchar_t 每个字符占2个字节

    char每个字符占1个字节

    wchar_t 实际上是 unsigned short 类型,定义时,需要在前面增加"L",通知编译器按照双字节编译字符串,采用UNICODE编译

  • 需要使用支持wchar_t函数操作宽字节字符串

    wchar_t * pwszText = L"Hello wchar";
    wprintf(L"%s\n",pwszText);
    
  • demo

    #include
    #include
    
    int main(){
        wchar_t * pszText = L"Hello wchar";
        int len = wcslen(pszText); // 返回字符个数
        wprintf(L"%d,%s",len,pszText);
        return 0;
    }
    
  • TCHAR 数据类型

    定义在WINNT.h中:

    #ifdef UNICODE
    	typedef wchar_t TCHAR
        #define _TEXT(quote) L##quote
    #else
        typedef char TCHAR
        #define _TEXT(quote) quote
    #endif
    

    ## 是拼接的作用

    TCHAR * pszText = _TEXT("Hello,wkk");
    

    定义UNICODE 宏要在window.h头文件前面

  • UNICODE字符打印

    wprintf 对UNICODE 字符打印支持不完善

    在Windows使用WriteConsole API打印UNICODE 字符 GetStdHandle

    //WriteConsole(标准输出句柄,输出的缓冲区,输出长度,实际输出的长度,备用参数);
    wchar_t *pszText = L"哇咔咔";
    WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),pszText,wcslen(pszText),NULL,NULL);
    // GetStdHandle() 获取标准句柄
    // STD_INPUT_HANDLE
    // STD_OUTPUT_HANDLE
    // STD_ERROR_HANDLE
    
  • 项目属性设置为UNICODE 字符集

    系统会自动增加UNICODE宏的定义

    如果是TCHAR * 类型增加了UNICODE宏,字符串字面量前面要加L

  • 系统调用函数的参数类型

    LPSTR === char * 
    LPCSTR === const char *
    LPWSTR === wchar_t * 
    LPCWSTR === const wchar_t * 
    LPTSTR === TCHAR *
    LPCTSTR === const TCHAR*
    
注册窗口类
窗口类

概念

  • 窗口类包含了窗口的各种参数信息的数据结构
  • 每个窗口都具有窗口类,基于窗口类创建窗口
  • 每个窗口类都具有一个名称,使用前必须注册到系统

分类

  • 系统窗口类

    系统已经定义好的窗口类,所有应用程序都可以直接使用

  • 应用程序全局窗口类

    由用户自己定义,当前应用程序所有模块都可以使用

  • 应用程序局部窗口类

    由用户自己定义,当前应用程序中本模块可以使用

系统窗口类

不需要用户注册,直接使用即可,系统已经注册好了

  • 按钮 - BUTTON
  • 编辑框 - EDIT
#include
int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPerIns,LPSTR lpCmdLine,int nCmdShow){

    HWND hWnd = CreateWindow("Button","按钮",WS_OVERLAPPEDWINDOW,100,100,100,100,NULL,NULL,hIns,NULL);

    ShowWindow(hWnd,SW_SHOW);

    UpdateWindow(hWnd);

    MSG nMsg = {0};
    while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg); // 翻译消息
        DispatchMessage(&nMsg) ; // 派发消息,交给窗口处理函数来处理
    }
    return 0;
}
全局窗口类
ATOM RegisterClass(
	CONST WNDCLASS * lpWndClass // 窗口类的数据
);
// 注册成功,返回一个数字标识 非0
// 失败,返回0
// 注册窗口类的结构体
typedef struct _WNDCLASS {
    UINT style; // 窗口类的风格
    WNDPROC lpfnWndProc; // 窗口处理函数
    int cbClsExtra ; // 窗口类的附加数据buff大小
    int cbWndExtra;  // 窗口的附加数据buff大小
    HINSTANCE hInstance; // 当前模块的实例了句柄
    HICON hIcon; // 窗口图标句柄
    HCURSOR hCursor; // 鼠标句柄
    HBRUSH hbrBackground; // 绘制窗口背景的画刷句柄
    LPCTSTR lpszMenuName; // 窗口菜单的资源ID字符串
    LpCTSTR lpszClassName; // 窗口类的名称
}WNDCLASS,*PWNDCLASS;
  • style 窗口类风格

    应用程序全局窗口类的注册,需要在窗口类的风格中增加CS_GLOBALCLASS.

    WNDCLASS wce = {0};
    wce.style = ... | CS_GLOBALCLASS;
    

    应用程序局部窗口类,在注册窗口类时,不添加CS_GLOBALCLASS风格

    CS_HREDRAW 当窗口水平变化时,窗口重新绘制

    CS_VERDRAW 当窗口垂直变化时,窗口重新绘制

    CS_DBLCLKS 允许窗口接收鼠标双击

    CS_NOCLOSE 窗口没有关闭按钮

一般不建议使用全局窗口类

局部窗口类

在注册窗口类时,不添加CS_GLOBALCLASS风格

窗口创建

CreateWindow / CreateWindowEx --> 加强版

加强版增加了扩展风格dwExStyle参数

HWND CrateWindowEx(
	DWORD dwExStyle , // 窗口的扩展风格
    LPCTSTR lpClassName, // 已经注册的窗口类名称
    LPCTSTR lpWindowName, // 窗口标题栏的名字
    DWORD dwStyle, // 窗口的基本风格
    int x, // 左上角水平坐标
    int y, // 左上角垂直坐标
    int nWidth,
    int nHeight,
    HWND hWndParent, // 窗口的父窗口句柄  --> 如果是子窗口要写这个参数
    HMENU hMenu, // 窗口菜单句柄
    HINSTANCE hInstance, // 应用程序实例句柄 --> WinMain 第一个参数
    LPVOID lpParam // 窗口创建时附加参数 --> 一般给NULL
); // 创建成功返回窗口句柄

窗口基本风格

WS_BORDER   有黑色的边界线
WS_CAPTION  有标题栏
WS_CHILD    子窗口
WS_CHILDWINDOW  同上
WS_CLIPCHILDREN  裁剪窗口  --> 不规则窗口
WS_CLIPSIBLINGS  
WS_DISABLED  禁用 --> 常用按钮控件
WS_DLGFRAME  对话框
WS_GROUP     分组
WS_HSCROLL   水平滚动条
WS_ICONIC    最初状态最小化状态
WS_MAXIMIZE  最大化状态
WS_MAXIMIZEBOX  最大化按钮
WS_MINIMIZE
WS_MINIMIZEBOX
WS_OVERLAPPED 交叠窗口  --> 标题栏+边框
WS_OVERLAPPEDWINDOW   基本都有 *********
WS_POPUP  弹出式对话框
WS_SIZEBOX 可以改变大小    
WS_TABSTOP   支持tap键顺序
WS_SYSMENU  系统菜单
WS_TILED
WS_VISIBLE  可见的 --> 显式子窗口   主窗口 -> showWindow 函数
WS_VSCROLL   垂直滚动条

CreateWindowEx 内部实现

  • 函数内部根据传入的窗口类名称,在应用程序局部窗口类中查找,如果找到执行2, 没有找到执行3
  • 比较局部窗口类与创建窗口时传入的HINSTANCE变量,如果发现相等,创建和注册的窗口类在同一模块,创建窗口返回。如果不相等执行3
  • 在应用程序全局窗口类,如果找到执行4,如果没有找到执行5
  • 使用找到的窗口类的信息,创建窗口返回
  • 在系统窗口类中查找,如果找到创建窗口返回,否则创建窗口失败
匹配查找窗口类
if(找到窗口类){
    申请一大块内存,将窗口的数据信息存入这块内存
    return 内存的句柄
}else{
    return NULL;
}
基本demo
#include

// 窗口处理函数(自定义,处理消息)
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){
	switch(msgID){
		case WM_DESTROY:
			PostQuitMessage(0); // 可以使GetMessage 函数返回0 
			break;
	}
    return DefWindowProc(hWnd,msgID,wParam,lParam);
}
// 入口函数
int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPerIns,LPSTR lpCmdLine,int nCmdShow){
    // 注册窗口类 --> 向系统的内核写入一些数据
    WNDCLASS wc={0}; // 结构体变量
    wc.cbClsExtra = 0; // 开缓冲区  --> n 字节的缓冲区
    wc.cbWndExtra = 0; // 开缓冲区  另一种缓冲区
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); // 背景色
    wc.hCursor = NULL; // 默认光标位置
    wc.hIcon = NULL;  // 默认图标
    wc.hInstance = hIns; // 当前程序实例句柄
    wc.lpfnWndProc = WndProc; // 窗口处理函数
    wc.lpszClassName = "窗口类名字"; // 窗口类的名字
    wc.lpszMenuName = NULL; // 没有菜单
    wc.style = CS_HREDRAW | CS_VREDRAW ; // 水平或者垂直有变化,重画窗口
    // 将上面复制写入操作系统
    RegisterClass(&wc);
    // 内存中创建窗口
    HWND hWnd = CreateWindow("窗口类名字","标题",WS_OVERLAPPEDWINDOW,100,100,100,100,NULL,NULL,hIns,NULL);
    //HWND hWnd = CreateWindow("窗口类名字","标题",WS_OVERLAPPEDWINDOW,x,y,w,h,父窗口,菜单,hIns,NULL);
    // WS_OVERLAPPEDWINDOW最后一个参数窗口风格
    // 父窗口,菜单没有就置位NULL
    // 最后一个参数没用,置为NULL
    // 显示窗口
    ShowWindow(hWnd,SW_SHOW);
    // 风格 SW_SHOW 原样显示
    UpdateWindow(hWnd);
    // 刷新 --> 再画一遍
    // 消息循环
    MSG nMsg = {0};
    while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg); // 翻译消息
        DispatchMessage(&nMsg) ; // 派发消息,交给窗口处理函数来处理
    }
    return 0;
}
创建子窗口
  • 创建时要设置父窗口句柄
  • 创建风格要增加WS_CHLID | WS_VISIBLE
WNDCLASS wc={0}; // 结构体变量
wc.cbClsExtra = 0; // 开缓冲区  --> n 字节的缓冲区
wc.cbWndExtra = 0; // 开缓冲区  另一种缓冲区
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); // 背景色
wc.hCursor = NULL; // 默认光标位置
wc.hIcon = NULL;  // 默认图标
wc.hInstance = hIns; // 当前程序实例句柄
wc.lpfnWndProc = DefWindowProc; // 系统默认处理函数 **************
wc.lpszClassName = "Child"; // 窗口类的名字
wc.lpszMenuName = NULL; // 没有菜单
wc.style = CS_HREDRAW | CS_VREDRAW ; // 水平或者垂直有变化,重画窗口
// 将上面复制写入操作系统
RegisterClass(&wc);
HWND hChlid1 = CreateWindowEx(0,"Child","child1",WS_CHILD|WS_VISIBLE|WS_OVERLAPPEDWINDOW,0,0,200,200,
      hWnd,NULL,hIns,NULL
);

HWND hChlid2 = CreateWindowEx(0,"Child","child2",WS_CHILD|WS_VISIBLE|WS_OVERLAPPEDWINDOW,200,0,200,200,
      hWnd,NULL,hIns,NULL
);
显式窗口
ShowWindow(hWnd,SW_SHOW);
// 根据窗口句柄,找到内存,绘制窗口
UpdateWindow(hWnd);
消息基础
消息
  • 消息组成(windows下)

    • 窗口句柄
    • 消息ID
    • 消息的两个参数(两个附带信息)
    • 消息的产生的时间
    • 消息产生时的鼠标位置
    typedef struct tagMSG{
        HWND hwnd;
        UINT message;
        WPARAM wparam;
        LPARAM lParam;
        DWORD time;
        POINT pt;
    }MSG;
    
  • 消息的作用

    当系统通知窗口工作时,采用消息的方法派发给窗口的处理函数

    每个窗口都有窗口处理函数

MSG nMsg = {0};
while(GetMessage(&nMsg,NULL,0,0)){
    TranslateMessage(&nMsg); // 翻译消息
    DispatchMessage(&nMsg) ; // 派发消息,交给窗口处理函数来处理
}

DispatchMessage函数

DispatchMessage(&nMsg){
    nMsg.hwnd --> 保存窗口数据的内存 --> WndProc
    WndProc(...){
        // 处理消息
    }
}
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam);
/**
* hWnd
* msgID
* wParam
* lParam
*/
消息的最后两个参数 消息的时间,消息时的鼠标位置没有传递
窗口处理函数
  • 每个窗口都必须有窗口处理函数

    LRESULT CALLBACK WindowProc(
    	HWND hwnd,// 窗口句柄
        UINT uMsg, // 消息ID
        WPARAM wParam, // 消息函数
        LPARAM lParam  // 消息参数
    );
    
  • 当系统通知窗口时,会调用窗口处理函数,同时将消息ID和消息参数传递给窗口处理函数。 在窗口处理函数中,不处理的消息,使用缺省窗口处理函数,DefWindowProc

  • DefWindowProc 给各种消息做默认处理

消息相关函数
  • GetMessage - 获取本进程的消息

    BOOL GetMessage(
    	LPMSG lpMsg,// 存放获取到的消息BUFF
        HWND hWnd,  // 窗口句柄  --> 抓指明的句柄的消息  NULL--> 抓所有的
        // 消息的范围 (0,0)本进程的消息都抓
        UINT wMsgFilterMin, // 获取消息的最小ID
        UINT wMsgFilterMax  // 获取消息的最大ID
    );
    

    lpMsg 当获取到消息后,将消息的参数存放到MSG结构中

    hWnd 获取到hWnd所指定窗口的消息

    wMsgFilterMin 和 wMsgFilterMax 只能获取到由它们指定的消息范围内的消息,如果都为0,表示没有范围

    返回值:

    • 如果message 为 WM_QUIT返回0, 其余消息返回非0
  • TranslateMessage 翻译消息

    将按键消息,翻译成字符消息 --> 可见字符按键

    BOOL TranslateMessage(
    	CONST MSG*lpMsg // 要翻译的消息地址
    );
    

    检查消息是否是按键的消息,如果不是按键消息,不做任何处理,继续执行

  • DispatchMessage 派发消息

    LRESULT DispatchMessage(
    	CONST MSG * lpmsg // 要派发的消息
    );
    

    将消息派发到该消息所属窗口的窗口处理函数上

常见消息

产生时间,附带的两个参数,一般用来做什么

  • WM_DESTROY

    • 产生时间 窗口被销毁时

    • 附带消息:

      wParam: 0

      lParam: 0

    • 常用于在窗口被销毁之前,做相应的善后处理,例如:资源,内存等

  • WM_SYSCOMMAND

    • 产生时间: 点击窗口的最大化,最小化,关闭等

    • 附带消息

      wParam: 具体点击的位置 例如关闭: SC_CLOSE

      lParam: 鼠标光标的位置 LOWORD(lParam) 水平位置 HIWORD(lParam) 垂直位置

    • 常用在窗口关闭时,提示用户处理

  • WM_CREATE

    • 产生时间: 窗口创建成功,但还没显式时,CreateWindow 和 ShowWindow之间

    • 附带信息

      wParam 为0

      lParam 为CREATESTRUCT 类型的指针,通过这个指针可以获取CreateWindowEx中的全部12个参数的信息

    • 一般用于初始化窗口的参数、资源等等,包含创建子窗口

  • WM_SIZE

    • 产生时间 在窗口的大小发生变化后( 第一次显示,从无到有也会触发 )

    • 附带消息

      wParam: 窗口大小变化的原因

      lParam: 窗口变化后的大小

      • LOWORD(lParam) 变化后的宽度

      • HIWORD(lParam) 变化后的高度

    • 常用于窗口大小变化后,调整窗口内各个部分的布局

  • WM_QUIT

    • 产生时间: 用户发送

    • 附带消息

      wParam : PostQuitMessage 函数传递的参数

      lParam : 0

    • 用于结束消息循环,当GetMessage收到这个消息后,会返回FALSE结束while处理,退出消息循环

    不需要手动处理

消息循环
消息循环的阻塞
  • GetMessage 从系统获取消息,将消息从系统移除,阻塞函数。当系统无消息时,会等候下一条消息

  • PeekMessage 以查看的方式从系统获取消息,可以不将消息从系统中移除,非阻塞函数。当系统没有消息时,返回FALSE。

    BOOL PeekMessage(
    	LPMSG lpMsg,  // 消息信息
        HWND hWnd,   // 窗口句柄
        UINT wMsgFilterMin, // first message
        UINT wMsgFilterMax, // last message
        UINT wRemoveMsg     // 是否移除标识  PM_REMOVE / PM_NOREMOVE
    );
    
while(1){
	if(PeekMessage(&nMsg,NULL,0,0,PM_NOREMOVE)){
        // 有消息
        if(GetMessage(&nMsg,NULL,0,0)){
            TranslateMessage(&nMsg);
            DispatchMessage(&nMsg);
        }else{
            break;
        }
    }else{
        // 无消息
        // 空闲处理
        
    }
}
发送消息
  • SendMessage 发送消息,会等待消息处理的结果 ( 等到消息处理完 , 会阻塞)

  • PostMessage 投递消息,消息发出后立刻返回,不等待消息执行结果

    将消息放到系统消息队列中

BOOL SendMessage / PostMessage (
	HWND hWnd , // 消息发送的目的窗口
    UINT Msg, // 消息ID
    WPARAM wParam , // 消息参数
    LPARAM lParam   // 消息参数
);
消息分类
  • 系统消息 ID范围 0-0x03ff (0-1024)

    系统定义好的消息,可以在程序中直接使用

  • 用户自定义消息 ID范围 0x0400 - 0x7fff ( 一共31743)

    用户自定义,满足用户自己的需求,由用户自己发出消息,并相应处理

    自定义消息宏: WM_USER

消息队列
概念
  1. 消息队列用于存放消息的队列
  2. 先进先出
  3. 所有窗口程序都具有消息队列
  4. 程序可以从队列中获取消息
分类
  • 系统消息队列,由系统维护的消息队列。存放系统产生的消息,例如:鼠标,键盘等
  • 程序消息队列 属于每一个应用程序(线程)的消息队列,由应用程序维护

Windows窗口程序_第3张图片

消息和消息队列的关系
  • 消息和消息队列的关系

    1. 产生消息时,会将消息存放到系统消息队列中
    2. 系统根据存放的消息,找到对应程序的消息队列
    3. 将消息投递到程序的消息队列中
  • 根据消息和消息队列之间使用关系,消息分为两类

    • 队列消息

      消息的发送和获取,都是通过消息队列完成

    • 非队列消息

      消息的发送和获取,是直接调用消息的窗口处理完成

  • 队列消息

    消息发送后,首先放入队列,然后通过消息循环,从队列中获取

    GetMessage 从消息队列获取消息

    PostMessage 将消息投递到消息队列

    常见队列消息: WM_PAINT, 键盘,鼠标,定时器

    WM_QUIT 必须进队列,不然GetMessage的循环不会结束

  • 非队列消息

    消息发送时,首先查找消息接收窗口的窗口处理函数,直接调用函数,完成消息

    SendMessage 直接将消息发送给窗口的处理函数,并等候处理结果

    常见消息: WM_CREATE, WM_SIZE等

    WM_CREATE 必须不能进队列 --> 此时窗口还没有显示

GetMessage详细解释
  • 在程序消息队列中查找信息,如果队列有消息,检查消息是否满足指定条件( HWND, ID范围 ), 不满足条件就不会取出消息,否则取出返回

  • 如果程序队列没有消息,向系统消息队列获取数据本程序的消息。如果系统队列的当前消息属于本程序,系统会将消息转发到程序消息队列中

  • 如果系统消息队列也没有消息,检查当前进程的所有窗口的需要重新绘制的区域,如果发现有需要绘制的区域,产生WM_PAINt 消息,取得消息返回处理

  • 如果没有重新绘制区域,检查定时器如果有到的定时器,产生WM_TIMER返回处理执行

  • 如果没有到时的定时器,整理程序的资源,内存等等

  • GetMessage 会继续等候下一条消息。PeekMessage 返回 FALSE, 交出程序的控制权

  • GetMessage 如果获取到是WM_QUIT函数返回FALSE

WM_PAINT消息
  • 产生时间: 当窗口需要绘制的时候( 并且系统没有其他消息时 )
  • 附带信息: wParam : 0 lParam : 0
  • 专职用法: 用于绘图
// paint demo
// 窗口无效区域: 需要重新绘制的区域
BOOL InvalidateRect(
	HWND hWnd, // 窗口句柄
    CONST RECT * lpRect, // 区域的矩形坐标, NULL -> 整个窗口
    BOOL bErase // 重绘前是否先擦除 TRUE / FALSE 
);

WM_LBUTTONDOWN 鼠标左键按下消息

绘图

步骤

  1. 开始绘图

    HDC BeginPain(
    	HWND hwnd , // 绘图窗口
        LPPAINTSTRUCT lpPaint // 绘图参数的buff
    ); // 返回绘图设备句柄HDC
    
  2. 正式绘图

  3. 结束绘图

    BOOL EndPaint(
    	HWND hWnd, // 绘图窗口
        CONST PAINTSTRUCT * lpPaint // 绘图参数的指针
    );
    
PAINTSTRUCT ps = {0};
HDC hdc = BeginPaint(hWnd,&ps);
TextOut(hdc,100,100,"Hello,Wkk",strlen(Hello,Wkk));
EndPaint(hWnd,&ps);
// 以上绘图的代码,必须放在处理WM_PAINT消息中
键盘消息
键盘消息分类
  • WM_KEYDOWN 按键按下产生
  • WM_KEYUP 按键被放开时产生
// 附带信息
WPARAM  按键的Virtual Key --> 虚拟键码值不能区分大小写
LPARAM  按键的参数,例如按下次数 --> 不重要
  • WM_SYSKEYDWON 系统键按下时产生 比如: ALT, F10
  • WM_SYSKEYUP 系统键放开时产生
字符消息( WM_CHAR )
  • TranslateMessage 在转换WM_KEYDOWN消息时,对于可见字符会产生WM_CHAR,不可见字符无此消息。

    TranslateMessage(&nMsg){
        // 只翻译可见字符的消息
        if(nMsg.message != WM_KEYDWON) return ;
        根据nMsg.wParam键码值可以获知哪个按键被按下
        if(不可见字符的按键) return ;
        查看CapsLock是否处于打开状态
        if(打开) PostMessage(nMsg.hwnd,WM_CHAR,大写字符,...);
        else PostMessage(nMsg.hwnd,WM_CHAR,小写字符,...);
    }
    
  • 附带信息

    WPARAM - 输入的字符的ASCII字符编码值
    LPARAM - 按键的相关参数  --> 不重要
    
鼠标消息
鼠标消息分类
  • 基本鼠标消息

    WM_LBUTTONDOWN 鼠标左键按下

    WM_LBUTTONUP 左键抬起

    WM_RBUTTONDWON

    WM_RBUTTONUP

    WM_MOUSEMOVE 鼠标移动消息

    1. 附带信息

      wPARAM: 其他按键的状态,例如: Ctrl / Shift等

      鼠标左键:1, Ctrl: 8 ,Shift: 4

      IPARAM: 鼠标的位置,窗口客户区坐标系

      • LOWORD x坐标
      • HIWORD y坐标
    2. 一般情况鼠标按下/ 抬起是成对出现的。

      在鼠标移动过程中,会根据移动速度产生一系列的WM_MOUSEMOVE消息

      移动慢产生消息多,移动快产生消息少

  • 双击消息

    WM_LBUTTONDBLCLK 鼠标右键双击

    WM_RBUTTONDBLCLK 左键双击

    1. 附带消息

      同基本鼠标消息

    2. 消息产生顺序

      以左键双击为例:

      WM_LBUTTONDOWN

      WM_LBUTTONUP

      WM_LBUTTONDBLCLK

      WM_LBUTTONUP

      使用时需要在注册窗口类的时候添加CS_DBLCLKS 风格, 加上才会出现双击事件

  • 滚轮消息

    WM_MOUSEWHEEL 鼠标滚轮消息

    1. 附带信息

      wPARAM

      • LOWORD 其他按键的状态

      • HIWORD 滚轮的偏移量,通过正负值表示滚动方向

        正:向前滚动, 负:向后滚动

      IPARAM: 鼠标当前的位置,屏幕坐标系

      • LOWORD x坐标
      • HIWORD y坐标
    2. 使用

      通过偏移量,获取滚动的方向和距离

      一般的滚动偏移都是120的倍数

定时器消息
定时器消息介绍
  • 产生时间

    在程序中创建定时器,当到达时间间隔时,定时器( 实际是GetMessage )会向程序发送一个WM_TIMER消息

    定时器的精度是毫秒,但是准确度低

    可以设置时间间隔为1000ms, 但是会在非1000毫秒到达消息

  • 附带信息

    wPARAM: 定时器ID

    IPARAM: 定时器处理函数的指针 --> 一般没用

  • 用途

    周期性做,时间要求不严

创建销毁定时器
  • 创建定时器

    UINT_PTR SetTimeer(
    	HWND hWnd,// 定时器窗口句柄
        UINT_PTR nIDEvent, // 定时器ID
        UINT uElapse, // 时间间隔   ms为单位
        TIMERPROC lpTimerFunc // 定时器处理函数指针(一般不使用,为NULL)  
    ); // 创建成功,返回非0
    
  • 关闭定时器

    BOOL KillTimer(
    	HWND hWnd, // 定时器窗口句柄
    	UINT_PTR uIDEvent  // 定时器ID
    );
    
菜单资源
菜单分类
  • 窗口的顶层菜单

  • 弹出式菜单

    例如:鼠标右键菜单

  • 系统菜单

    Windows窗口程序_第4张图片

    HMENU 类型表示菜单
    ID 表示菜单项  --> 每一个菜单项都有一个ID
    
资源相关
  • 资源脚本文件: *.rc文件

    描绘图片、菜单资源

  • 编译器: RC.EXE

    Windows窗口程序_第5张图片

菜单资源的使用
  1. 添加菜单资源

    • 增加一个.rc文件 --> 会自动增加一个resource.h文件
    • 选中rc文件 --> Add Resource
    • 添加菜单资源
    • 编辑菜单,下拉项 右键可以查看&修改属性
    • 分割线 Separator -> true
    • 菜单ID
  2. 加载菜单资源

    • 注册窗口类时设置菜单

      wc.lpszMenuName = (LPCTSTR)IDR_MENU1;  // 强转一下路径
      
    • 创建窗口时传递参数

      倒数第三个参数 传递菜单句柄 ( 不是上面的菜单ID )

      HWND hWnd = CreateWindowEx(0,"Main","window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,LoadMenu(hIns,(LPCTSTR)IDR_MENU1),hIns,NULL)
      

      加载菜单资源:

      HMENU LoadMenu (
      	HINSTANCE hInstance, // handle to module  --> 找本进程的内存
          LPCTSTR lpMenuName   // menu name or resource identifier
      );
      
    • 在主窗口WM_CREATE消息中利用SetMenu函数设置菜单

      HMENU hMenu = LoadMenu(hIns,(LPCTSTR)IDR_MENU1));
      SetMenu(hWnd,hMenu);
      
命令消息处理

单击菜单项发出WM_COMMAND消息

  • 附带信息

    wPARAM : HIWORD 对于菜单为0 
             LOWORD 菜单项的ID
    lPARAM : 对于菜单为0
    
图标资源

以.ico结尾的图片

  • 添加资源

    注意图标的大小,一个图标文件中,可以有多个不同大小的图标

  • 加载

    HICON LoadIcon(
    	HINSTANCE hInstance, // 当前程序实例句柄
        LPCTSTR lpIconName  // 图标资源id
    );// 成功返回HICON句柄
    
  • 设置

    注册窗口类

    wc.hIcon = LoadIcon(hIns,(LPCTSTR)IDI_ICON1);
    
光标资源
  • 添加光标的资源

    关标的大小默认是32x32像素,每个光标有HotSpot( 热点 ), 是当前鼠标的热点

  • 加载资源

    HCURSOR LoadCursor(
    	HINSTANCE hInstance, // 程序实例
        LPCTSTR lpCursorName // 资源ID
    ); // hInstance 可以为NULL, 获取系统默认的Cursor
    
  • 设置资源

    • 在注册窗口时,设置光标

      wc.hCursor = LoadCursor(hIns,(char*)IDC_CURSOR1);
      
    • 使用SetCursor设置光标

      HCURSOR SetCursor(
      	HCURSOR hCursor // 光标
      ); // 返回原来的光标
      

      必须放在消息的处理中设置

      WM_SETCURSOR 消息参数
          wPARAM 当前使用的光标句柄
          IPARAM - LOWORD 当前区域的代码 在哪个区域活动  HICLIENT  / HTCAPTION  客户区/标题区
                 - HIWORD 当前鼠标消息ID,有没有点右键左键等等
      
      DefWindowPro() 默认处理,会将光标重新改成注册窗口类的关标
      所以SetCursor后要直接返回,不能经过DefWindowPro函数    
      
字符串资源
  • 添加字符串资源

    添加字符串表,在表中添加字符串

  • 字符串资源的使用

    int LoadString(
    	HINSTANCE hInstance, //程序实例
        UINT uID, // 字符串ID
        LPTSTR lpBuffer, // 存放字符串的BUFF
        int nBufferMax // 字符串BUFF长度
    ); // 成功返回字符串长度,失败0
    
快捷键资源
  • 添加 资源添加快捷键表,增加命令ID对应的快捷键 Accelerator

    让快捷键的ID 和 命令的ID一样 就是绑定

  • 使用

    // 加载加速键表 --> 表
    HACCEL LoadAccelerators(
    	HINSTANce hInstance, // 程序实例句柄
        LPCTSTR lpTableName   // 快捷键表名
    ); // 返回快捷键表句柄
    // 翻译快捷键
    int TranslateAccelerator(
    	HWND hWnd, // 处理消息的窗口句柄
        HACCEL hAccTable, // 快捷键表句柄
        LPMSG lpMsg// 消息
    ); // 如果快捷键,返回非零
    
    TranslateAccelerator(hWnd,hAccel,&nMsg){
        if(nMsg.message != WM_KEYDOWN ) return 0; // 没有按键按下,一定不是快捷键
        根据nMsg.wParam(键码值),获知哪些按键按下
        到快捷键表中去匹配查找
        if(找不到) return 0; 
        else{
            SendMessage(hWnd,WM_COMMAND,(HI)1(LO)快捷键对应的ID,...);
            return 1;
        }
    }
    
    // 调用
    while( GetMessage(&nMsg,NULL,0,0)){
    	if( !TranslateAccelerator(hWnd,hAccel,&nMsg)){
    		TranslateMessage(&nMsg);
    		DispatchMessage(&nMsg);
    	}
    }
    
  • 在WM_COMMAND中相应消息,消息参数

    wPARAM : HWORD 为1表示加速键,为0表示菜单

    ​ LOWORD 为命令ID

    lParam: 为0

绘图编程
绘图基础
  • 绘图设备 DC (Device Context), 绘图上下文/绘图描述表

  • HDC-DC 句柄,表示绘图设备

  • GDI - Windows graphics device interface ( Win32 提供的绘图API )

  • 颜色 RGB

    32位: 8,8,8,8 --> 透明度

  • 颜色的使用

    COLORREF–> DWORD

    COLORREF nColor = 0;
    
  • 赋值使用RGB宏

    nColor = RGB(0,0,255);
    
  • 获取RGB值

    GetRValue / GetGValue / GetBValue
    BYTE nRed = GetRValue( nColor );
    
HDC hdc = BeginPaint(hWnd,...);  //--> 抓绘图设备
TextOut(hdc,x,y,"hello",...);
...
EndPaint(hwnd,...);
基本图形绘制
  • SetPixel 设置指定点的颜色

    COLORREF SetPixel(
    	HDC hdc, // DC句柄
        int X,
        int Y,
        COLORREF crColor 
    );// 返回原来的颜色
    
    PAINTSTRUCT ps = {0};
    HDC hdc = BeginPaint(hWnd,&ps);
    SetPixel(hdc,100,100,RGB(255,0,0));
    EndPaint(hWnd,&ps);
    
  • 线得使用(直线,弧线)

    MoveToEx – 指明窗口当前点

    LineTo 从窗口当前点到指定点绘制一条直线

    当前点: 上一次绘制时的最后一点,初始为(0,0)点

    MoveToEx(hdc,100,100,NULL); // 最后一个参数返回上一次的当前点
    LineTo(hdc,300,300);
    
  • 封闭图形:能够用画刷填充的图形

    Rectangle / Ellipse

    Rectangle(hdc,x,y,x+w,y+h);
    
    Ellipse(hdc,100,100,x+w,y+h); // 外接矩形
    
GDI绘图对象
画笔
  • 画笔的作用

    线的颜色,线型(虚线画笔、点线画笔…),线粗

    HPEN : 画笔句柄

  • 画笔的使用

    1. 创建画笔

      HPEN CreatePen(
      	int fnPenStyle, // 画笔的样式
          int nWidth, // 画笔的粗细
          COLORREF crColor // 画笔的颜色
      ); // 创建成功返回句柄
      
      • PS_SOILD - 实心线,可以支持多个像素宽,其他线型只能是一个像素宽

      • PS_DASH - 虚线画笔

    2. 将画笔应用到DC中

      	HGDIOBJ SelectObject(
      		HDC hdc, // 绘图设备句柄
      	    HGDIOBJ hgdiobj  // GDI绘图对象句柄
      	); // 返回原来的GDI绘图对象句柄   --> 注意保存原来DC当中画笔
      
    3. 绘图

    4. 取出DC中的画笔,将原来的画笔,使用SelectObject函数,放入到设备DC中

    5. 释放画笔

      BOOL DeleteObject(
      	HGDIOBJ hObject   // GUI绘图对象句柄
      );
      

      只能删除不被DC使用的画笔,所以在释放前,必须将画笔从DC中取出

画刷

封闭图形的填充的颜色、图案

HBRUSH 画刷句柄

  1. 创建画刷

    HBRUSH hBursh = CreateSolidBrush(RGB(0,255,0));
    //CreateSolidBrush 创建实心画刷
    HBRUSH hBurshhat = CreateHatchBrush(HS_CROSS,RGB(0,255,0)); // HS_CROSS 经纬线
    //CreateHatchBrush 创建纹理画刷
    
  2. 将画刷应用到DC中

    HBRUSH oldBrush = SelectObject(hdc,hBursh); // 原本的刷子是白色的
    
  3. 绘图

  4. 将画刷从DC中取出

    SelectObject(hdc,oldBrush);
    
  5. 删除画刷

    DeleteObject(hPen);
    

用一条条的单独直线围起来的图形,不是封闭图形,不能填充

系统GDI对象

使用GetStockObject 函数获取系统维护的画刷,画笔等

如果不使用画刷填充,需要使用NULL_BRUSH 参数,获取透明画刷。

GetStockObject 返回的画刷不需要DeleteObject

// 透明画刷
HGDIOBJ hBursh = GetStockObject(NULL_BRUSH);
// 应用刷子
// 恢复刷子
位图
位图绘制
  • 位图相关

    光栅图形: 记录图像中每一点的颜色等信息

    矢量图形: 记录图像算法,绘图指令等等

    HBITMAP 位图句柄

  • 位图的使用

    1. 在资源中添加位图资源 --> 资源ID

    2. 从资源中加载位图LoadBitmap

      HBITMAP hBmp = LoadBitmap(g_hInstance,(char*)IDB_BITMAP1);
      
    3. 创建一个与当前DC相匹配的DC(内存DC) --> 在内存中绘图

      HDC CreateCompatibleDC(
        HDC hdc  // 当前DC句柄,可以为NULL, 使用屏幕DC
      ); // 返回创建好的DC句柄
      
    4. 将位图放入匹配的内存DC中 --> 在虚拟区域中绘制图形

      SelectObject
      
    5. 成像 (1:1)

      BOOL BitBlt(
        HDC hdcDest; // 目的DC
        int nXDest, 
        int nYDest,
        int nWidth,
        int nHeight,
        HDC hdcSrc, // 源DC --> 内存DC
        int nXSrc,  // 源左上X
        int nYSrc,  // 源左上Y
        DWORD dwRop  // 成像方法,SRCCOPY
      );
      

      缩放成像

      BOOL StretchBlt(
      	HDC hdcDest,
          int nXOriginDest,
          int nYOriginDest,
          int nWidthDest,
          int nHeightDest,
          HDC hdcSrc,
          int nXOriginSrc,
          int nYOriginSrc,
          int nWidthSrc,   // 源DC宽
          int nHeightSrc,  // 源DC高
          DWORD dwRop
      );
      // 目的区域比原始图像小  --> 缩小
      // 目的区域比原始图像大  --> 放大
      
    6. 取出位图

      SelectObject   从内存DC取数据
      
    7. 释放位图

      DeleteObject(hBmp);
      
    8. 释放匹配的内存DC

      DeleteDC(hMemdc);
      
文本绘制
  • 文字绘制

    TextOut -> 将文字绘制在指定坐标位置,只能单行绘制

    TextOut(hdc,x,y,strdata,nCount);
    
    RECT rc;
    rc.left = 100;  // (left,top) (right,bottom)
    rc.top= 150;
    rc.right = 200;
    rc.bottom= 200;
    
    int DrawText(
    	HDC hDC,    // DC句柄
        LPCTSTR lpString,  // 字符串
        int nCount,        // 字符数量
        LPRECT lpRect,     // 绘制文字的矩形框
        UINT uFormat       // 绘制的方式  // DT_LEFT|DT_TOP  水平靠左|垂直靠上  DT_WORDBREAK 多行绘制
    );                                  // DT_NOCLIP 打破矩形区域的限制,不裁剪
    // DT_CENTER DT_VCENTER --> DT_VCENTER、DT_BOTTOM只适用于单行DT_SINGLELINE,和DT_WORDBREAK冲突
    
  • 文本颜色和背景

    文字颜色: SetTextColor(hdc,RGB(…));

    文字背景色: SetBkColor(hdc,RGB(…)); --> 仅适用于不透明模式

    文字背景模式: SetBkMode(QPAQUE / TRANSPARENT );

    • QPAQUE 不透明,默认
    • TRANSPARENT 透明
  • 字体

    Windows常用的字体为 TrueType格式的字体文件 --> 保存点阵字型

    字体名 - 标识字体类型 --> 第一行有字体名称

    HFONT - 字体句柄

    1. 创建字体

      HFONT CreateFont(
      	int nHeight, // 字体高度
          int nWidth, // 字体宽度     一般给一个高度,宽度为0,系统字节匹配一个合适的宽度
          int nEscapement, // 字符串倾斜角度(以0.1度为单位)  --> 和斜体不同 
          int nOrientation, // 字符旋转角度   二维看不出效果
          int fnWeight, // 字体的粗细 ,不是以像素为单位  ,  900是细体
          DWORD fdwItalic, // 斜体
          DWORD fdwUnderline, // 下划线
          DWORD fdwStrikeOut, // 删除线    1/0
          DWORD fdwCharSet, // 字符集 --> GB2312_CHARSET  涵盖基本所有的汉字
          DWORD fdwOutputPrecision, // 输出精度  , 废弃
          DWORD fdwClipPrecision, // 剪切精度  , 废弃
          DWORD fdwQuality,  // 输出质量  , 废弃
          DWORD fdwPitchAndFamily,// 匹配字体  , 废弃
          LPCTSRT lpszFace  // 字体名称  ---> 字体文件内容第一行
      );
      
    2. 应用字体到DC

      HGDIOBJ oldfont = SelectObject(hdc,hFont);

    3. 绘制文字

      DrawText / TextOut

    4. 取出字体

      SelectObject(hdc,oldfont);

    5. 删除字体

      DeleteObject(hFont);

对话框
  • 普通窗口: 自定义函数调用缺省函数

  • 对话框窗口: 缺省函数调用自定义函数

    缺省函数(...){
        ...
        自定义函数(...)
    }
    
对话框原理
  • 对话框分类

    模态对话框:当对话框显式时,会禁止其他窗口和用户的交互操作

    非模态对话框:在对话框显示后,其他窗口仍然可以和用户交互操作

  • 对话框基本使用

    1. 对话框窗口处理函数
    2. 注册窗口类( 不使用 ), 操作系统帮忙注册, 名字–>Dialog
    3. 创建对话框
    4. 对话框的关闭
  • 对话框窗口处理函数 --> 并非真正的对话框窗口处理函数

    INT_PTR CALLBACK DialogProc(
    	HWND hwndDlg , // 窗口句柄
        UINT uMsg, // 消息ID
        WPARAM wParam , // 消息参数
        LPARAM lParam // 消息参数
    );
    // 返回TRUE   缺省处理函数不需要处理
    // 返回FALSE  交给缺省处理函数处理
    // 一般不需要调用缺省对话框窗口处理函数
    
模态对话框
  • 创建对话框

    INT DialogBox(
    	HINSTANCE hInstance, // 应用程序实例句柄
        LPCTSTR lpTemplate, // 对话框资源ID
        HWND hWndParent, // 对话框父窗口
        DLGPROC lpDialogFunc  // 自定义函数
    );
    

    DialogBox事一个阻塞函数,只有当对话框关闭后,才会返回,继续执行后续代码

    返回值通过EndDialog设置 --> 通过EndDialog结束对话框

    点击关闭按钮消息: WS_SYSCOMMAND

  • 对话框处理函数

    INT_PTR CALLBACK DlgProc(HWND hwndlg,UINT msgID,WPARAM wParam,LPARAM lParam){
        switch(msgID){
            case  WM_SYSCOMMAND:
                if(wParam == SC_CLOSE){ // 点击关闭按钮
    				EndDialog(hwndlg,100);
                }
                break;
        }
        return FALSE; // 将消息交给真正的对话框处理函数的后续处理部分处理
    }
    
  • 对话框的关闭

    BOOL EndDialog(
    	HWND hDlg, // 关闭的对话框窗口句柄
        INT_PTR nResult // 关闭的返回值
    );// 消除模式对话框 & 解除模式对话框的阻塞状态
    

    关闭模式对话框,只能使用EndDialog,不能使用DestroyWindow(销毁窗口)等函数。

    DestroyWindow 可以销毁对话框,但是不能消除阻塞

    nResult 是DialogBox函数退出时的返回值

  • 对话框的消息

    WM_INITDIALOG 对话框创建之后显示之前,通知对话框窗口处理函数,可以完成自己的初始化相关的操作。

    其他消息和窗口的消息一致

非模态对话框
  • 创建对话框

    HWND CreateDialog(
    	HINSTANCE hInstance , // 应用程序实例句柄
        LPCTSTR lpTemplate, // 模板资源ID
        HWND hWndParent, // 父窗口
        DLGPROC lpDialogFunc // 自定义函数
    );
    

    非阻塞函数,创建成功后返回窗口句柄**,需要使用ShowWindow函数显示对话框**

  • 对话框的关闭

    关闭时使用DestroyWindow销毁窗口,不能使用EndDialog关闭对话框

其他
增加DOS窗口

用于调试

HANDLE g_hOutput = 0 ;// 接收标准输出句柄
WinMain(...){
    AllocConsole(); //增加入口函数
    g_hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    // FreeConsole(); // 释放控制台
    
    WriteConsole(g_hOutput,text,strlen(text),NULL,NULL);
}
参考
  1. https://www.bilibili.com/video/BV1Qb4y1o7u9

你可能感兴趣的:(c++,c,windows,win32,窗口程序)