基本 window 创建

Basic window creation

我在处理 Windows window 时遇到问题,尽管我以前这样做过一次并且效果很好。在阅读了针对此问题的最常见建议后,它仍然存在。有人能告诉我为什么 Input-handling 坏了吗?

预期行为:

  1. 创建一个 window 标题为 'FirstTry'
  2. 使用 PatBlt 将其背景设为黑色
  3. 第一次进入主循环时显示消息框,然后按'w'。
  4. 按 Alt+F4、Escape 或 Close-button 时关闭 window,显示关闭消息。

观察到的行为:

  1. 符合预期
  2. 符合预期
  3. MessageBox 首次出现,但无法通过 'w'
  4. 重新触发
  5. Window 它不可关闭,使用 TaskManager 除外(有一次它按预期显示 'closing Application'-MessageBox,但只有一次)
    • window 可拖动直到第一个 'entered loop'-MessageBox 关闭,之后它固定
    • 小蓝 'busy'- windows10 的圆圈显示 full-time,在第一个 MessageBox
    • 之后

结论:Message-handling 坏了。 我不知道为什么...


系统:

来自 VS 2017 社区版的编译器:


#include "windows.h"

static bool bAppIsRunning = false;
static bool bMessageAlreadyShown = false;

LRESULT CALLBACK win_MainWNDCallback(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam){
    LRESULT result = 0;
    switch(msg){
        case WM_SYSKEYDOWN:
        case WM_SYSKEYUP:
        case WM_KEYDOWN:
        case WM_KEYUP:{
            WPARAM vKeyCode = wParam;
            bool bWasDown = ((lParam & (1 << 30)) != 0);
            bool bIsDown = ((lParam & (1 << 31)) == 0);
            if (bWasDown != bIsDown)
            {
                switch (vKeyCode)
                {
                    case VK_ESCAPE:{
                        bAppIsRunning = false;
                    }break;
                    default:{
                        result = DefWindowProc(wnd,msg,wParam,lParam);
                    }break;
                }
            }
        }break;
        default:{
            result = DefWindowProc(wnd,msg,wParam,lParam);
        }break;
    }

    return result;
}

int CALLBACK WinMain(HINSTANCE HInstance, HINSTANCE HPrevInstance, LPSTR LpCmdLine, int NCmdShow){

    WNDCLASSA wndCLass = {};
    wndCLass.style = CS_HREDRAW | CS_VREDRAW;
    wndCLass.lpfnWndProc = win_MainWNDCallback;
    wndCLass.hInstance = HInstance;
    wndCLass.lpszClassName = (LPCSTR)"WindowClass";

    if(RegisterClassA(&wndCLass)){

        HWND wnd = CreateWindowExA(
            0, wndCLass.lpszClassName, (LPCSTR)"FirstTry", 
            WS_OVERLAPPEDWINDOW | WS_VISIBLE, 
            CW_USEDEFAULT, CW_USEDEFAULT, 
            1240, 720,
            0, 0, HInstance, 0);

        if(wnd){
            bAppIsRunning = true;

            HDC DeviceContext = GetDC(wnd);
            PatBlt(DeviceContext, 0, 0, 1240, 720, BLACKNESS);
            ReleaseDC(wnd, DeviceContext);

            while(bAppIsRunning){

                if(!bMessageAlreadyShown){
                    MessageBoxA(NULL, (LPCSTR)"Successfully entered loop.", (LPCSTR)"Success!", MB_ICONINFORMATION | MB_OK);
                    bMessageAlreadyShown = true;
                }

                MSG msg;
                while(PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)){
                    switch(msg.message){
                        case WM_SYSKEYDOWN:
                        case WM_SYSKEYUP:
                        case WM_KEYDOWN:
                        case WM_KEYUP:{
                            WPARAM vKeyCode = msg.wParam;
                            bool bWasDown = ((msg.lParam & (1<<30)) != 0);
                            bool bIsDown = ((msg.lParam & (1<<31)) != 0);
                            if(bIsDown != bWasDown){
                                switch(vKeyCode){
                                    case 'W':{
                                        bMessageAlreadyShown = false;
                                    }break;
                                    default:{
                                        TranslateMessage(&msg);
                                        DispatchMessageA(&msg);
                                    }break;
                                }
                            }
                        }
                    }
                }
            }
            MessageBoxA(NULL, (LPCSTR)"Closing Application.", (LPCSTR)"Bye bye!", MB_ICONINFORMATION | MB_OK);
        }
    }
    return ERROR_SUCCESS;
}

