CRefice
CRefice

Reputation: 430

How can I retrieve the message-sending object from WndProc's parameters?

So I'm writing my own little GUI framework, wrapping the Windows API in useful classes. I'm currently trying to have the user handle WndProc's messages in an object-oriented way, but I've hit a bit of a snag.

I have a Button class, that inherits from an abstract Control class, that is pretty much a wrapper around the Button's HWND handle. There's also a Window class, that contains (what do you know) a handle to a window. This class also has a container of Controls, and is responsible for creating its own (static) WndProc method.

What I'm trying to do is have a big switch statement in the containing window's WndProc that, based on the function's wParam and lParam parameters, calls the appropriate sending control's handling function. The way I've seen most people do something like it is this:

#define MY_BUTTON 101 //Define button's ID

Button::Button()
{
    hwndButton= CreateWindow(
        "BUTTON", text,
        WS_VISIBLE | WS_CHILD,
        position.x, position.y,
        size.x, size.y,
        parent_handle,
        (HMENU)MY_BUTTON,
        GetModuleHandle(NULL),
        NULL);
}
Button* button = new Button();

//Later on, in the Window class...

LRESULT CALLBACK Window::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch((LOWORD)wParam)
    {
    case MY_BUTTON:
        button->HandleMessage(&msg);
        break;
    }
}

However, I don't want the user to assign a unique integer for every object instance they create. Rather, since I have a container of controls, I would rather do something like this:

//In the Window Class...
Button* button = new Button();
AddControl(button);

static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(&msg)
    {
        ObtainControlFromParams(wParam, lParam)->HandleMessage(&msg);
        //ObtainControlFromParams should return a pointer to a Control, 
        //and it should look into the window's Control collection.
    }
}

For as great as this sounds, I can't find a way to implement the ObtainControlFromParams function.

The way I thought of to distinguish every control instance is to have a string, that I call the control's "name", that should be unique to every object instantiation. This would leave me with two options (that I can think of)

I apologize if what I'm trying to obtain isn't really clear, it's kind of a complicated concept to me.

Any help is greatly appreciated!

Upvotes: 2

Views: 1436

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 595339

Store the button's Control* object pointer in the button HWND itself where the message procedure can retrieve it. You can use (Set|Get)WindowLongPtr(GWL_USERDATA) or (Get|Set)SetProp() for that purpose.

Only certain messages, like WM_COMMAND and WM_NOTIFY, identify the control that sends them. These messages are sent to the control's parent window. These messages contain the child control's HWND. The parent can retrieve the child's Control* and forward the message to it.

Other messages are sent directly to the control's own window, not its parent window. These messages do not identify the control they are being sent to. As such, each Control needs its own individual WndProc procedure for its own HWND. Use SetWindowLongPtr(GWL_WNDPROC) or SetWindowSubclass() to assign that WndProc to the HWND after CreateWindow() is called.

Try something like this (this is very rough, there is a lot more involved in wtiting a UI framework, but this should give you some ideas):

typedef std::basic_string<TCHAR> tstring;

class Control
{
private:
    HWND fWnd;
    Control *fParent;
    POINT fPosition;
    SIZE fSize;
    tstring fText;
    std::list<Control*> fControls;
    ...

    void addControl(Control *c);
    void removeControl(Control *c);

    virtual void createWnd() = 0;
    void internalCreateWnd(LPCTSTR ClassName, DWORD style, DWORD exstyle);

    ...

    static LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

protected:
    virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
    ...

public:
    Control();
    virtual ~Control();

    HWND getWnd();
    void destroyWnd();
    void wndNeeded();

    Control* getParent();
    void setParent(Control *value);

    POINT getPosition();
    void setPosition(POINT value);

    SIZE getSize();
    void setSize(SIZE value);

    tstring getText();
    void setText(const tstring &value);
    ...
};

static const LPTSTR szControlPropStr = TEXT("ControlPtr")

Control* ControlFromWnd(HWND hwnd)
{
    return (Control*) GetProp(hwnd, szControlPropStr);
}

Control::Control()
    : fWnd(0), fParent(0)
{
    fPosition.x = fPosition.y = 0;
    fSize.cx = fSize.cy = 0;
    ...
}

Control::~Control()
{
    setParent(0);
    destroyWnd();
}

void Control::addControl(Control *c)
{
    fControls.push_back(c);
    c->fParent = this;
    if (fWnd)
            c->wndNeeded();
}

void Control::removeControl(Control *c)
{
    fControls.remove(c);
    c->destroyWnd();
    c->fParent = 0;
}

HWND Control::getWnd()
{
    wndNeeded();
    return fWnd;
}

