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

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

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

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

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

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

Хорошо, так что создание подкласса элемента управления редактирования — это нормально, если 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 для возвращаемого значения, но пока я не преодолею это, нет смысла беспокоиться об этом.

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


person Peter    schedule 06.01.2013    source источник
comment
Используйте SetWindowSubclass, если можете...   -  person K-ballo    schedule 07.01.2013


Ответы (1)


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

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

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

// 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-ballo, SetWindowSubclass определенно подходит для этого, если вы не хотите совместимости с pre-XP. Он автоматически обрабатывает процедуру подкласса, позволяет связать указатель пользовательских данных (например, this), который автоматически передается процедуре подкласса, и безопасно обрабатывает удаление подкласса в конце.

person Jonathan Potter    schedule 06.01.2013
comment
Красивый. Передача указателя на вызывающий объект в GWLP_USERDATA была просто билетом! - person Peter; 07.01.2013
comment
Обратите внимание, что слот GWLP_USERDATA принадлежит коду EDIT, а не вам! Оказывается, вам, возможно, повезло, и код EDIT может на самом деле не использовать это, но, вообще говоря, вы не хотите делать такое предположение. Обязательно используйте _USERDATA в тех случаях, когда вы владеете всем классом; но если вы создаете подкласс другого класса, лучше отслеживать HWND и связанные данные на отдельной карте или использовать SetProp(), как описано в эта статья о подклассах - person BrendanMcK; 07.01.2013
comment
@BrendanMcK: Нет, на этот счет нет жесткого и быстрого правила - например. см. blogs.msdn.com/b/oldnewthing /archive/2005/03/03/ (комментарий Рэймонда внизу, если якорная ссылка не работает). Ни один из стандартных элементов управления Windows не использует GWLP_USERDATA самостоятельно. SetProp — это еще один способ сделать это, но, безусловно, лучший вариант в наши дни — использовать SetWindowSubclass, как я упоминал выше. - person Jonathan Potter; 07.01.2013
comment
@JonathanPotter - вот почему я сказал ... но, вообще говоря, вы не хотите делать такое предположение. Зачем писать код, основанный на недокументированном поведении, когда можно так же легко написать код, работающий в общем случае? В статье Рэймонда также говорится: обратите внимание, что это значение [...] принадлежит классу окна, а класс окна здесь - EDIT; создание подклассов путем изменения wndproc этого не меняет. (И, очевидно, SysLink использует это, по крайней мере, на XP! — см. комментарий в конце эта страница) - person BrendanMcK; 07.01.2013
comment
@BrendanMcK: SysLink не является одним из стандартных элементов управления окном, это обычный элемент управления, и в любом случае ОП конкретно говорил об элементе управления «Правка», а не просил гипотетического решения, которое всегда будет работать. Ваша точка зрения верна, и, как я постоянно говорю, SetWindowSubclass является гораздо лучшим решением для любого метода. - person Jonathan Potter; 07.01.2013