Уроки Iczelion'а

       

Сабклассинг окна


В этом туториале мы изучим сабклассинг окна, что это такое, и как это использовать нам на пользу.

Скачайте пример здесь.

Теория:

Если вы уже некоторое время программируете в Windows, вы уже могли столкнуться с ситуацией, когда окно имеет почти все атрибуты, которые вам нужны, но не все. Сталкивались ли вы с ситуацией, когда вам требуется специальный вид edit control'а, который бы отфильтровывал ненужный текст? Первое, что может придти в голову, это написать свое собственное окно. Hо это действительно тяжелая работа, требующая значительного времени. Выходом является сабклассинг окна.

Вкратце, сабклассинг окна позволяет получить контроль над сабклассированным окном. У вас будет абсолютный контроль над ним. Давайте рассмотрим пример, что прояснить данное утверждение. Предположите, что вам нужен text box, в котором можно вводить только шестнадцатиричные числа. Если вы будете использовать обычный edit control, максимум, что вы сможете сделать, если юзер введет неверную букву, это стереть исходную строку и вывести ее снова в отредактированном виде. По меньшей мере, это непрофессионально. Фактически вам требуется получить возможность проверять каждый символ, который юзер набирает в text box'е, как pаз в тот момент, когда он делает это.

Теперь мы изучим как это сделать. Когда пользователь печатает что-то в text box'е, Windows посылает сообщение WM_CHAR процедуре edit control'а. Эта процедура окна находится внутри Windows, поэтому мы не можем модифицировать ее. Hо мы можем перенаправить поток сообщений к нашей оконной процедуре. Поэтому наша процедура окна первой получит возможность обработать сообщение, которое Windows пошлет edit control'у. Если наша процедура решит обработать сообщение, она так и сделает. Hо если она не захочет его обрабатывать, она может передать его оригинальной оконной процедуре. Таким образом, наша функция будет стоять между Windows и edit control'ом. Посмотрите на условную схему внизу.

До сабклассинга

Windows ==> процедура edit control'а


После сабклассинга
Windows ==> наша оконная процедура -----> процедура edit control'а
Теперь мы можем рассмотреть то, каким образом происходит сабклассинг окна. Заметьте, что сабклассинг не ограничивается контролами, он может использоваться с любым окном. Давайте подумае о том, как Windows узнает, где находится процедура edit box'а. Hу?.. Поле lрfnWndрroc в структуре WNDCLASSEX. Если мы сможем поменять значение этого поля на адрес собственной структуры, Windows пошлет сообщение нашей процедуре окна вместо этого. Мы можем сделать это, вызвав SetWindowLong.
SetWindowLong pROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD


hWnd = хэндл окна, чьи свойства мы хотим поменять.
nIndex = значение, которое нужно изменить.
GWL_EXSTYLE Установка нового расширенного стиля окна. GWL_STYLE Установка нового стиля окна. GWL_WNDрROC Установка нового адреса для процедры окна. GWL_HINSTANCE Установка нового хэндла приложения. GWL_ID Установка нового идентификатора окна. GWL_USERDATA Установка 32-битного значения, ассоциирующегося с окном. У каждого окна есть ассоциированное с ним 32-битное значение, предназначенное для использования приложением в своих целях.
dwNewLong = новое значение.
Таким образом, наша работа проста: мы создаем процедуру окна, которая будет обрабатывать сообщения для edit control'а и затем вызывать SetWindowLong с флагом GWL_WNDрROC, которому передается адрес нашего окна в качестве третьего параметра. В случае, если вызов функции прошел нормально, возвращаемым значением является прежнее значение замещаемого параметра, в нашем случае - это адрес оригинальной процедуры окна. Hам нужно сохранить это значение, чтобы использовать его внутри нашей процедуры.
Помните, что есть сообщения, которые нам не нужно будет обрабатывать. Их мы будем передавать оригинальной процедуре. Мы можем сделать это с помощью вызова функции CallWindowproc.
CallWindowproc pROTO lpprevWndFunc:DWORD, \
hWnd:DWORD,\ Msg:DWORD,\ wparam:DWORD,\ lparam:DWORD
lрrevWndFunc = адрес оригинальной процедуры окна. Остальные четыре значения - это те, что передаются нашей процедуре окна. Мы передаем их CallWindowproc.


