YGP.Escapist 发表于 2020-4-25 19:55:29

【C++技术】仅仅用Windows API完成对Win32窗口的创建

本帖最后由 YGP.Escapist 于 2020-4-25 20:11 编辑

我正在研究Windows的API,就顺势研究了一下Windows关于创建窗口的API,自我感觉小有成就,就过来分享一下,希望对各位有用!


大佬请绕道,谢谢~


为了方便和原始,我在VS的空项目(控制台程序)里简单编写了一个小窗口,一块来分析一波~
由于是控制台程序,所以需要使用 函数来获取模块句柄:
HINSTANCE InstanceHandle = GetModuleHandleW(NULL);然后就是对WindowClass类型(这里使用的是WNDCLASSEXW作示例):
WNDCLASSEXW的声明:
typedef struct tagWNDCLASSEXW {
    UINT      cbSize;
    /* Win 3.x */
    UINT      style;
    WNDPROC   lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR   hCursor;
    HBRUSH      hbrBackground;
    LPCWSTR   lpszMenuName;
    LPCWSTR   lpszClassName;
    /* Win 4.0 */
    HICON       hIconSm;
} WNDCLASSEXW, *PWNDCLASSEXW, NEAR *NPWNDCLASSEXW, FAR *LPWNDCLASSEXW;第一个参数:unsigned int cbSize: WNDCLASSEX类型的大小,直接sizeof(WNDCLASSEXW)即可
第二个参数:unsigned int style: CS_开头的常量不同的常量使用"|"连接,由于本人刚开始研究,所以没能每一个都给大家解释,抱歉,这里我用的是CS_HREDRAW | CS_VREDRAW
第三个参数:WNDPROC lpfnWndProc 指向消息处理的函数
第四个参数和第五个参数有待研究,通常为0,
第六个参数:HINSTANCE hInstance 实例句柄,直接用刚才得到的即可
第七个参数:HICON HIcon 图标句柄,如果没有使用   LoadIconW(InstanceHandle, NULL); VS自带的使用的是LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ESCAPISTCPPWINDOW));,其实就是第二个的区别
第八个参数:HCURSOR hCursor 鼠标句柄,没有的话使用   LoadCursorW(nullptr, IDC_ARROW);
第九个参数:HBRUSH hbrBackGround 画刷句柄,可以理解成窗口背景,默认的是(HBRUSH)(COLOR_WINDOW+1);
第十个参数:const wchar_t* lpszMenuName 菜单名字,有待研究,没有的话直接使用NULL
第十一个参数:const wchar_t* lpszClassName 窗口类名,自定义,注意这两个参数是wchar_t*,需要在字符串前加一个L
第十二个参数:HICON hIconSm 托盘处小图标的句柄,直接和大图标定义相同即可~ LoadIconW(InstanceHandle, NULL);

WNDCLASSEXW定义示例代码:
      WNDCLASSEXW WindowClass;
      WindowClass.cbSize = sizeof(WNDCLASSEXW);
      WindowClass.lpfnWndProc = WindowProcess;
      WindowClass.style = CS_HREDRAW | CS_VREDRAW;
      WindowClass.hInstance = InstanceHandle;
      WindowClass.cbClsExtra = 0; WindowClass.cbWndExtra = 0;
      WindowClass.hIcon = LoadIconW(InstanceHandle, NULL);
      WindowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW);
      WindowClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
      WindowClass.lpszMenuName = NULL;
      WindowClass.lpszClassName = L"WindowClass";
      WindowClass.hIconSm = LoadIconW(InstanceHandle, NULL);
      RegisterClassExW(&WindowClass);
