在 C++ 中创建图形窗口

· · 科技·工程

0 前言

控制台软件实在是太低级了!分辨率低的要命!也就输出一点文字了!根本没法好好画图!对于游戏作者一点也不友好!

但是 EasyX 根本不会安装!

有没有 Windows 自带的画图工具啊?

有的,兄弟,有的,我们可以使用 Windows 提供的 GDI(图形设备接口)来实现这一切。

1 准备工作

既然是 Windows API,那肯定少不了 #include<windows.h>,接着,肯定要导入一些来自 Windows 的库文件。如果你使用的是 MinGW64,那么你使用 #pragma 是无效的,你需要指定编译指令 -luser32 -lgdi32 -lkernel32。(至于怎么添加编译指令?难道您不会百度么?)

2 创建窗口

在创建窗口之前,因为编写图形窗口的特殊性,我们同样需要讲解一下 main 函数的写法。

这次不是简单的使用 int main();,而是使用 int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow);

这些是干什么的呢?

很好!你已经完成了主函数的定义,你现在可以创建窗口了。至于创建窗口,直接套模板就行,实在是没什么可以写的,也没什么大的变化:

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow){
    // 1. 注册窗口类
    const wchar_t CLASS_NAME[] = L"Sample Window Class";// 窗口类名

    WNDCLASSW wc = {};
    wc.lpfnWndProc = WindowProc;       // 指定窗口过程函数
    wc.hInstance = hInstance;          // 当前实例句柄
    wc.lpszClassName = CLASS_NAME;     // 窗口类名
    wc.hCursor = LoadCursor(NULL, IDC_ARROW); // 默认箭头光标
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // 窗口背景色

    RegisterClassW(&wc);
    // 2. 创建窗口实例
    HWND hwnd = CreateWindowExW(       // 返回值是窗口句柄
        0,                             // 扩展样式(无)
        CLASS_NAME,                    // 窗口类名
        L"Windows GUI test",           // 窗口标题
        WS_OVERLAPPEDWINDOW,           // 普通窗口样式
        CW_USEDEFAULT, CW_USEDEFAULT,  // 默认位置
        800, 600,                      // 窗口大小
        NULL,                          // 无父窗口
        NULL,                          // 无菜单
        hInstance,                     // 实例句柄
        NULL                           // 无额外参数
    );
    if (hwnd == NULL){
        return 0;
    }
    // 3. 显示和更新窗口
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
}

容易发现各种字符串前面都有一个 L,放心,这不会导致编译错误,这表示使用宽字符(Unicode),这是一个十分现代化的字符集,是 Windows GDI 编程推荐的编码。这也是预防多语言字符串乱码最方便快捷的方法。

现在,发生了编译错误,编译器提示 WindowProc 未定义,不用担心,我们马上就开始定义该函数了。

3 窗口过程函数

编写窗口过程函数,一般是在 WinMain() 函数前声明,其后定义,不过具体还是看大家自己的意愿与习惯。

窗口过程函数是用于处理窗口信息的,通常,在主函数里有一个用于处理窗口消息的消息循环,会由它将消息分发到窗口过程函数。(必须将其写在创建窗口后)

// 4. 消息循环:处理窗口消息
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)){
    TranslateMessage(&msg);            // 翻译键盘消息
    DispatchMessage(&msg);             // 分发消息到窗口过程
}
return msg.wParam;                     // 结束运行时的主函数返回值

现在,定义窗口过程函数,因为前面有 wc.lpfnWndProc = WindowProc; 所以,我们的窗口过程函数就叫做 WindowProc 了。

窗口过程函数的原型是 LRESULT CALLBACK WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam); ,其各个参数与返回值代表了不同的含义:

在函数中,可以通过 switch(){case:}if(){}else if(){}else{} 来针对每一种消息类型给出不同的处理方法。(不过我自认为 switch(){case:} 写起来更好看一点)

这里给出常见的 UINT uMsg 的含义: |UINT uMsg|含义| |:-|:-| |WM_CREATE|窗口创建| |WM_PAINT|窗口需要重绘| |WM_SIZE|窗口大小改变| |WM_CLOSE|请求关闭窗口| |WM_DESTROY|窗口销毁| |WM_KEYDOWN|键盘按键按下| |WM_LBUTTONDOWN|鼠标左键按下| |WM_RBUTTONDOWN|鼠标右键按下|

该套模板了!

// 窗口过程函数:处理所有发送到窗口的消息
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
    switch (uMsg){
        case WM_DESTROY:               // 窗口关闭时
        {
            PostQuitMessage(0);        // 通知消息循环退出
            return 0;
        }
        case WM_PAINT:                 // 窗口需要重绘时
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd,&ps);// 这里的 hdc 十分重要!
            // 在这里画图 
            EndPaint(hwnd,&ps);
            return 0;
        }

        case WM_SIZE:                  // 窗口大小改变时
        {
            // 可添加处理窗口大小变化的代码
            return 0;
        }
    }

    // 处理未显式处理的消息(必须调用默认处理函数)
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