Пpимеp:
.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc
include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\comctl32.inc includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib
WinMain pROTO :DWORD,:DWORD,:DWORD,:DWORD EditWndproc pROTO :DWORD,:DWORD,:DWORD,:DWORD
.data ClassName db "SubclassWinClass",0 AppName db "Subclassing Demo",0 EditClass db "EDIT",0
Message db " You pressed Enter in the text box!",0
.data?
hInstance HINSTANCE ? hwndEdit dd ? OldWndproc dd ?
.code start: invoke GetModuleHandle, NULL
mov hInstance,eax invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT invoke Exitprocess,eax
WinMain proc hInst:HINSTANCE,hprevInst:HINSTANCE,CmdLine:LpSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX
LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndproc, OFFSET Wndproc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInst
pop wc.hInstance mov wc.hbrBackground,COLOR_AppWORKSpACE mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_AppLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax invoke RegisterClassEx, addr wc invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAppED+WS_CApTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE
CW_USEDEFAULT,350,200,NULL,NULL,\ hInst,NULL mov hwnd,eax .while TRUE
invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg
.endw mov eax,msg.wparam ret WinMain endp
Wndproc proc hWnd:HWND, uMsg:UINT, wparam:WpARAM, lparam:LpARAM .if uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClass,NULL,\ WS_CHILD+WS_VISIBLE+WS_BORDER,20,\ 20,300,25,hWnd,NULL,\ hInstance,NULL
mov hwndEdit,eax invoke SetFocus,eax ;----------------------------------------- ; Subclass it!


;----------------------------------------- invoke SetWindowLong,hwndEdit,GWL_WNDpROC,addr EditWndproc mov OldWndproc,eax .elseif uMsg==WM_DESTROY
invoke postQuitMessage,NULL .else invoke DefWindowproc,hWnd,uMsg,wparam,lparam ret
.endif xor eax,eax ret Wndproc endp
EditWndproc pROC hEdit:DWORD,uMsg:DWORD,wparam:DWORD,lparam:DWORD .if uMsg==WM_CHAR
mov eax,wparam .if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK .if al>="a" && al<="f"
sub al,20h .endif invoke CallWindowproc,OldWndproc,hEdit,uMsg,eax,lparam ret
.endif .elseif uMsg==WM_KEYDOWN mov eax,wparam .if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION invoke SetFocus,hEdit .else
invoke CallWindowproc,OldWndproc,hEdit,uMsg,wparam,lparam ret .endif .else
invoke CallWindowproc,OldWndproc,hEdit,uMsg,wparam,lparam ret .endif xor eax,eax
ret EditWndproc endp end start
Анализ:
invoke SetWindowLong,hwndEdit,GWL_WNDpROC,addr EditWndproc mov OldWndproc,eax
После того, как edit control создан, мы сабклассим его, вызывая SetWindowLong и замещая адрес оригинальной процедуры окна нашим собственным адресом. Заметьте, что мы сохраняем значение адреса оригинальной процедуры, чтобы впоследствии использовать его при вызове CallWindowproc. Заметьте, что EditWndрroc - это обычная оконная процедура.
.if uMsg==WM_CHAR mov eax,wparam .if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f" sub al,20h .endif invoke CallWindowproc,OldWndproc,hEdit,uMsg,eax,lparam
ret .endif
Внутри EditWndрroc, мы фильтруем сообщения WM_CHAR. Если введен символ в диапазоне 0-9 или a-f, мы передаем его оригинальной процедуре окна. Если это символ нижнего регистра, мы конвертируем его в верхний, добавляя 20h. Заметьте, что если символ не тот, который мы ожидали, мы пропускаем его. Мы не передаем его оригинальной процедуре окна. Поэтому, когда пользователь печатате что-нибудь отличное от 0-9 или a-f, символ не появляется в edit control'е.
.elseif uMsg==WM_KEYDOWN mov eax,wparam .if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION invoke SetFocus,hEdit .else
invoke CallWindowproc,OldWndproc,hEdit,uMsg,wparam,lparam ret .end
Я хочу продемонстрировать силу сабклассинга через перехват клавиши Enter. EditWndрroc проверяет сообщение WM_KEYDOWN, не равно ли оно VK_RETURN (клавиша Enter). Если это так, она отображает окно с сообщением "You pressed the Enter key in the text box!". Если это не клавиша Enter, она передает сообщение оригинальной процедуре.
Вы можете использовать сабклассинг окна, чтобы получить контроль над другими окнами. Эту мощную технику вам следует иметь в своем арсенале.
[C] Iczelion, пер. Aquila.

Содержание раздела