Создание подкласса элемента управления редактирования из пользовательской функции класса / указателя на член

Я думаю, что попал в ту же ловушку, что и многие до меня, где я пытаюсь навязать приятную ОО-методологию для программирования Win32 API. Нет MFC, нет AFX, я даже не использую VC++, я использую C::B с gcc.

Я думаю, что то, что я пытаюсь сделать, невозможно, но поскольку MFC существует (хотя я не использую его), должен быть какой-то путь.

Я создал класс для нескольких оконных элементов управления. Он реализует обработчики для WM_CREATE и WM_COMMAND и отслеживает все связанные данные вокруг моей небольшой группы элементов управления (идентификационные коды и HWND).

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

На самом деле, я просто хочу захватить клавишу "ввод", но, как засвидетельствует любой, кто был на этом пути раньше, когда элемент управления редактирования имеет фокус, родительское окно не получает WM_KEYDOWN или WM_COMMAND, нам остается реализовать наш собственный Proc. Супер хромая.

Хорошо, так что подклассы элемента управления редактирования - это нормально, если editProc является глобальным или статическим. Я знаю, что это потому, что SetWindowLongPtr нужен адрес функции, и эта концепция неясна для функции-члена.

Таким образом, объект моего класса объявлен как "статический" внутри родительского WndProc. Но эта функция не является "статической", потому что тогда у меня не будет доступа к нестатическим элементам данных (полностью игнорируя цель этого упражнения). Я надеюсь, что, поскольку объект сам по себе является статическим, я должен иметь возможность правильно определить адрес одной из его функций-членов.

Читатели, которые пробовали это раньше, либо сдаются и используют MFC, либо что-то еще, либо, возможно, найдут умный обходной путь.

Я позволю этому примеру кода сделать остальную часть разговора: (упрощенно - не будет компилироваться как таковой)

/**** 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);
}

Заголовочный файл для моего класса:

/**** 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);
};

... и реализация:

/**** 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
}

Даже когда я это сделаю, мне все равно нужно будет найти способ передать адрес родительского WndProc в myEditProc для возвращаемого значения, но до тех пор, пока я не преодолею это, нет смысла беспокоиться об этом.

Заранее спасибо за чтение!

1 ответ

Решение

myEditProc должна быть статической функцией. Сделав это, вы можете передать адрес функции напрямую, не переходя через промежуточную переменную:

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

Чтобы получить доступ к данным вашего класса из статической функции, вы можете сохранить их в поле userdata элемента управления редактирования, например:

// 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);

Но, как подсказал @K-ball, SetWindowSubclass это определенно способ сделать это, если вы не хотите совместимости с pre-XP. Он обрабатывает процедуру подкласса для вас автоматически, позволяет связать указатель пользовательских данных (например, this), который автоматически передается процедуре подкласса и безопасно обрабатывает удаление подкласса в конце.

Другие вопросы по тегам