用户
搜索

该用户从未签到

i春秋作家

Rank: 7Rank: 7Rank: 7

16

主题

27

帖子

249

魔法币
收听
0
粉丝
0
注册时间
2018-1-1

i春秋签约作者

发表于 2020-7-21 00:40:19 62386

本文原创作者flag0,本文属于i春秋原创奖励计划,未经许可禁止转载!

创建窗口的6要素

  1. 设计窗口类
  2. 创建窗口实例
  3. 显示窗口
  4. 更新窗口
  5. 建立消息循环
  6. 实现窗口过程函数

设计注册窗口类

窗口类应该包含不同窗口里面共有的东西。

RegisterClass:注册窗口类

ATOM RegisterClass(
    const WNDCLASS *lpWndClass //类信息
    ); 

lpWndClass为指向WNDCLASS结构体类型的长指针。

成功返回ATOM值,失败返回0。

WNDCLASS

typedef struct _WNDCLASS { 
    UINT       style; //风格
    //当窗口被修改时,系统会通知窗口重新绘制界面(没有特殊需求填下面两个)。
    //CS_VREDRAW || CS_HREDRAW
    WNDPROC    lpfnWndProc; //回调函数
    int        cbCl**tra;  //额外大小,窗口类使用
    int        cbWndExtra;  //额外大小,窗口实例使用
    //不使用额外大小,填0即可
    HINSTANCE  hInstance; //窗口句柄,可以从main函数的参数中获取。
    HICON      hIcon; //图标
    HCURSOR    hCursor; //光标
    HBRUSH     hbrBackground; //背景颜色
    //以上三项,没有可以填NULL
    LPCTSTR    lpszMenuName; //菜单名称,没有可以填NULL
    LPCTSTR    lpszClassName; //窗口类的名字
} WNDCLASS, *PWNDCLASS; 

1592839168080

都是宏定义,而且二进制位不一样,所以可以使用位或,方法自由组合和多种风格的同时设置。

#include <iostream>
#include <Windows.h>

//回调函数
LRESULT CALLBACK FirstWindowProc(
    HWND hwnd,      // handle to window
    UINT uMsg,      // message identifier
    WPARAM wParam,  // first message parameter
    LPARAM lParam   // second message parameter
)
{
    return 1;
}

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    //窗口类
    WNDCLASS wc;
    wc.style = CS_VREDRAW | CS_VREDRAW | CS_DBLCLKS;
    //取消风格
    //wc.style &= ~CS_DBLCLKS;//
    //wc.style ^= CS_DBLCLKS;
    wc.lpfnWndProc = FirstWindowProc;
    wc.cbCl**tra = NULL;//额外空间
    wc.cbWndExtra = NULL;//额外空间
    wc.hInstance = hPrevInstance;//实例句柄
    wc.hIcon = NULL;//图标
    wc.hCursor = NULL;//光标
    wc.hbrBackground = NULL;//背景
    wc.lpszMenuName = NULL;//菜单
    wc.lpszClassName = TEXT("CR37CLASS");//类名
    //注册窗口类
    ATOM ret = RegisterClass(&wc);
    if (ret == 0)//注册窗口类失败
    {
        MessageBox(NULL, TEXT("注册窗口类失败"), TEXT("提示"), MB_OK);
        return 0;
    }
    return 0;
}

SDK中的错误处理

GetLastError获取API失败的信息

DWORD GetLastError(VOID);//返回操作失败API的错误码

通过工具->错误查找查看错误码类型。

1592842755011

FormatMessage将错误码转换为错误信息

可以用于输出错误信息。

demo

//显示错误信息
void ShowErrorMsg()
{
    LPVOID lpMsgBuf;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        GetLastError(),
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
        (LPTSTR)&lpMsgBuf,
        0,
        NULL
    );
    //弹窗显示错误信息
    MessageBox(NULL, (LPCTSTR)lpMsgBuf, TEXT("Error"), MB_OK | MB_ICONINFORMATION);
    //释放
    LocalFree(lpMsgBuf);
}

