关于winapi:基本窗口创建

Basic window creation

我在处理Windows窗口时遇到了问题,尽管我以前曾经这样做过,而且工作正常。在阅读了有关此问题的最常见建议之后,它仍然存在。有人可以告诉我为什么输入处理被破坏了吗?

预期行为:

  • 创建一个名为" FirstTry"的窗口
  • 使用PatBlt将背景变黑
  • 首次进入主循环时,以及按" w"后,将显示一个消息框。
  • 当按下Alt + F4,Escape或"关闭"按钮时,关闭窗口,显示关闭消息。
  • 观察到的行为:

  • 如预期
  • 如预期
  • MessageBox第一次出现,但不能用'w'重新触发
  • 窗口不可关闭,除了使用TaskManager之外(它一次显示了"关闭应用程序" -MessageBox的状态,但只有一次)

    • 可拖动窗口,直到第一个"进入循环" -MessageBox关闭,然后将其固定
    • 在第一个MessageBox之后,全天显示Windows10的蓝色小"忙碌"圆圈
  • 结论:消息处理已损坏。
    我不知道为什么...

    系统:

    • Windows 10版本1803(内部版本17134.81),64位

    VS 2017 Community Edition的编译器:

    • vcvarsall.bat amd64

    • cl -MTd -nologo -FC -Zi -W4 -WX -wd4100 -wd4312 FirstTry.cpp / link User32.lib Gdi32.lib

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    #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类型转换。将字符串/字符常量转换为TCHAR时,需要使用TEXT()宏。

    尝试类似这样的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    #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的处理,因为操作系统会自动为您处理。它关闭窗口,触发WM_CLOSE消息。默认情况下,DefWindowProc()通过销毁窗口来处理WM_CLOSE,这会触发WM_DESTROY消息。

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