最后记得使用RegisterClassExW函数来注册窗口类哦~
HInstance = WindowClass.hInstance;将实例句柄赋值给全局变量,之后的进程可能会被用到~
重头戏来了!重头戏来了!重头戏来了!接下来就是CreateWindow相关的API!!!!!
这里使用的是CreateWindowExW
HWND __stdcall CreateWindowExW(
      DWORD dwExStyle,
      LPCWSTR lpClassName,
      LPCWSTR lpWindowName,
      DWORD dwStyle,
      int X,
      int Y,
      int nWidth,
      int nHeight,
      HWND hWndParent,
      HMENU hMenu,
      HINSTANCE hInstance,
      LPVOID lpParam
);
第一个参数:unsigned long dwExStyle 拓展风格,更多请看:https://docs.microsoft.com/zh-cn/windows/win32/winmsg/extended-window-styles,默认使用WS_EX_LEFT
第二个参数:const wchar_t* lpClassName 窗口类名,一定要和上面WNDCLASSEXW的类名一模一样
第三个参数:const wchar_t* lpWindowName 窗口标题,自定义即可
第四个参数:unsigned long dwStyle 窗口风格,更多请看:https://docs.microsoft.com/zh-cn/windows/win32/winmsg/window-styles,我用的是WS_OVERLAPPEDWINDOW,这是几个其他的位或得出的
第五、六、七、八个参数:分别对应:左、上、宽度、高度
第九个参数:HWND hWndParent 父窗口句柄,如果没有设置为NULL
第十个参数:HMENU hMenu 指向菜单的句柄,如果没有或者使用了菜单类可以设定为NULL
第十一个参数:HINSTANCE hInstance 刚才说到的实例句柄
第十二个参数:LPVOID lpParam有待研究,设置为NULL
HWND WindowHandle = CreateWindowExW(WS_EX_LEFT, L"WindowClass", L"My First Window In C++", WS_OVERLAPPEDWINDOW, 100, 100, 840, 560, NULL, NULL, HInstance, nullptr);
      if (!WindowHandle)
                return 0;
至此,最令人头疼的定义部分到此结束,接下来的步骤简单了很多哦~往下看,接下来我不会像刚才一样那么正经的介绍了,因为理解起来方便了许多
BOOL __stdcall ShowWindow(
    HWND hWnd,
    int nCmdShow
);ShowWindow是用来设置窗口的显示状态,第一个参数是刚才的句柄,第二个参数是想让窗口如何去展示,使用SW开头常量,通常是SW_SHOW,更多请看:https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
UpdateWindow这个函数本人有点不会解释,度娘百科带您看问题——如果窗口更新的区域不为空,UpdateWindow函数就发送一个WM_PAINT消息来更新指定窗口的客户区。函数绕过应用程序的消息队列,直接发送WM_PAINT消息给指定窗口的窗口过程,如果更新区域为空,则不发送消息。


接下来就是对消息的处理了,那么就说到了刚才的WNDPROC函数了,下面是VS默认的函数:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_PAINT:
      {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 在此处添加使用 hdc 的任何绘图代码...
            EndPaint(hWnd, &ps);
      }
      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    default:
      return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}WPARAM wParamLPARAM lParam 是两个消息参数,本人不是太懂,最近正在看,对应的类型分别是unsigned int和long
至于message这个参数,请看https://www.cnblogs.com/lsqandzy/p/5398768.html这个链接,有那么那么那么多种事件,可以根据自己的需要,来进行添加哦~


历尽千辛万苦,终于来到了最后一步——主消息循环
MSG Message;
      while (GetMessageW(&Message, nullptr, 0, 0))
      {
                TranslateMessage(&Message);
                DispatchMessageW(&Message);
      }
分别用到了三个API函数,先看下MSG结构:
typedef struct tagMSG {
    HWND      hwnd;
    UINT      message;
    WPARAM      wParam;
    LPARAM      lParam;
    DWORD       time;
    POINT       pt;
} MSG第一个就是窗口句柄,不解释
第二三四个就是WndProc的message、WParam、lParam,
第五个是消息发送的时间,第六个是坐标,就是pt.x和pt.y,不过多解释谢谢