调用

if (ret == 0)//注册窗口类失败
{
    ShowErrorMsg();
    return 0;
}

在监视窗口中查看错误信息

  • VS2019:@err,hr

  • VC6.0:*(unsigned long*)(tib+0x34),hr

  • 错误码,hr

1592843658651

创建窗口实例

CreateWindow

HWND CreateWindow
( 
  LPCTSTR lpClassName,  // 类名
  LPCTSTR lpWindowName, // 标题 
  DWORD dwStyle,        // 风格
  /*三种基本风格:其他风格都是依次为基础添加的
  WS_CHILD:子窗口
  WS_OVERLAPPED:重叠
  WS_POPUP:对话框
  这三种风格互斥,且必须有一个,常用前两个
  子窗口依附于父窗口,父窗口关闭,子窗口也随之关闭。
  */
  //xy坐标,窗口宽高,如果不想填写可以使用 CW_USEDEFAULT
  int x,                // horizontal position of window
  int y,                // vertical position of window
  int nWidth,           // window width  
  int nHeight,          // window height
  HWND hWndParent,      // 父窗口
  HMENU hMenu,          // 菜单
  HINSTANCE hInstance,  // 实例句柄
  LPVOID lpParam        // 参数,没有参数填NULL
);

一般情况用这个WS_OVERLAPPEDWINDOW,重叠窗口。

#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED     | \//重叠
                             WS_CAPTION        | \//窗口有标题
                             WS_SYSMENU        | \//系统菜单
                             WS_THICKFRAME     | \//窄边框
                             WS_MINIMIZEBOX    | \//最大化按钮
                             WS_MAXIMIZEBOX)//最小化按钮

是一个组合出来的风格。

    //创建窗口实例
    HWND hWnd = CreateWindow(
        TEXT("CR37CLASS"),//类名
        TEXT("Hello Window"),//标题
        WS_OVERLAPPEDWINDOW,//风格,重叠窗口
        100,100,800,600, //坐标,窗口大小
        NULL,//父窗口
        NULL,//菜单
        hInstance,//实例句柄
        NULL//参数
    );

    if (hWnd == NULL)    //判断返回值
    {
        ShowErrorMsg();
        return 0;
    }

显示窗口

ShowWindow

BOOL ShowWindow(
  HWND hWnd,     // 窗口句柄
  int nCmdShow   // 显示状态
);

Demo

ShowWindow(hWnd, SW_SHOW);

更新窗口

BOOL UpdateWindow(  
    HWND hWnd// handle to window
    );

demo

    //更新窗口
    UpdateWindow(hWnd);

消息循环 && 窗口过程函数

windows消息机制

需要处理消息才可以将窗口显示出来。

1592873995106

会将所有发生的消息入队,应用程序从队列中取出消息进行处理,处理完该条后,去从队列中取出下一条进行处理,依次循环。

系统中创建了七个消息队列,用作不同的使用,从开发角度看只有一个消息队列,每个进程拥有一个消息队列。

一个进程可能有多个窗口,当拿到消息后,需要判断是发往哪个窗口的消息,所以MSG结构体中有窗口句柄,为了便于区分,队列中的消息发往哪个窗口,而GetMessage中的窗口句柄是为了区分获取指定窗口的消息,还是获取所有消息。

获取消息

GetMessage

BOOL GetMessage(
  LPMSG lpMsg,         // 消息信息
  HWND hWnd,           // 窗口句柄
  //消息的类型为宏定义出来的数值
  //下面参数用于界定获取消息的范围
  //如果想要拿到所有消息,均填0即可
  UINT wMsgFilterMin,  // first message  
  UINT wMsgFilterMax   // last message
);

MSG

typedef struct tagMSG {
  HWND   hwnd; //窗口句柄
  UINT   message; //消息种类
  //两个参数,作用一样,只有一个参数不够用,所以有两个
  WPARAM wParam; 
  LPARAM lParam; 
  //以下基本不会用到
  DWORD  time; //消息产生时间
  POINT  pt; //鼠标坐标
} MSG, *PMSG; 

