Win32: More “object oriented” window message handl

2019-02-07 15:19发布

问题:

In Win32 API a window has the pointer to a user defined version of WndProc function that handling its messages.

There are some ways to cover this low-level mechanism with a solution like MFC message map and so on.

In my very little app I'm looking for a way to encapsulate this low-level thing with a object oriented solution.

I tried to create a C++ map with HWND key and "MyWindowClass" item and when I created an object of the MyClass I added a pair to the map and then looking for the MyWindowClass object by HWN. But the problem is after CreateWindowEx called Win32 internally send a WM_CREATE message to the just created window so I can't add pair in the map before this message and can't control WM_CREATE through passing it to the object instanced WndProc.

The code is:

#ifndef NOTIFYWINDOW_H
#define NOTIFYWINDOW_H

#include "Bacn.h"

class NotifyWindow
{
private:

    HWND m_hWnd;

    Gdiplus::Graphics* m_graphics;

protected:

    static std::map<HWND, NotifyWindow*> s_NotifyWindows;

    static LRESULT CALLBACK s_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

    static void s_WndMessageLoop();

    LRESULT WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam);

    void Initialize();

    void OnPaint();

    void OnCreate();

public: 

    NotifyWindow();

    ~NotifyWindow();
};

#endif //NOTIFYWINDOW_H

And its implementation:

#include "NotifyWindow.h"

using namespace Gdiplus;
using namespace std;

map<HWND*, NotifyWindow> NotifyWindow::s_NotifyWindows;

LRESULT CALLBACK NotifyWindow::s_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    map<HWND, NotifyWindow*>::iterator search = s_NotifyWindows.find(hWnd);
    if (search != s_NotifyWindows.end())
    {
        search->second->WndProc(uMsg, wParam, lParam);
    }

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

void NotifyWindow::s_WndMessageLoop()
{

}

LRESULT NotifyWindow::WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CREATE:
        OnCreate();
        break;
    case WM_PAINT:
        OnPaint();
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_SIZE:
        break;
    case WM_SETFOCUS:
        break;
    case WM_KILLFOCUS:
        break;
    case WM_MOUSEMOVE:
        break;
    default:
        return DefWindowProc(m_hWnd, uMsg, wParam, lParam);
    }
    return 0;
}

void NotifyWindow::Initialize()
{
    WNDCLASSEX wc;

    const wchar_t *className = L"BacnNotifyWindowClass";
    const wchar_t *windowName = L"BacnNotifyWindow";

    HINSTANCE hInstance = GetModuleHandle(NULL);

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.lpszClassName = className;
    wc.lpfnWndProc = NotifyWindow::s_WndProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon = NULL;
    wc.hIconSm = NULL;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;

    RegisterClassEx(&wc);

    DWORD dwExtStyle = WS_EX_TOPMOST;
    DWORD dwStyle = WS_POPUP | WS_SYSMENU;  

    m_hWnd = CreateWindowEx(
        dwExtStyle,
        className,
        windowName,
        dwStyle,
        300,
        300,
        100,
        100,
        NULL,
        NULL,
        hInstance,
        NULL);  

    s_NotifyWindows.insert(pair<HWND, NotifyWindow*>(m_hWnd, this));

    ShowWindow(m_hWnd, SW_SHOW);
}

NotifyWindow::NotifyWindow()
{
    Initialize();   
}

NotifyWindow::~NotifyWindow()
{

}

void NotifyWindow::OnPaint()
{

}

void NotifyWindow::OnCreate()
{

}

回答1:

Suggestion: make WndProc virtual in your window base class. Request Windows to allocate extra memory for each window instance big enough to store a pointer (use cbWndExtra). When creating a window, put a pointer to your windows class object into this extra memory associated with each window instance using SetWindowLongPtr. In your static sWndProc, retrieve this pointer using GetWindowLongPtr and call your window base class function virtual WndProc. In my opinion, it's more neat way than having a whole extra map object dedicated to dispatching WndProc calls.

EDIT: Plus, you do realize, in your code you are trying to register Windows window class every time you create an object of your window class? If you create just one window this is technically fine, I guess, but even then it's error-prone design. Windows window class should be registered just once, not every time you create a window with this Windows window class.

EDIT: Also, in your code, if you are not processing Windows message in your WndProc, you code will call DefWindowProc twice for this message: first time in member function within switch clause, second time in static sWndProc. DefWindowProc shouldn't be called twice on the same message.

EDIT: Sorry, I missed your actual question before somehow, I thought your post was about design, not about WM_CREATE. In order to process WM_NCCREATE, WM_NCCALCSIZE or WM_CREATE in a uniform way, you can set lpParam in call to CreateWindowEx to, again, pointer to the object of your window class. This parameter will be passed to your static sWndProc as a member of CREATESTRUCT with WM_NCCREATE and WM_CREATE. You can process, say, WM_NCCREATE (first message to be sent) inside static sWndProc, get this pointer to your object, use SetWindowLongPtr to put it into window instance extra memory, and then use it to call member function WndProc (just be careful about calling not fully created object of your windows class, if you call CreateWindowEx from its constructor). This way, you don't need to worry about "low-level" Windows message dispatching anywhere else in your program. You, of course, will still need your member function WndProc to dispatch messages to actual function calls.