Окно, созданное с помощью WINAPI, не рисует объекты. В чем проблема?

#c #winapi #gdi

#c #winapi #gdi

Вопрос:

У меня есть мой файл окна ( 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(amp;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(amp;msg, hWnd, 0, 0, PM_REMOVE);
        while(running)
        {
            if(PeekMessage(amp;msg, hWnd, 0, 0, PM_REMOVE))
            {
                if(msg.message == WM_QUIT)
                    running = false;

                TranslateMessage(amp;msg);
                DispatchMessage(amp;msg);
            }
            else
            {
                // Here, the draw function is called.
                PAINTSTRUCT ps;
                HDC hdc = BeginPaint(hWnd, amp;ps);
                Gdiplus::Graphics* g = Gdiplus::Graphics::FromHDC(hdc);
                draw(g);
                EndPaint(hWnd, amp;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(amp;brown_brush, 0, 0, 200, 200);
    }
};

int main()
{
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    Gdiplus::GdiplusStartup(amp;gdiplusToken, amp;gdiplusStartupInput, nullptr);

    AppWindow w;
    w.run();

    Gdiplus::GdiplusShutdown(gdiplusToken);
    return 0;
}
  

У меня проблема в том, что оно просто не рисуется!

Он обрабатывает каждое сообщение, все хорошо, но он не рисует. Отправляются даже сообщения типа WM_PAINT , но ничего не происходит.

Можете ли вы определить проблему?

Мне просто нужен класс window, который имеет переопределяемую draw() функцию с run() функцией, которая обрабатывает все события, такие как WM_LBUTTONDOWN . Все это работает нормально, экран просто остается пустым.

Кроме того, я не могу закрыть окно, при нажатии X кнопки в правом верхнем углу окно просто остается; только после изменения размера и быстрого нажатия X оно закрывается.

Как вы можете видеть, у меня довольно странное поведение, и я не знаю, в чем проблема.

Комментарии:

1. Вы вызываете BeginPaint() извне WM_PAINT обработчика. Это недопустимо.

2. @rodrigo Ах, это действительно была проблема. Теперь это работает.

Ответ №1:

Ваша логика рисования находится не в том месте. Оно должно быть внутри MessageHandler при обработке WM_PAINT сообщения. PeekMessage() сгенерирует WM_PAINT сообщение, если окно необходимо нарисовать, и никакие другие сообщения не ожидаются. Вы не можете рисовать в окне снаружи WM_PAINT обработчика.

Кроме того, вы присваиваете неправильное значение wc.hbrBackground in init() . Если вы используете константу цвета like COLOR_WINDOW , вам нужно добавить 1 к ней. Это указано в WNDCLASS документации.

Кроме того run() , ваш первый PeekMessage() способ создания очереди сообщений отбрасывает начальное сообщение, если оно находится в ожидании, это сообщение не обрабатывается вашим циклом отправки. Этот 1-й вызов должен использовать PM_NOREMOVE флаг вместо этого.

Кроме того, помните об опасностях фильтрации оконных сообщений в вашем цикле сообщений.

С учетом сказанного, попробуйте это вместо:

 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 = amp;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(amp;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(amp;msg, NULL, 0, 0, PM_NOREMOVE);

        while (GetMessage(amp;msg, NULL, 0, 0))
        {
            TranslateMessage(amp;msg);
            DispatchMessage(amp;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, amp;ps);
            if (pThis)
            {
                Gdiplus::Graphics* g = Gdiplus::Graphics::FromHDC(hdc);
                pThis->draw(g);
                delete g;
            }
            EndPaint(hWnd, amp;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(amp;brown_brush, 0, 0, 200, 200);
    }
};

int main()
{
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    Gdiplus::GdiplusStartup(amp;gdiplusToken, amp;gdiplusStartupInput, nullptr);

    AppWindow w;
    w.run();

    Gdiplus::GdiplusShutdown(gdiplusToken);
    return 0;
}
  

Комментарии:

1. Спасибо за этот ответ. Но я действительно не знаю, что происходит в MessageHandler(): «case WM_NCCREATE:{…}». Не возражаете, объяснив это? И является ли ~Window() чем-то вроде «антиконструктора»?

2. Обратите внимание, что я добавил this указатель объекта класса к lpParam параметру CreateWindow() . Таким образом, this указатель может быть передан в MessageHandler() (который не является членом класса) в WM_NCCREATE сообщении (и WM_CREATE ). Затем я сохраняю this указатель в самом HWND себе для использования с более поздними сообщениями, в данном случае WM_PAINT , чтобы он мог вызывать draw() правильный Window объект, окно которого рисуется.

3. ~Window() формально известен как деструктор . Я удивлен, что вы узнали о конструкторах, но не о деструкторах, поскольку они идут рука об руку вместе. Компилятор вызывает конструктор при создании объекта и вызывает деструктор при уничтожении объекта.

4. Это необычный способ сделать это, но разве я не могу просто создать экземпляр window снаружи main() , а затем получить к нему доступ MessageHandler() ? Кстати: я начал с C несколько дней назад, а до этого занимался Java. Но я провел некоторое исследование и понял, что происходит внутри case WM_NCCREATE:{...} .

5. @Lost » разве я не могу просто создать экземпляр окна снаружи main() , а затем получить к нему доступ MessageHandler() ? » — вы могли бы, если у вас одновременно есть только 1 Window объект в памяти. Если вам когда-нибудь понадобится иметь более 1 Window одновременно, этот подход больше не будет работать, поскольку они будут использовать одно и то же MessageHandler() , поэтому он должен иметь возможность различать их. Именно здесь hWnd вступает в игру его параметр, который создает удобное место для хранения this указателя каждого объекта для легкого доступа.