1

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!

Peter
  • 13
  • 3

1 Answers1

1

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.

Jonathan Potter
  • 36,172
  • 4
  • 64
  • 79
  • Beautiful. Passing a pointer to the calling object in GWLP_USERDATA was just the ticket! – Peter Jan 07 '13 at 04:07
  • Note that the GWLP_USERDATA slot belongs to the EDIT code - not you! Turns out that you might have got lucky here and EDIT code might not actually use this, but generally speaking, you don't want to make that assumption. By all means use _USERDATA in cases where you own the whole class; but if you're subclassing another class, better bets are to keep track of HWNDs and associated data in a separate map, or use SetProp() as outlined in [this article about subclassing](http://msdn.microsoft.com/en-us/library/windows/desktop/bb773183(v=vs.85).aspx#prior_store_data) – BrendanMcK Jan 07 '13 at 15:14
  • @BrendanMcK: No, there's no hard and fast rule about that - e.g. see http://blogs.msdn.com/b/oldnewthing/archive/2005/03/03/384285.aspx#395978 (Raymond's comment at the bottom if the anchor link doesn't work). None of the standard windows controls use `GWLP_USERDATA` themselves. SetProp is another way to do it but by far the best option these days is to use `SetWindowSubclass` as I mentioned above. – Jonathan Potter Jan 07 '13 at 18:16
  • @JonathanPotter - and that's why I said "...but generally speaking, you don't want to make that assumption". Why write code that relies on undocumented behavior when you can as easily write code that works in the general case? Raymond's article also says "Note that this value [...] belongs to the window class" - and the window class here is EDIT; subclassing by changing the wndproc doesn't change that. (And apparently SysLink *does* use this, at least on XP! - see comment at end of [this page](http://msdn.microsoft.com/en-us/library/windows/desktop/bb760706(v=vs.85).aspx)) – BrendanMcK Jan 07 '13 at 18:29
  • @BrendanMcK: SysLink isn't one of the standard window controls, it's a common control, and in any event the OP was specifically talking about the Edit control and not asking for a hypothetical solution that would always work. Your point is valid and as I keep saying, `SetWindowSubclass` is a far better solution to either method. – Jonathan Potter Jan 07 '13 at 18:53