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

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

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

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

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

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

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

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]