Question

I think I've fallen in the same trap as many before me where I try to impose a nice OO methodology on win32 API programming. No MFC, no AFX, I'm not even using VC++, I'm using C::B with gcc.

I think what I'm trying to do is impossible, but since MFC exists (although I'm not using it) there must be some way.

I've created a class to contain several window controls. It implements handlers for WM_CREATE and WM_COMMAND, and keeps track of all the associated data around my small group of controls (ID codes and HWNDs).

It works great for buttons, static controls, even light GDI methods, but it all breaks down when I try to subclass an edit control.

Really, I just want to capture the "enter" key, but as anybody who's been down that road before will attest, when an edit control has focus, the parent window doesn't receive WM_KEYDOWN or WM_COMMAND, we are left to implement our own proc. Super lame.

OK, so subclassing an edit control is fine, if the editProc is global or static. I know that is because SetWindowLongPtr needs a function address, and that concept is nebulous for a member function.

So the object of my class is declared as "static" inside the parent WndProc. But the function is not "static" because then I wouldn't have access to non-static data members (completely defeating the purpose of this exercise). I'm hoping that because the objest is itself static, I should be able to properly define the address of one of its member functions.

Readers that have tried this before will either have given up and used MFC or something else, or perhaps have found a clever work-around.

I'll let this sample code do the rest of the talking: (simplified - will not compile as such)

/**** myprogram.c ****/
#include "MyControlGroup.h"

int winMain(){ // etc... }

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    // object is static becuse it only needs to be initialized once
    static MyControlGroup myControl; 

    if (msg == WM_CREATE)
        myControl.onWMCreate(hWnd);

    else if (msg == WM_COMMAND)
        myControl.onWMCommand( wParam, lParam );

    else if (msg == WM_DESTROY) 
        PostQuitMessage(0);

    return DefWindowProcW(l_hWnd, l_msg, l_wParam, l_lParam);
}

The header file for my class:

/**** MyControlGroup.h ****/
class MyControlGroup
{
private:
    HWND m_hWndParent;
    HWND m_hWndEditBox;
    int  m_editBoxID;
public:
    MyControlGroup();
    void onWMCreate(HWND);
    void onWMCommand(WPARAM, LPARAM);

    // want to find a way to pass the address of this function to SetWindowLongPtr
    LRESULT myEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
};

...and the implementation:

/**** MyControlGroup.cpp ****/
static int staticID = 1;
MyControlGroup::MyControlGroup()
{
    m_editBoxID = staticID++;
}

void MyControlGroup::onWMCreate(HWND hWnd)
{
    // My control group has buttons, static controls, and other stuff which are created here with CreateWindowW.  It also has an edit control:
    m_hWndEditBox = CreateWindowW(L"EDIT", L"initial text", WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 10, 150, 20, hWnd, (HMENU)m_editBoxID, NULL, NULL);

    /* 
    To subclass the edit control, I need a pointer to my customized proc.  That means I 
    need a pointer-to-member-function, but SetWindowLongPtr needs a pointer to global or 
    static function (__stdcall or CALLBACK, but not __thiscall).
    */

    // I'd like to do something like this, adapted from a great write-up at
    // http://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible

    LERSULT (MyControlGroup::*myEditProcPtr)(HWND, UINT, WPARAM, LPARAM);
    myEditProcPtr = &MyControlGroup::myEditProc;

    // Up to now it compiles ok, but then when I try to pass it to SetWindowLongPtr, I get 
    // an "invalid cast" error.  Any ideas?
    SetWindowLongPtr(m_hWndEditBox, GWLP_WNDPROC, (LPARAM)myEditProcPtr);
}

void MyControlGroup::onWMCommand(WPARAM wParam, LPARAM lParam){ /* process parent window messages.  Editboxes don't generate WM_COMMAND or WM_KEYDOWN in the parent :''( */}

LRESULT MyControlGroup::myEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    // process messages like IDOK, WM_KEYDOWN and so on in the edit control
}

Even once I get this done, I'll still need to figure out a way to pass the address of the parent WndProc to myEditProc for the return value, but until I get past this there is no point in worrying about that.

Thanks in advance for reading!

Was it helpful?

Solution

myEditProc needs to be a static function. Once you've done that you can pass the address of the function directly without going through the intermediate variable:

static LRESULT myEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
...
SetWindowLongPtr(m_hWndEditBox, GWLP_WNDPROC, (LPARAM)myEditProc);

To access your class data from the static function, you can save it in the userdata field of the edit control, e.g.:

// before sub-classing the control
SetWindowLongPtr(m_hWndEditBox, GWLP_USERDATA, (LPARAM)this);

// in the sub-class procedure
MyControlGroup* pThis = (MyControlGroup*)GetWindowLongPtr(m_hWndEditBox, GWLP_USERDATA);

But as @K-ballo suggested, SetWindowSubclass is definitely the way to do this unless you want compatibility with pre-XP. It handles the sub-classing procedure for you automatically, lets you associate a userdata pointer (e.g. this) that is automatically passed to the sub-class procedure, and safely handles removing the sub-class at the end.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top