November 6, 2005

Subclassing of subclassed control

...Или собственная обработка сообщений в MFC без наследования.

Для каждого edit-box-а в Win9x/ME/XP есть контекстное меню, причем меню это прорисовывается и обробатывается ОС. А вот для Pocket PC такая обработка будет только в случае, если в диалоге есть WC_SIPPREF контрол. Но этот контрол еще и раздвигает-сдвигает SIP во время фокусировки на Editы и Combo-Boxы. А в случае, когда автораздвижение-сдвижение SIP нам по религиозным причинам нежелательно, или же мы хотим сделать собственное контекстное меню для Edit-а, такой контрол не очень-то подойдет.

Чтобы сделать свое меню, нам нужно просабклассить EDIT, обработав там WM_LBUTTONDOWN (для грамотной обработки ::SHRecognizeGuesture()) и WM_CONTEXTMENU. Грамотный для MFC способ - наследовать CEdit и со счстливыми глазами реализовать там все, что душе угодно. А если контрол уже просабклассен другим наследником CEdit - наследовать от наследника? Можно, но если у нас есть как просто CEdit-ы, так и какие-то CCustomEdit-ы, да еще и доступа к ним нету (прилинкованы если из библиотеки какой-то левой) то наследовать придется оочень много.

Зато можно неплохо создать CEditMenu класс, который будет сабклассить нужный нам EDIT, добавляя туда то контекстное меню, которое нам нужно. Но просто наследовать его от CWnd и Attach()-ить к окну, которое уже приаттачено к какому-то MFC-классу не получится (карта хендлов-то у MFC одна).

Реализуем в таком случае свою карту хендлов (да поможет нам в этом stl::map), и свою базовую WndProc. О том, как это делается - писать не буду, смотрите сорцы MFC и там класс CWnd. Далее просто перекрываем WM_CONTEXTMENU в нашей новой WndProc, на все необработанные сообщения вызываем полученную при сабклассинге m_pOldWndProc, а вот с сообщением WM_LBUTTONDOWN встает следующая проблемма. Дело в том, что CWnd имеет свою обработку этого сообщения, и нам она не нужна (см. туеву хучу статей типа HOWTO: Disable red dots with MFC в инете). Нам нужна своя его обработка, и в концее нее нужно вызвать стандартную процедуру окна для EDIT, а не ту, что выдает нам MFC.

Пара слов от том, как винда работает со стандартными системными контролами (типа EDIT). Когда мы создаем окно по CreateWindow, и указываем там системный класс (EDIT) например, то система создает окно с этим классом, и указывает системную WndProc этому окну, которая зависит от конкретного класса. А в этой WndProc по сообщеню WM_CREATE создается структура (специфичная для каждого типа контрола), которая инициализируется в зависимости от LPCREATESTRUCT.style и хранит текущее состояние контрола (например, положение выделения для EDIT). Указатель на эту структуру выстанавливается через ::SetWindowLong(hwnd, 0, &windowDataStruct) (мне это все приснилось в страшном сне).

А получить стандартную процедуру для виндового класса мы можем... правильно, через структуру WNDCLASS, которую в свою очередь получим по ::GetClassInfo(AfxGetInstanceHandle(), _T("EDIT"), &wcWndClass). Именно эту процедуру и будем вызывать в качестве дефолтной при WM_LBUTTONDOWN.

Теперь дело осталось за малым, и это малое вполне тривиально...