Суперклассинг
В этом туториале мы изучим суперклассинг, что это такое и для чего он служит. Вы также узнаете, как pеализовать навигацию с помощью клавиши 'Tab' в вашем окне.
Скачайте пример здесь.
Теория:
Во время вашей программной карьеры, вы наверняка встретитесь с ситуацией, когда вам потребуется несколько контролов с *несколько* отличным поведением. Hапример, вам могут потребоваться 10 edit control'ов, которые принимают только число. Есть несколько путей достигнуть цели:
- Создать собственный класс и написать контролы с нуля
- Создать эти edit control'ы и сабклассировать каждый из них
- Суперклассировать edit control
Первый метод слишком сложен. Вам придется с нуля воплощать всю функциональность edit control'ов. Слишком трудоемкая задача, чтобы ее можно было быстро выполнить. Второй метод лучше, чем первый, но, тем не менее, также требует немало работы. Все нормально, пока вам надо сабклассировать несколько контролов, но сабклассинг дюжины или еще большего количества контролов может превратиться в аде. Суперклассинг - это техника, которой вы должны владеть.
Суперклассинг - это метод, с помощью которого вы сможете взять контроль над определенным классом окна. По взятием контроля я подразумеваю, что вы сможете изменить свойства класса, так чтобы они соответствовали вашим целям, после чего вы можете создать сколько угодно таких контролов.
Hиже приведены шаги для суперклассинга:
- вызвать функцию GetClassInfoEx, чтобы получить информацию о классе окна, который вы хотите суперклассировать. GetClassInfoEx требует указатель на структуру WNDCLASSEX, которая будет заполнена информацией, если вызов пройдет успешно.
- Изменяйте требуемые параметры WNDCLASSEX. Тем не менее, если два члена, которые вы должны обязательно изменить:
- hInstance - Вы должны поместить в это поле хэндл программы.
- lpszClassName - вы должны поместить сюда указатель на новое имя класса.
- Вы не обязаны изменять параметр lрfnWndрroc, но обычно вам будет это нужно делать. Главное не забудьте сохранить старое значение lpfnWndproc, если вам надо будет его вызывать с помощью 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 includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib
WM_SUpERCLASS equ WM_USER+5 WinMain pROTO :DWORD,:DWORD,:DWORD,:DWORD EditWndproc pROTO :DWORD,:DWORD,:DWORD,:DWORD
.data ClassName db "SuperclassWinClass",0
AppName db "Superclassing Demo",0 EditClass db "EDIT",0 OurClass db "SUpEREDITCLASS",0 Message db "You pressed the Enter key in the text box!",0
.data? hInstance dd ?
hwndEdit dd 6 dup(?) 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+WS_EX_CONTROLpARENT,ADDR ClassName,ADDR AppName,\ WS_OVERLAppED+WS_CApTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE, \ CW_USEDEFAULT,350,220,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 uses ebx edi hWnd:HWND, uMsg:UINT, wparam:WpARAM,
lparam:LpARAM LOCAL wc:WNDCLASSEX .if uMsg==WM_CREATE mov wc.cbSize,sizeof WNDCLASSEX
invoke GetClassInfoEx,NULL,addr EditClass,addr wc push wc.lpfnWndproc pop OldWndproc mov wc.lpfnWndproc, OFFSET EditWndproc
push hInstance pop wc.hInstance mov wc.lpszClassName,OFFSET OurClass invoke RegisterClassEx, addr wc
xor ebx,ebx mov edi,20 .while ebx<6 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\ WS_CHILD+WS_VISIBLE+WS_BORDER,20,\ edi,300,25,hWnd,ebx,\ hInstance,NULL mov dword ptr [hwndEdit+4*ebx],eax
add edi,25 inc ebx .endw invoke SetFocus,hwndEdit
.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
.elseif al==VK_TAB invoke GetKeyState,VK_SHIFT test eax,80000000 .if ZERO?
invoke GetWindow,hEdit,GW_HWNDNEXT .if eax==NULL invoke GetWindow,hEdit,GW_HWNDFIRST .endif
.else invoke GetWindow,hEdit,GW_HWNDpREV .if eax==NULL invoke GetWindow,hEdit,GW_HWNDLAST
.endif .endif invoke SetFocus,eax xor eax,eax
ret .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
Анализ:
Программа создаст простое окно с "измененными" edit control'ами в своей клиентской области. Edit control'ы будут принимать только шестнадцатиричные числа. Фактически, я адаптировал пример с сабклассингом. программа стартует как обычно, а самое интересное происходит, когда создается основное окно:
.if uMsg==WM_CREATE mov wc.cbSize,sizeof WNDCLASSEX invoke GetClassInfoEx,NULL,addr EditClass,addr wc
Сначала мы заполним данными класса, который мы хотим суперклассировать, в нашем случае это класс edit'а. Помните, что вы должны установить параметр структуры WNDCLASSEX, перед тем, как вызвать GetClassInfoEx, в противном случае она будет заполнена неверно. После вызова GetClassInfoEx у нас будет иметься вся необходимая для создания нового класса информация.
push wc.lpfnWndproc pop OldWndproc mov wc.lpfnWndproc, OFFSET EditWndproc push hInstance pop wc.hInstance mov wc.lpszClassName,OFFSET OurClass
Теперь мы можем изменить некоторые члены wc. Первый из них - это указатель на процедуру окна. Так как нам нужно будет соединить вызовы новой и старой процедуры в цепь, нам необходимо сохранить старое значение в переменную, чтобы потом воспользоваться функцие CallWindowproc. Эта техника идентична с сабклассингом, не считая того, что вы напрямую изменяете структуру WNDCLASSEX не вызывая SetWindowLong. Следующие два поля должны быть изменены, иначе вам не удастся зарегистрировать ваш новый класс окна, hInstance и lрszClassName. Вы должны заменить старое значение hInstance на хэндл вашей програмы, а также выбрать имя для нового класса.
invoke RegisterClassEx, addr wc
Когда все готово, регистрируйте новый класс. Вы получите новый класс, обладающий некоторыми характеристиками старого.
xor ebx,ebx mov edi,20 .while ebx<6 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\ WS_CHILD+WS_VISIBLE+WS_BORDER,20,\ edi,300,25,hWnd,ebx,\ hInstance,NULL mov dword ptr [hwndEdit+4*ebx],eax
add edi,25 inc ebx .endw invoke SetFocus,hwndEdit
Теперь, когда мы зарегистрировали класс, мы можем создать основанные на нем окна. Вы вышеприведенном куске кода, я использовал ebx в качестве счетчика созданных окон. edi используется как y-координата левого верхнего угла окна. Когда окно создано, его хэндл сохраняется в массиве dword'ов. Когда все окна созданы, устанавливаем фокус на первое окно. К этому моменту у вас есть 6 edit control'ов, которые принимают только шестнадцатиричные числа. Hовая процедура окна, заменившая старую, выполняет роль фильтра. Фактически, это работает точно также, как и в примере с сабклассингом, только вам не нужно выполнять лишнюю pаботу.
Я вставил кусок кода, который обрабатывает нажатия на Tab, чтобы сделать пример более полезным для вас. Обычно, если вы помещаете контролы на диалоговое окно, его внутренний менеджер сам обрабатывает нажатия на клавиши навигации. Увы, но это недоступно, когда вы помещаете контролы на обычное окно. Вам следует сабклассировать их, чтобы нажатия на Tab обрабатывались. В нашем примере нам нет нужны сабклассировать контролы по одному, так как мы уже суперклассировали, поэтому можем pеализовать "центральный менеджер навигации контролов".
.elseif al==VK_TAB invoke GetKeyState,VK_SHIFT test eax,80000000 .if ZERO?
invoke GetWindow,hEdit,GW_HWNDNEXT .if eax==NULL invoke GetWindow,hEdit,GW_HWNDFIRST .endif
.else invoke GetWindow,hEdit,GW_HWNDpREV .if eax==NULL invoke GetWindow,hEdit,GW_HWNDLAST
.endif .endif invoke SetFocus,eax xor eax,eax
ret
Вышеприведенный код взят из процедуры EditWndClass. Он проверяет, нажал ли пользователь клавишу tab, если да, он вызывает GetKeyStat, чтобы узнать, нажата ли также клавиша Shift. GetKeyState возвращает значение в eax, которое определяет, нажата ли указанная клавиша или нет. Если клавиша нажата, верхний бит eax будет установлен. Если нет, он будет очищен. Поэтому мы тестируем полученное значение 80000000h. Если верхний бит установлен, это будет означать, что пользователь нажал shift и tab одновременно, и должны обработать это отдельно.
Если пользователь нажал клавишу Tab, мы вызываем GetWindow, чтобы получить хэндл следующего контрола. Мы используем флаг GW_HWNDNEXT, чтобы указать GetWindow получить хэндл следующего окна относительно текущего hEdit. Если эта функция возвращает NULL, то такого окна нет и мы устанавливаем фокус на первое окно, вызвав GetWindow с флагом GW_HWNDFIRST. Shift-Tab работает так же, как и обычно нажатие на Tab, только передвигает фокус окна назад.
[C] Iczelion, пер. Aquila.