• C++使用代码创建一个Windows桌面应用程序


    WinMain函数

    Windows应用程序的唯一程序入口。

    函数原型

    1 int WINAPI WinMain
    2 {
    3     HINSTANCE hInstancem
    4     HINSTANCE hPreInstance,
    5     LPSTR lpCmdLine,
    6     int nCmdShow 
    7 }

    WINAPI定义如下

    #define WINAPI _stdcall

    _stdcall是一个函数调用约定,除此之外,还有__cdeclfastcallthiscallnaked call等函数调用约定。

    _stdcall调用约定又称Pascal调用约定,也是Pascal语言的调用约定。它使用的方式为:

    1 int __stdcall sum(int a,int b);

    __stdcall:函数的多个参数由调用者按从右到左的顺序压入堆栈,被调用函数获得参数的序列是从左到右的的;清理堆栈的工作由被调用函数负责。
    在Visual C++中,常用宏WINAPICALLBACK来表示__stdcall调用约定。

    更详细的说明可以查看

    https://docs.microsoft.com/en-us/cpp/cpp/stdcall?view=vs-2019

    __cdecl(也可写成_cdecl)调用约定又称C调用约定,是C函数默认的调用约定,也是C++全局函数的默认调用约定,通常省略。

    1 int sum(int a,int b);
    2 int __cdecl sum(int a,int b);

    __cdecl:函数的多个参数由调用者按从右向左的顺序压入堆栈,被调函数获得参数的序列是从左到右的;清理堆栈的工作由调用者负责

    更详细的说明可以查看
    https://docs.microsoft.com/en-us/cpp/cpp/cdecl?view=vs-2019

    WinMain函数的各参数说明

    hInstance

    应用程序当前运行的实例的句柄,该句柄由Windows系统生成。

    hPrevInstance

    当前实例的前一个实例的句柄,在Win32环境下,该参数总是NULL,不再起作用

    lpCmdLine

    一个以空终止的字符串,代表传递给程序的命令行参数。

    nCmdShow

    指定窗口的显示状态

    常用值如下

    nCmdShow = 0;不显示

    nCmdShow = 1;正常显示(默认)

    nCmdShow = 2;最小化显示

    nCmdShow = 3;最大化显示

    使用代码创建Windows程序的步骤

    1、设计一个Windows类

    2、在Windows系统中注册Windows类

    3、用该Windows类创建一个窗口

    4、显示窗口

    5、创建一个消息循环

    6、创建一个窗口过程函数WndProc

    一、设计Windows类

    在创建一个窗口前,必须对窗口进行设计,指定窗口的属性。系统已经定义了WNDCLASS结构用于描述待创建窗口的参数。

    WNDCLASS声明如下

     1 typedef struct tagWNDCLASSA {
     2   UINT      style;
     3   WNDPROC   lpfnWndProc;
     4   int       cbClsExtra;
     5   int       cbWndExtra;
     6   HINSTANCE hInstance;
     7   HICON     hIcon;
     8   HCURSOR   hCursor;
     9   HBRUSH    hbrBackground;
    10   LPCSTR    lpszMenuName;
    11   LPCSTR    lpszClassName;
    12 } WNDCLASSA, *PWNDCLASSA, *NPWNDCLASSA, *LPWNDCLASSA;

    下面介绍各参数

    style

    窗口样式,可用值如下

    CS_VREDRAW:垂直重绘,当窗口垂直方向上的高度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,在垂直方向上调整窗口高度时,将不会重绘窗口。

    CS_HREDRAW:水平重绘,当窗口水平方向上的宽度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,在水平方向上调整窗口高度时,将不会重绘窗口。

    CS_OWNDC:独占设备描述表,为该类中的每个窗口分配一个单值的设备描述表。

    CS_SAVEBITS:在一个窗口中保存用户图像,以便于在该窗口被遮住、移动时不必每次刷新屏幕。但是,这样会占用更多的内存,并且比人工进行同样操作时要慢得多。

    CS_DBLCLKS:使窗口可以检测到鼠标双击事件,当用户在窗口中双击鼠标时,向窗口过程发送鼠标双击消息

    CS_BYTEALLGNCLIENT:鼠标用户区域按字节对齐显示。

    CS_BYTEALLGNWINDOW:鼠标用户窗口按字节对齐显示。

    CS_PARENTDC:在父窗口中设定一个子窗口的剪切区,以便于子窗口能够画在父窗口中。

    CS_NOCLOSE:系统菜单中没有CLOSE菜单项,窗口没有关闭按钮。

    lpfnWndProc

    指向窗口过程函数的函数指针。窗口过程函数是一个回调函数,针对Windows的消息处理机制,窗口过程函数被调用的过程如下:

    1、在设计窗口类的时候,将窗口过程函数的地址赋给lpfnWndProc成员变量

    2、调用RegisterClass(&wndclass)注册窗口类,系统就有了用户编写的窗口过程函数的地址

    3、当应用程序接收到某一窗口的信息时,调用DispatchMessage(&msg)将消息回传给系统。系统则利用先前注册窗口类时得到的函数指针,调用窗口过程函数对消息进行处理

    cbClsExtra

    Windows系统为窗口类结构分配追加的额外字节数。一般为0

    cbWndExtra

    Windows系统为窗口实例分配或追加的额外字节数,一般为0。如果应用程序使用资源文件里的CLASS指令创建对话框,并用WNDCLASS结构注册对话框框时,cbWndExtra必须设置成DLGWINDOWEXTRA

    hInstance

    包含窗口过程程序的实例句柄。一般直接赋WinMain()的hInstance即可

    hIcon

    窗口类的图标资源。这个成员变量必须是一个图标资源的句柄。可以使用LoadIcon()函数加载图标,如果hIcon为NULL,窗口将使用系统提供的默认图标

    hCursor

    窗口类的光标句柄。这个成员变量必须是一个光标资源的句柄。可以使用LoadCursor()函数加载光标。如果hCursor为NULL,应用程序必须在鼠标进入应用程序窗口时,明确设置光标的形状

    hbrBackground

    窗口类的背景画刷句柄。当窗口发生重绘时,系统使用这里指定的画刷来填充窗口的背景。该成员可以指定为用于绘制背景的物理画刷的句柄,也可以指定为标准的系统颜色值。如下:

    BLACK_BRUSH 黑色

    DKGRAY_BRUSH 深灰

    GRAY_BRUSH 灰色

    HOLLOW_BRUSH 空

    LTGRAY_BRUSH 浅灰

    NULL_BRUSH 等同于HOLLOW_BRUSH

    WHITE_BRUSH 白色

    BLACK_BRUSH 黑色

    lpszMenuName

    指向一个以空终止的字符串,该字符串描述菜单的资源名。若使用整数来标识菜单,需要用MAKEINTRESOURCE宏来进行转换。如果lpszMenuName设置为NULL,那么基于窗口类创建的窗口将没有默认菜单

    lpszClassName

    指向一个以空终止的字符串,该字符串描述窗口类的名字。这个类名可以是由RegisterClass或者RegisterClassEx注册的名字,或者是任何预定义的控件类名

    WNDCLASS使用实例如下

     1 WNDCLASS wc;
     2 
     3     wc.style          = CS_HREDRAW | CS_VREDRAW;
     4     wc.lpfnWndProc    = WndProc;
     5     wc.cbClsExtra     = 0;
     6     wc.cbWndExtra     = 0;
     7     wc.hInstance      = hInstance;
     8     wc.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
     9     wc.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    10     wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    11     wc.lpszMenuName   = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);
    12     wc.lpszClassName  = szWindowClass;

    二、注册Windows类

    Windows类设计完成时,需要调用RegisterClass()函数去注册这个类,才可以创建该类型的窗口

    1 ATOM RegisterClass(
    2   const WNDCLASSA *lpWndClass
    3 );

    注册代码如下

    if(!RegisterClass(&wc))
    {
        return 0;
    }

    三、创建窗口

     使用CreateWindow函数创建窗口,如果函数调用成功,返回值为新窗口的句柄;如果调用失败,返回值为NULL。可以使用GetLastError()函数获取错误信息

     1 HWND CreateWindow( 
     2   LPCTSTR lpClassName, 
     3   LPCTSTR lpWindowName, 
     4   DWORD dwStyle, 
     5   int x, 
     6   int y, 
     7   int nWidth, 
     8   int nHeight, 
     9   HWND hWndParent, 
    10   HMENU hMenu, 
    11   HANDLE hInstance, 
    12   PVOID lpParam 
    13 ); 

    lpClassName

    指定窗口类的名称,这个名称就是WNDCLASSA中的lpszClassName。如果在调用CreateWindow函数之前,没有调用RegisterClass函数注册这个类,系统无法得知窗口的相关信息,窗口创建就会失败。

    lpWindowName

    指定窗口名称,如果指定了标题栏,那么这里指向的字符串就会显示在标题栏上。

    dwStyle

    指定创建窗口的样式,可以组合不同的窗口样式

    常量 说明
    WS_CAPTION(0x00C00000L) 创建一个有标题栏的窗口
    WS_SYSMENU(0x00080000L) 创建一个在标题栏上带有系统菜单的窗口(需要和WS_CAPTION一起使用)
    WS_MINIMIZEBOX(0x00020000L) 创建一个具有最小化按钮的窗口(需要和WS_SYSMENU一起使用)
    WS_MAXIMIZEBOX(0x00010000L) 创建一个具有最大化按钮的窗口(需要和WS_SYSMENU一起使用)
    WS_TILED(0x00000000L) 创建一个层叠的窗口,层叠的窗口有一个标题栏和一个边框
    WS_TILEDWINDOW 创建一个使用(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)样式的层叠的窗口
    WS_CHILD(0x40000000L) 创建窗口为子窗口,不能应用于弹出式窗口样式
    WS_OVERLAPPED 与WS_TILED样式相同
    WS_OVERLAPPEDWINDOW 创建一个使用(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)样式的层叠的窗口
    WS_EX_TOPMOST 创建一个始终置顶的窗口(不管窗口是否已经激活)  
    WS_POPUP(0x80000000L) 创建一个弹出式窗口(不能与WS_CHILD一起使用)
    WS_VISIBLE(0x10000000L) 创建一个初始状态为可见的窗口(可以使用ShowWindow函数来控制显示或隐藏窗口)
    完整窗口样式可以访问:https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles

    x

    指定窗口左上角的x坐标

    y

    指定窗口左上角的y坐标

    nWidth

    以设备单元指定窗口的宽度 

    nHeight

    以设备单元指定窗口的高度

    hWndParent

    指定被创建窗口的父窗口的句柄。如果要创建一个子窗口,这里就需要提供父窗口的句柄。

    hMenu

    菜单句柄,指向附属于该窗口的菜单

    hInstance

    WinMain函数中传入的应用程序实例的句柄

    lpParam

    作为WM_CREATE消息的附加参数lParam传入的数据指针。在创建多文档界面的客户窗口时,lpParam必须指向CLIENTCREATESTRUCT结构体。多数窗口将这个参数设置为NULL

    CreateWindow示例代码如下

    HWND hwnd; 
     
        hwnd = CreateWindow( 
            "MainWClass",        
            "Test Window",           
            WS_OVERLAPPEDWINDOW, 
            0,      
            0,    
            CW_USEDEFAULT,       // 默认宽度
            CW_USEDEFAULT,       // 默认高度
            NULL,         // 没有父窗体
            NULL,        // 没有菜单
            hinstance,           
            NULL);      //没有附加数据

    四、显示窗口

    执行CreateWindow函数,窗体创建成功之后,需要调用ShowWindow函数把窗口显示在桌面上

    BOOL ShowWindow(HWND hWnd,int nCmdShow);

    hWnd

    CreateWindow创建窗口成功后返回的窗口句柄

    nCmdShow

    指示窗口显示的状态

    常用的窗口显示状态如下

    SW_HIDE: 隐藏窗口并激活其它窗口

    SW_SHOW: 在窗口原来的位置以原来的尺寸激活并显示窗口

    SW_SHOWMAXIMIZED: 激活并以最大化显示窗口

    SW_SHOWMINIMIZED: 激活并最小化显示窗口

    SW_SHOWNORMAL 激活并显示窗口。如果窗口是最大化或最小化的状态,系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口时,应该使用这种状态

    调用ShowWindow()函数之后,需要调用UpdateWindow()函数来更新窗口。

    1 BOOL UpdateWindow(HWND hwnd);

    hwnd:调用CreateWindow()成功创建窗口后返回的窗口句柄

    UpdateWindow()函数通过发送一个WM_PAINT消息来刷新窗口。如果窗口更新的区域不为空,UpdateWindow()函数绕过应用程序的消息队列,直接发送WM_PAINT消息给指定窗口的窗口过程函数进行处理。如果更新区域为空,则不发送消息。

    至此,我们就完成了Window类的设计和注册,窗口的创建、显示及更新,接下来开始处理窗体的消息。

    五、创建消息循环

    在窗口创建成功之后,需要编写一个消息循环来不断地从消息队列中取出消息,并进行响应。

    调用GetMessage()函数从消息队列中取出消息

    1 BOOL GetMessage(
    2   LPMSG lpMsg,
    3   HWND  hWnd,
    4   UINT  wMsgFilterMin,
    5   UINT  wMsgFilterMax
    6 );

    lpMsg:指向一个消息结构体(MSG),GetMessage从线程的消息队列中取出的消息将保存在该结构体对象中。

    hWnd:指向被接收消息的窗口句柄(指定接收属于哪一个窗口的消息),设置为NULL时,函数接收属于调用线程的所有窗口的窗口消息。

    wMsgFilterMin:指定要获取的消息的最小值,通常设置为0

    wMsgFilterMax:指定要获取的消息的最大值,如果wMsgFilterMin和wMsgFilterMax都设置为0,则接收所有消息。

    关于消息的介绍,可以参考:

    https://www.cnblogs.com/zhaotianff/p/11285312.html

    取出消息后,需要对消息进行转换。这个时候就需要调用TranslateMessage()函数,该函数将虚拟消息转换为字符消息。字符消息被送到调用线程的消息队列里,当下一次线程调用函数GetMessage()或PeekMessage()时被读出。

    1 BOOL TranslateMessage(
    2   const MSG *lpMsg
    3 );

    lpMsg:指向MSG结构的指针,该结构用于存放调用函数GetMessage()或PeekMessage()从消息队列里取出的消息

    返回值:如果消息可以得到,返回非零值;如果没有消息,返回值是0

    未完

    life runs on code

    作者: zhaotianff

    转载请注明出处

  • 相关阅读:
    使用委派代替继承
    《重构,改善既有代码的设计》读书笔记
    理解C指针: 一个内存地址对应着一个值
    C#实现窗口最小化到系统托盘
    C#中访问私有成员--反射
    不要在构造函数中调用可重写的方法
    链表解决约瑟夫环问题
    C数据结构(文件操作,随机数,排序,栈和队列,图和遍历,最小生成树,最短路径)程序例子
    java this,super简单理解
    数据与计算机通信习题
  • 原文地址:https://www.cnblogs.com/zhaotianff/p/11297319.html
Copyright © 2020-2023  润新知