您的代码的主要问题是您仅在收到某些按键消息时才调用 TranslateMessage()DispatchMessage()。您需要在主消息循环中为所有消息调用它们。您应该处理 WndProc 回调中的所有消息。

您也在使用基于 TCHAR 的 API,但滥用了 LPCTSTR 类型转换。在将 string/char 文字转换为 TCHAR 时,您需要使用 TEXT() 宏。

尝试更像这样的东西:

#include <windows.h>

static bool bMessageAlreadyShown = false; 

LRESULT CALLBACK win_MainWNDCallback(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;

        case WM_SYSKEYDOWN:
        case WM_SYSKEYUP:
        case WM_KEYDOWN:
        case WM_KEYUP: {
            WPARAM vKeyCode = wParam;
            bool bWasDown = ((lParam & (1 << 30)) != 0);
            bool bIsDown = ((lParam & (1 << 31)) == 0);
            if (bWasDown != bIsDown) {
                switch (vKeyCode) {
                    case 'W':
                    case VK_ESCAPE:
                        DestroyWindow(wnd);
                        return 0;
                }
            }
            break;
        }

        case WM_ERASEBKGND:
            PatBlt((HDC)wParam, 0, 0, 1240, 720, BLACKNESS);
            return 0;
    }

    return DefWindowProc(wnd, msg, wParam, lParam);;
}

int CALLBACK WinMain(HINSTANCE HInstance, HINSTANCE HPrevInstance, LPSTR LpCmdLine, int NCmdShow) {
    WNDCLASS wndCLass = {};
    wndCLass.style = CS_HREDRAW | CS_VREDRAW;
    wndCLass.lpfnWndProc = win_MainWNDCallback;
    wndCLass.hInstance = HInstance;
    wndCLass.lpszClassName = TEXT("WindowClass");

    if (RegisterClass(&wndCLass)) {
        HWND wnd = CreateWindowEx( 0, wndCLass.lpszClassName, TEXT("FirstTry"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 1240, 720, 0, 0, HInstance, 0);
        if (wnd) {
            MSG msg;
            while (GetMessage(&msg, 0, 0, 0)) {
                if (!bMessageAlreadyShown) {
                    bMessageAlreadyShown = true;
                    MessageBox(NULL, TEXT("Successfully entered loop."), TEXT("Success!"), MB_ICONINFORMATION | MB_OK);
                }
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
    }

    MessageBox(NULL, TEXT("Closing Application."), TEXT("Bye bye!"), MB_ICONINFORMATION | MB_OK);

    return ERROR_SUCCESS;
}

请注意,我删除了您的 bAppIsRunning 变量,因为一旦消息循环处理了 WM_QUIT 消息,它就变得多余了。

我还删除了 ALT-F4 的处理,因为 OS 会自动为您处理。它关闭 window,触发 WM_CLOSE 消息。默认情况下,DefWindowProc() 通过销毁 window 来处理 WM_CLOSE,这会触发 WM_DESTROY 消息。

我还添加了对 WM_ERASEBKGND 的处理以在 window 上绘制背景。从消息循环外部绘制是错误的。一旦 window 需要在屏幕上刷新,您所做的任何绘制都会丢失,因此您必须重新绘制所有内容以响应 WM_ERASEBKGNDWM_PAINT