Basic window creation

2019-08-19 04:04发布

问题:

I have a problem handling a Windows window, even though I did it this way once before and it worked fine. After reading through the most common suggestions for this problem, it still resides. Could someone tell me why Input-handling is broken?

Intended Behaviour:

  1. create a window titled 'FirstTry'
  2. Make its background black, using PatBlt
  3. Show a message box when the main loop is entered the first time, and after pressing 'w'.
  4. Close the window when pressing either Alt+F4, Escape, or the Close-button, displaying a closing message.

Observed behaviour:

  1. as intended
  2. as intended
  3. MessageBox shows up the first time, but is not retriggerable with 'w'
  4. Window it not closable, except with TaskManager (one time it showed the 'closing Application'-MessageBox as intended, but only one time)
    • window draggable until the first 'entered loop'-MessageBox is closed, after that its fixed
    • little blue 'busy'-circle of windows10 is shown full-time, after the first MessageBox

Conclusion: The Message-handling is broken. And i cannot figure out why...


System:

  • Windows 10, Version 1803 (Build 17134.81), 64-bit

Compiler from VS 2017 Community Edition:

  • vcvarsall.bat amd64

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


#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;
}

回答1:

The main problem with your code is that you are calling TranslateMessage() and DispatchMessage() only when you receive certain key press messages. You need to call them in your main message loop for ALL messages instead. And you should be processing ALL of the messages in your WndProc callback.

You are also using TCHAR-based APIs, but are misusing LPCTSTR typecasts. You need to use the TEXT() macro instead when casting string/char literals to TCHAR.

Try something more like this instead:

#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;
}

Note that I removed your bAppIsRunning variable, as it has beecome redundant once the message loop processes the WM_QUIT message instead.

I also removed handling of ALT-F4, as the OS handles that for you automatically. It closes the window, triggering a WM_CLOSE message. By default, DefWindowProc() handles WM_CLOSE by destroying the window, which triggers a WM_DESTROY message.

I also added handling for WM_ERASEBKGND to draw a background on the window. Drawing from outside of the message loop is wrong. As soon as the window needs to be refreshed onscreen, any drawing you do is lost, so you have to redraw everything in response to WM_ERASEBKGND and WM_PAINT.