处理消息

处理消息时需要用到 回调函数,又称为窗口过程函数。

DispatchMessage 该函数接收消息后会自动调用回调函数进行消息处理

LRESULT DispatchMessage(
    CONST MSG *lpmsg   // message information
);

接下来需要在回调函数中对消息进行处理,此时我们调用DefWindowProc来进行处理。

DefWindowProcwindows处理消息API,用于处理所有不想去处理的消息

LRESULT DefWindowProc(  
  HWND hWnd,      // handle to window
  UINT Msg,       // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
);
//该参数与WindowProc一样,所以不详细解释了

获取并处理消息Demo

//获取并处理消息
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))//拿到所有消息
{
    //调用回调函数
    DispatchMessage(&msg);
    /*
    //这里还可以手动去调用回调函数
    FirstWindowProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
    //如果有多个窗口,需要手动调用他们对应的窗口回调函数
    //如果用了DispatchMessage就会自动区分并且调用(最主要的作用)

    switch (msg.hwnd)
    {
    case hwnd1:
        ...
        break;
    case hwnd2:
        break;
        ...
    default:
        break;
    }
    */
}
//回调函数内部
LRESULT CALLBACK FirstWindowProc(
    HWND hwnd,      // handle to window
    UINT uMsg,      // message identifier
    WPARAM wParam,  // first message parameter
    LPARAM lParam   // second message parameter
)
{
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

窗口销毁

进程在结束时没有退出,为了解决这个问题,我们需要,手动压入WM_QUIT到队列。

PostQuitMessage

VOID PostQuitMessage(
        int nExitCode   // exit code
);

需要在点击关闭按钮时调用PostQuitMessage,点击关闭按钮时会发送WM_CLOSE消息。

LRESULT CALLBACK WindowProc(
  HWND hwnd,       // handle to window
  UINT uMsg,       // WM_CLOSE
  WPARAM wParam,   // not used
  LPARAM lParam    // not used
);

在回调函数中进行拦截处理,处理结束消息。

//回调函数
LRESULT CALLBACK FirstWindowProc(
    HWND hwnd,      // handle to window
    UINT uMsg,      // message identifier
    WPARAM wParam,  // first message parameter
    LPARAM lParam   // second message parameter
)
{
    switch (uMsg)
    {
    case WM_CLOSE:
        //向消息队列中投递一个WM_QUIT消息用来结束消息循环。
        PostQuitMessage(0); 
        return 0;

    default: 
        break;
    };
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

处理鼠标消息

WM_LBUTTONDOWN//按下弹起

LRESULT CALLBACK WindowProc(
        HWND hwnd,       // handle to window
        UINT uMsg,       // WM_LBUTTONDOWN  
        WPARAM wParam,   // key indicator:按键说明(有些快捷键是鼠标和键盘配合)
        LPARAM lParam    // 坐标,将int值进行拆分,低16位是x坐标,高16位是y坐标。
);

在回调函数解析鼠标消息

case WM_LBUTTONDOWN:
    WORD xPos = GET_X_LPARAM(lParam);//需要调用windowsx.h头文件
    WORD yPos = GET_Y_LPARAM(lParam);

    CHAR szBuff[MAXBYTE] = { '\0' };
    wsprintf(szBuff, TEXT("x:%d y:%d"), xPos, yPos);
    MessageBox(hwnd, szBuff, TEXT("坐标"), MB_OK);
    break;
师傅很强啊
使用道具 举报 回复
学习了学习了
使用道具 举报 回复
期待后续更完
使用道具 举报 回复
发表于 2020-7-25 13:36:39
大佬tql
使用道具 举报 回复
看了大佬的,才发现自己真的很菜
使用道具 举报 回复
这个系列很强
使用道具 举报 回复
发新帖
您需要登录后才可以回帖 登录 | 立即注册