使用 WINAPI 创建的窗口不是绘图对象。有啥问题?

Posted

技术标签:

【中文标题】使用 WINAPI 创建的窗口不是绘图对象。有啥问题?【英文标题】:Window created with WINAPI is not drawing objects. What's the problem?使用 WINAPI 创建的窗口不是绘图对象。有什么问题? 【发布时间】:2020-10-20 17:44:42 【问题描述】:

我有我的窗口文件 (Window.h):

LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);

class Window

private:
    HWND hWnd;
    HINSTANCE hInstance;
    bool running = true;
    const char* ID = "WINAPI_JVM64";
public:
    Window()
    
        init();
    

    virtual void draw(Gdiplus::Graphics*) = 0;

    void init()
    
        hInstance = (HINSTANCE)GetModuleHandle(NULL);
        WNDCLASS wc;

        wc = ;
        wc.style = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc = MessageHandler;
        wc.hInstance = hInstance;
        wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
        wc.hCursor = LoadCursor(NULL, IDC_HAND);
        wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
        wc.lpszClassName = ID;

        assert(RegisterClass(&wc));

        hWnd = CreateWindow(ID, "Title", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                       200, 200, 400, 400, NULL, NULL, hInstance, NULL);

        ShowCursor(true);
        SetForegroundWindow(hWnd);
        SetFocus(hWnd);
    
    void run()
    
        MSG msg;
        PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE);
        while(running)
        
            if(PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE))
            
                if(msg.message == WM_QUIT)
                    running = false;

                TranslateMessage(&msg);
                DispatchMessage(&msg);
            
            else
            
                // Here, the draw function is called.
                PAINTSTRUCT ps;
                HDC hdc = BeginPaint(hWnd, &ps);
                Gdiplus::Graphics* g = Gdiplus::Graphics::FromHDC(hdc);
                draw(g);
                EndPaint(hWnd, &ps);
            
        
        UnregisterClass(ID, hInstance);
    
;

还有主文件(main.cpp):

#include "Window.h"

LRESULT CALLBACK MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

    switch(uMsg)
    
    case WM_CLOSE:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    
    return 0;


class AppWindow : public Window

public:
    void draw(Gdiplus::Graphics* g) override
    
        Gdiplus::SolidBrush brown_brush(Gdiplus::Color(255, 128, 57, 0));
        g->FillRectangle(&brown_brush, 0, 0, 200, 200);
    
;

int main()

    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);

    AppWindow w;
    w.run();

    Gdiplus::GdiplusShutdown(gdiplusToken);
    return 0;

我的问题是它不会画!

它处理每条消息,一切都很好,但它不绘制。甚至WM_PAINT 类型的消息也被发送,但没有任何反应。

你能发现问题吗?

我只想要一个窗口类,它有一个可覆盖的draw() 函数和一个处理所有事件的run() 函数,例如WM_LBUTTONDOWN。所有这一切都很好,屏幕只是保持空白。

另外,我无法关闭窗口,当按下右上角的X 按钮时,窗口只是停留;只有在调整大小并快速按下X 后,它才会关闭。

如您所见,我有一些非常奇怪的行为,我不知道问题出在哪里。

【问题讨论】:

您正在从WM_PAINT 处理程序外部调用BeginPaint()。这是不允许的。 @rodrigo 啊,这确实是问题所在。现在可以了。 【参考方案1】:

你的绘图逻辑放错地方了。处理WM_PAINT 消息时,它需要在MessageHandler 内。 PeekMessage() 如果需要绘制窗口并且没有其他消息未决,则将生成 WM_PAINT 消息。您不能在 WM_PAINT 处理程序之外的窗口上绘图。

另外,您在init() 中为wc.hbrBackground 分配了错误的值。如果您使用像COLOR_WINDOW 这样的颜色常量,则需要在其中添加1。这在WNDCLASS documentation 中有很多说明。

此外,在run() 中,您创建消息队列的第一个PeekMessage() 正在丢弃一条初始消息(如果有一条消息处于待处理状态),那么您的调度循环不会处理该消息。第一次调用应该使用PM_NOREMOVE 标志。

另外,请注意消息循环中的the dangers of filtering window messages。

话虽如此,试试这个:

LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);

class Window