void Control::internalCreateWnd(LPCTSTR ClassName, DWORD style, DWORD exstyle)
{
    style |= WS_VISIBLE;
    if (fParent)
         style |= WS_CHILD;

    fWnd = CreateWindowEx(exstyle,
        ClassName, fText.c_cstr(), style,
        fPosition.x, fPosition.y,
        fSize.cx, fSize.cy,
        (fParent) ? fParent->getWnd() : 0,
        0,
        GetModuleHandle(NULL),
        NULL);
    SetProp(fWnd, szControlPropStr, (HANDLE)this);
    SetWindowLongPtr(fWnd, GWL_WNDPROC, (LONG_PTR)&Control::WndProc);
}

void Control::destroyWnd()
{
    DestroyWindow(fWnd);
    fWnd = 0;
}

void Control::wndNeeded()
{
    if (!fWnd)
    {
        createWnd();
        for (std::list<Control*>::iterator iter = fControls.begin(); iter != fControls.end(); ++iter)
            iter->wndNeeded();
    }
}

Control* Control::getParent()
{
    return fParent;
}

void Control::setParent(Control *value)
{
    if (fParent != value)
    {
        if (fParent)
            fParent->removeControl(this);
        fParent = value;
        if (fParent)
            fParent->addControl(this);
    }
}

POINT Control::getPosition()
{
    return fPosition;
}

void Control::setPosition(POINT value)
{
    fPosition = value;
    if (fWnd)
        SetWindowPos(fWnd, 0, fPosition.x, fPosition.y, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
}

SIZE Control::getSize()
{
    return fSize;
}

void Control::setSize(SIZE value)
{
    fSize = value;
    if (fWnd)
        SetWindowPos(fWnd, 0, 0, 0, fSize.cx, fSize.cy, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
}

tstring Control::getText()
{
    return fText;
}

void Control::setText(const tstring &value)
{
    fText = value;
    if (fWnd)
        SetWindowText(fWnd, fText.c_str());
}

LRESULT CALLBACK Control::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    Control *pThis = ControlFromWnd(hwnd);
    if (pThis)
        return pThis->HandleMessage(uMsg, wParam, lParam);
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

LRESULT Control::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        WM_NCDESTROY:
        {
            RemoveProp(fWnd, szControlPropStr);
            fWnd = 0;
            break;
        }

        case WM_COMMAND:
        {
            HWND hwnd  = (HWND) lParam;
            if ((hwnd) && (hwnd != fWnd))
            {
                Control *c = ControlFromWnd(hwnd);
                if  (c)
                    return c->HandleMessage(uMsg, wParam, lParam);
            }
            ...
            break;
        }

        case WM_NOTIFY:
        {
            NMHDR *hdr = (NMHDR*) lParam;
            if ((hdr->hwndFrom) && (hdr->hwndFrom != fWnd))
            {
                Control *c = ControlFromWnd(hdr->hwndFrom);
                if  (c)
                    return c->HandleMessage(uMsg, wParam, lParam);
            }
            ...
            break;
        }

        case WM_WINDOWPOSCHANGED:
        {
            WINDOWPOS *p = (WINDOWPOS*) lParam;

            if (!(p->flags & SWP_NOMOVE))
            {
                fPosition.x = p->x;
                fPosition.y = p->y;
            }

            if (!(p->flags & SWP_NOSIZE))
            {
                fSize.cx = p->cx;
                fSize.cy = p->cy;
            }

            ...
            return 0;
        }

        case WM_SETTEXT:
        {
            LRESULT ret = DefWindowProc(fWnd, uMsg, wParam, lParam);
            if (ret == TRUE)
                fText = (TCHAR*) lParam;
            return ret;
        }

        ...
    }

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

class Button : public Control
{
protected:
    virtual void createWnd();
    virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);

public:
    Button();
};

Button::Button()
    : Control()
{
}

void Button::createWnd()
{
    Control::intetnalCreateWnd(TEXT("BUTTON"), BS_PUSHBUTTON | BS_TEXT, 0);
    ...
}

LRESULT Button::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_COMMAND:
        {
            HWND hwnd = (HWND) lParam;
            if (hwnd != fWnd)
                break;

            switch (HIWORD(wParam))
            {
                case BN_CLICKED:
                {
                    ...
                    return 0;
                }

                ...
            }

            break;
        }
    }

    return Control::HandleMessage(uMsg, wParam, lParam);
}

//In the Window Class...
Button* button = new Button();
button->setPosition(...);
button->setSize(...);
button->setText(...);
button->setParent(this);

Upvotes: 5

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145194

The usual approach is to reflect messages like WM_COMMAND and WM_NOTIFY, which are sent to the control's parent window, back to the control that sent them. This is possible because these messages identify the sender (each in a unique way, so check the docs).

There are a number of ways to associate a C++ object pointer with a window:

  • Dynamically generated trampoline function as window-proc.
    Most efficient but also most tricky. Probably needs to use VirtualAlloc.

  • Window properties.
    The SetProp and GetProp functions.

  • Window object words.
    SetWindowLongPtr. Needs to be sure there's allocated space.

  • Static map.
    E.g. a singleton std::unordered_map, mapping handle → object.

  • Whatever's used by Windows standard subclassing.
    I.e. using SetWindowSubclass.

Upvotes: 5

Related Questions