所以,现在你可以在 case WM_PAINT:{} 中指定画图内容,但是画图的函数是什么呢?我们将在下一部分列举所有常见的画图函数。

4 画图函数

在 GUI 编程中,有许多神奇的函数,通过把这些函数放入窗口过程函数的 case WM_PAINT 部分,便可以画图了。

画图函数里面有一个 HDC hdc 的参数,它指的是设备上下文句柄,是所有画图操作的基础。 一般情况下,调用函数时的参数用上面 HDC hdc = BeginPaint(hwnd,&ps); 得到的 hdc 就行了。

接下来,我们将列举常见的画图函数:

绘制直线

函数原型

BOOL MoveToEx(HDC hdc,int x,int y,LPPOINT lpPoint);
BOOL LineTo(HDC hdc,int x,int y);

解释:

标志 效果
DT_TOP 顶部对齐(默认)
DT_LEFT 左对齐(默认)
DT_CENTER 水平居中
DT_RIGHT 右对齐
DT_VCENTER 垂直居中
DT_BOTTOM 底部对齐

它们还有更多,可以自己上网去查。

5 有用但不是画图的函数

(呃其实只有两个,但我不知道为什么又放了一个大章)

这里面的窗口句柄就是窗口过程函数的 HWND hwnd 参数。

强制重绘(步骤 1)

函数原型

InvalidateRect(HWND hWnd,CONST RECT* lpRect,BOOL bErase);

解释: 在句柄为 hWnd 的窗口,标记 lpRect 范围内为无效,并根据 bErase 决定是否擦除背景。

使用示例

InvalidateRect(hwnd,NULL,TRUE);

解释: 在句柄为 hwnd 的窗口,标记整个窗口为无效,并擦除背景。

强制重绘(步骤 2)

函数原型

UpdateWindow(HWND hWnd);

解释: 在句柄为 hWnd 的窗口,立即触发重绘。

使用示例

UpdateWindow(hwnd);

解释: 在句柄为 hwnd 的窗口,立即触发重绘。

注意:两者有区别,一个是标记无效,另一个是立即触发重绘。两者通常一起使用。

6 一个完整的示例代码


#include<windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow){ //1. 注册窗口类 const wchar_t CLASS_NAME[] = L"Sample Window Class";// 窗口类名

WNDCLASSW wc = {};
wc.lpfnWndProc = WindowProc;       // 指定窗口过程函数
wc.hInstance = hInstance;          // 当前实例句柄
wc.lpszClassName = CLASS_NAME;     // 窗口类名
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // 默认箭头光标
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // 窗口背景色

RegisterClassW(&wc);
// 2. 创建窗口实例
HWND hwnd = CreateWindowExW(       // 返回值是窗口句柄
    0,                             // 扩展样式(无)
    CLASS_NAME,                    // 窗口类名
    L"Windows GUI test",           // 窗口标题
    WS_OVERLAPPEDWINDOW,           // 普通窗口样式
    CW_USEDEFAULT, CW_USEDEFAULT,  // 默认位置
    800, 600,                      // 窗口大小
    NULL,                          // 无父窗口
    NULL,                          // 无菜单
    hInstance,                     // 实例句柄
    NULL                           // 无额外参数
);
if (hwnd == NULL){
    return 0;
}
// 3. 显示和更新窗口
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// 4. 消息循环:处理窗口消息
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)){
    TranslateMessage(&msg);        // 翻译键盘消息
    DispatchMessage(&msg);         // 分发消息到窗口过程
}

}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ switch (uMsg){ case WM_DESTROY: // 窗口关闭时 { PostQuitMessage(0); // 通知消息循环退出 return 0; } case WM_PAINT: // 窗口需要重绘时 { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd,&ps);// 这里的 hdc 十分重要! // 画直线 MoveToEx(hdc,114,514,NULL); LineTo(hdc,1919,810); // 画矩形 Rectangle(hdc,114,514,1919,810); // 画椭圆形 Ellipse(hdc,114,514,1919,810); // 画四边形 POINT points[] = {{100,50}, {200,80}, {250,150}, {150,200}}; Polygon(hdc,points,4); // 画字符串 RECT rc = {100, 100, 300, 200}; DrawTextW(hdc,L"Centered Text",-1,&rc,DT_CENTER | DT_VCENTER); EndPaint(hwnd,&ps); return 0; }

    case WM_SIZE:                  // 窗口大小改变时
    {
        // 可添加处理窗口大小变化的代码
        return 0;
    }
}

// 处理未显式处理的消息(必须调用默认处理函数)
return DefWindowProc(hwnd, uMsg, wParam, lParam);

}


# 7 结语
图形化软件实在是太好用了!分辨率也很不错!各种图形都能很好地输出!完全可以好好画图!对于游戏作者十分的友好!

而且是 Windows 自带的!

有没有更高级的画图工具或函数啊?

有的,兄弟,有的,我们很快就能看到《在 C++ 中创建图形窗口(进阶)》了。~~(然而我也不知道要花多长时间)~~