private:
    HWND hWnd;
    HINSTANCE hInstance;
    const char* ID = "WINAPI_JVM64";

public:
    Window()
    
        init();
    

    ~Window()
    
        cleanup();
    

    virtual void draw(Gdiplus::Graphics*) = 0;

    void init()
    
        hInstance = (HINSTANCE)GetModuleHandle(NULL);

        WNDCLASS wc;
        wc.style = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc = &MessageHandler;
        wc.hInstance = hInstance;
        wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
        wc.hCursor = LoadCursor(NULL, IDC_HAND);
        wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
        wc.lpszClassName = ID;

        assert(RegisterClass(&wc));

        hWnd = CreateWindow(ID, "Title", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                       200, 200, 400, 400, NULL, NULL, hInstance, this);
        assert(hWnd != NULL);

        ShowCursor(true);
        SetForegroundWindow(hWnd);
        SetFocus(hWnd);
    

    void cleanup()
    
        UnregisterClass(ID, hInstance);
    

    void run()
    
        MSG msg;
        PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);

        while (GetMessage(&msg, NULL, 0, 0))
        
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        
    
;
#include "Window.h"

LRESULT CALLBACK MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

    switch (uMsg)
    
        case WM_NCCREATE:
        
            Window *pThis = static_cast<Window*>(reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams);
            SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pThis));
            break;
        

        // DefWindowProc(WM_CLOSE) calls DestroyWindow(),
        // WM_CLOSE is not the right place to call PostQuitMessage()...
        //case WM_CLOSE:
        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_PAINT:
        
            Window *pThis = reinterpret_cast<Window*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            if (pThis)
            
                Gdiplus::Graphics* g = Gdiplus::Graphics::FromHDC(hdc);
                pThis->draw(g);
                delete g;
            
            EndPaint(hWnd, &ps);
            return 0;
        
    

    return DefWindowProc(hWnd, uMsg, wParam, lParam);


class AppWindow : public Window

public:
    void draw(Gdiplus::Graphics* g) override
    
        Gdiplus::SolidBrush brown_brush(Gdiplus::Color(255, 128, 57, 0));
        g->FillRectangle(&brown_brush, 0, 0, 200, 200);
    
;

int main()

    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);

    AppWindow w;
    w.run();

    Gdiplus::GdiplusShutdown(gdiplusToken);
    return 0;

【讨论】:

感谢您的回答。但我真的不知道 MessageHandler() 中发生了什么:“case WM_NCCREATE:...”。介意解释一下吗? ~Window() 是不是类似于“反构造函数”? 请注意,我将类对象的this 指针添加到lpParam 参数CreateWindow()。这样,this 指针可以传递到WM_NCCREATE(和WM_CREATE)消息中的MessageHandler()(不是该类的成员)。然后我将this 指针存储在HWND 本身中以供以后的消息使用,在本例中为WM_PAINT,因此它可以在正在绘制窗口的正确Window 对象上调用draw()。跨度> ~Window() 正式称为destructor。我很惊讶你了解了构造函数而不是析构函数,因为它们是齐头并进的。编译器在创建对象时调用构造函数,在对象销毁时调用析构函数。 这是一种奇特的方式,但我不能只在main() 之外创建窗口实例,然后在MessageHandler() 中访问它吗?顺便说一句:我几天前开始使用 C++,在那之前做过 Java。但我做了一些研究,了解了case WM_NCCREATE:... 中发生的情况。 @Lost "我不能在main() 之外创建窗口实例,然后在MessageHandler() 中访问它吗?" - 如果你只有一次在内存中有 1 个 Window 对象。如果您一次需要超过 1 个Window,则该方法将不再有效,因为它们将共享相同的MessageHandler(),因此需要能够区分它们。这就是它的hWnd 参数发挥作用的地方,它为存储每个对象的this 指针提供了一个方便的位置,以便于访问。

以上是关于使用 WINAPI 创建的窗口不是绘图对象。有啥问题?的主要内容,如果未能解决你的问题,请参考以下文章

Winapi 应用程序在绘图时冻结

无法使用 WinAPI 显示窗口

WinApi中的GetClientRect和GetWindowRect有啥区别?

如何使用winapi实现类似Steam的窗口?

单缓冲和双缓冲 有啥区别

防止创建绘图窗口 - Pyqtgraph plot