【问题标题】:Basic window creation基本窗口创建
【发布时间】:2018-11-12 15:11:58
【问题描述】:

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

预期行为:

  1. 创建一个名为“FirstTry”的窗口
  2. 使用 PatBlt 将其背景设为黑色
  3. 在第一次进入主循环时以及按“w”后显示一个消息框。
  4. 按下 Alt+F4、Escape 或关闭按钮时关闭窗口,显示关闭消息。

观察到的行为:

  1. 如预期的那样
  2. 如预期的那样
  3. MessageBox 第一次出现,但不能用 'w' 重新触发
  4. 窗口不可关闭,但 TaskManager 除外(有一次它按预期显示“正在关闭的应用程序”-MessageBox,但只有一次)
    • 窗口可拖动,直到第一个“进入循环”-MessageBox 关闭,之后其固定
    • windows10 的小蓝色“忙碌”圆圈在第一个 MessageBox 之后全时显示

结论:消息处理被破坏。 而且我不知道为什么......


系统:

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

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

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

【问题讨论】:

  • 如果你有 VS 2017,为什么要从命令行构建?学习使用 IDE 和(尤其是)调试器。
  • @IInspectable 我认为这是最低要求,因为它只包含窗口创建、窗口回调和消息处理。下次我会尽量短一点,抱歉。
  • @PaulSanders 我正在使用 VS Code 及其调试器。 VS2017 对我的笔记本电脑来说有点太重了。无论哪种方式,我是使用 IDE 编译并在那里设置编译器参数,还是通过 cmd-line 进行编译都无关紧要
  • 哦,好吧,我从来没有用过,所以我不知道它有什么能力。不过,如果它有一个可以与本机 C++ 一起使用的调试器,我会使用它。有迹象表明here 确实如此,或者您可以安装一些启用它的东西。我不能太强调一个像样的调试器是多么重要。如果您至少不尝试在您的平台上运行某些东西,这里的人们将非常不愿意帮助您。雷米在那里加倍努力。

标签: windows winapi input message-queue


【解决方案1】:

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

您也在使用基于 TCHAR 的 API,但滥用了 LPCTSTR 类型转换。将字符串/字符文字转换为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 的处理,因为操作系统会自动为您处理。它关闭窗口,触发WM_CLOSE 消息。默认情况下,DefWindowProc() 通过销毁窗口来处理WM_CLOSE,这会触发WM_DESTROY 消息。

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

【讨论】:

  • 谢谢,我把我的和你的比较了一下,发现我忘记了 PeekMessage 开关的默认情况。这是唯一的问题
  • TEXT 宏和显式 Unicode 或 ANSI 版本的 API 调用不兼容。
  • @IInspectable 我解决了这个问题。
  • @Dan I compared mine to yours... 真的吗?这是你能看到的唯一区别?在这种情况下,你需要再看一遍。
  • 这不是重点。 Remy 的消息循环和 WndProc 显示了它应该如何完成。如果您还没有掌握这一点,那么这里什么都没有取得。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多