接下来就是三个API:
BOOL __stdcall GetMessageW(
    LPMSG lpMsg,
    HWND hWnd,
    UINT wMsgFilterMin,
    UINT wMsgFilterMax第一个参数就是MSG结构传址,第二个就是窗口句柄,后两个是指定被检索的最小消息值的整数和指定被检索的最大消息值的整数,如果两个都是0就返回所有的消息
BOOL __stdcall TranslateMessage(CONST MSG *lpMsg);辅助API:将所有用户的按键信息转化为相应的字符(仅A-Z,a-z),在这里不过多做解释了哈~
LRESULT __stdcall DispatchMessageW(CONST MSG *lpMsg);LRESULT就是long,这个函数看似简单,实则很重要,它的功能正是把这个MSG结构传给WndProc


好了,差不多就是这样了,最后附上完整代码:
HINSTANCE HInstance;
//#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )
LRESULT CALLBACK WindowProcess(HWND WindowHandle, unsigned int message, unsigned int wParam, long lParam);
int main()
{
      HINSTANCE InstanceHandle = GetModuleHandleW(NULL);
      WNDCLASSEXW WindowClass;
      WindowClass.cbSize = sizeof(WNDCLASSEXW);
      WindowClass.lpfnWndProc = WindowProcess;
      WindowClass.style = CS_HREDRAW | CS_VREDRAW;
      WindowClass.hInstance = InstanceHandle;
      WindowClass.cbClsExtra = 0; WindowClass.cbWndExtra = 0;
      WindowClass.hIcon = LoadIconW(InstanceHandle, NULL);
      WindowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW);
      WindowClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
      WindowClass.lpszMenuName = NULL;
      WindowClass.lpszClassName = L"WindowClass";
      WindowClass.hIconSm = LoadIconW(InstanceHandle, NULL);
      RegisterClassExW(&WindowClass);
      HInstance = WindowClass.hInstance;
      HWND WindowHandle = CreateWindowExW(WS_EX_LEFT, L"WindowClass", L"My First Window In C++", WS_OVERLAPPEDWINDOW, 100, 100, 840, 560, NULL, NULL, HInstance, nullptr);
      if (!WindowHandle)
                return 0;
      ShowWindow(WindowHandle, SW_SHOW);
      UpdateWindow(WindowHandle);
      MSG Message;
      while (GetMessageW(&Message, nullptr, 0, 0))
      {
                TranslateMessage(&Message);
                DispatchMessageW(&Message);
      }
      return 0;
}
LRESULT CALLBACK WindowProcess(HWND WindowHandle, unsigned int message, unsigned int wParam, long lParam)
{
      switch (message)
      {
      case WM_PAINT:
      {
                PAINTSTRUCT ps;
                HDC hdc = BeginPaint(WindowHandle, &ps);
                // TODO: 在此处添加使用 hdc 的任何绘图代码...
                EndPaint(WindowHandle, &ps);
      }
      break;
      case WM_DESTROY:
                PostQuitMessage(0);
                break;
      default:
                return DefWindowProc(WindowHandle, message, wParam, lParam);
      }
      return 0;
}
为了调试方便,使用了空项目、控制台,如果是使用VS自带的资源的话,他们已经帮你写好了,你只需要添加自己的事件就好,我主要是想体验体验从头开始,从零开始的感觉,同时多懂一些原理、也学学写文章。

效果图:上传不了

最后比较一下C++的窗口程序和别的区别:优点就是什么都可以自己定义,轻松使用了Windows API,就可以创造出很强大很复杂的程序。缺点,有目共睹,过于复杂,上面这么一长串代码,仅仅创建了一个窗口,其他的窗口组件,还需要自己去继续写,而且如果只用API,缺乏了可视化,当然C++可视化编程也不是没有,可参考https://cqp.cc/t/48956这个帖子,感觉把几个常用的都介绍了,在易语言、VS C#等等其他编程语言中,简单点几下,一个窗口就出来了,C++所需要的技术含量相比于C#、易语言、VB设计窗口程序要高出很多,且时间要付出的更多。


以上所有的都是个人观点,如果有不同意本人观点,或者有更好的建议的话,都可以在下方进行讨论,谢谢。这篇文章是我在另一个论坛上写的,直接复制粘贴过来了,应该问题不大~支持一波走起!!

YGP.Escapist 发表于 2020-4-25 20:35:41

啦啦啦自己占个沙发~
{:5_109:}
页: [1]
查看完整版本: 【C++技术】仅仅用Windows API完成对Win32窗口的创建