платформа Win32 - Туториалы Iczelion'a на русском, адаптированные для FreeBasic. Урок 20 - Сабклассинг окна

Предыдущая тема Следующая тема Перейти вниз

платформа Win32 - Туториалы Iczelion'a на русском, адаптированные для FreeBasic. Урок 20 - Сабклассинг окна

Сообщение  electrik в Вт Июн 30, 2009 6:41 pm

Win32 API. Урок 20. Сабклассинг окна

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

Теория:

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

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

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

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

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

После сабклассинга

Windows ==> наша оконная процедура -----> процедура edit control'а

Теперь мы можем рассмотреть то, каким образом происходит сабклассинг окна. Заметьте, что сабклассинг не ограничивается контролами, он может использоваться с любым окном. Давайте подумаем о том, как Windows узнает, где находится процедура edit box'а. Ну?.. Поле lрfnWndproc в структуре WNDCLASSEX. Если мы сможем поменять значение этого поля на адрес собственной структуры, Windows пошлет сообщение нашей процедуре окна вместо этого. Мы можем сделать это, вызвав SetWindowLong.

function SetWindowLong _
(byval hWnd as HWND, _
byval nIndex as integer, _
byval dwNewLong as LONG) as LONG

hWnd = хэндл окна, чьи свойства мы хотим поменять.

nIndex = значение, которое нужно изменить.

      GWL_EXSTYLE Установка нового расширенного стиля окна.
GWL_STYLE Установка нового стиля окна.
GWL_WNDPROC Установка нового адреса для процедуры окна.
GWL_HINSTANCE Установка нового хэндла приложения.
GWL_ID Установка нового идентификатора окна.
GWL_USERDATA Установка 32-битного значения, ассоциирующегося с окном.
У каждого окна есть ассоциированное с ним 32-битное значение, предназначенное для использования приложением в своих целях.

dwNewLong = новое значение.

Таким образом, наша работа проста: мы создаем процедуру окна, которая будет обрабатывать сообщения для edit control'а и затем вызывать SetWindowLong с флагом GWL_WNDPROC, которому передается адрес нашего окна в качестве третьего параметра. В случае, если вызов функции прошел нормально, возвращаемым значением является прежнее значение замещаемого параметра, в нашем случае - это адрес оригинальной процедуры окна. Нам нужно сохранить это значение, чтобы использовать его внутри нашей процедуры.

Помните, что есть сообщения, которые нам не нужно будет обрабатывать. Их мы будем передавать оригинальной процедуре. Мы можем сделать это с помощью вызова функции CallWindowproc.

function CallWindowProc _
(byval lpprevWndFunc as WNDPROC, _
byval hWnd as HWND, _
byval Msg as UINT, _
byval wparam as WPARAM, _
byval lparam as LPARAM) as LRESULT

lprevWndFunc = адрес оригинальной процедуры окна. Остальные четыре значения - это те, что передаются нашей процедуре окна. Мы передаем их CallWindowProc.

Пример:

Код:

' Сабклассинг окна
#include "windows.bi"

declare function EditWndProc (byval hEdit as HWND, byval uMsg as UINT, _
byval wParam as WPARAM, byval lParam as LPARAM) as LRESULT

declare function WinMain ( byval hInst as HINSTANCE, _
byval hPrevInst as HINSTANCE, _
  byval szCmdLine as LPSTR, _
  byval iCmdShow as integer ) as integer


' начало программы

#define ClassName "SubclassWinClass" ' Имя нашего класса окна
#define AppName "Subclassing Demo" ' Имя нашего окна
#define EditClass "EDIT" ' класс edit box'а
#define Message "You pressed Enter in the text box!"
dim shared hModule as HINSTANCE ' Хэндл нашей программы
dim shared hwndEdit as HWND ' хэндл edit control'а
dim shared OldWndProc as LONG ' адрес оригинальной процедуры
hModule = GetModuleHandle( NULL ) ' Взять хэндл программы
end WinMain( hModule,NULL,GetCommandLine(), SW_SHOWNORMAL ) ' вызвать основную функцию
' здесь заканчивается программа

' процедура окна
function  WndProc _
(byval hwnd as HWND, _ ' хэндл окна
byval uMsg as UINT, _ ' сообщение
byval wParam as WPARAM, _ ' дополнительный  параметр сообщений
byval lParam as LPARAM) as LRESULT ' дополнительный параметр сообщений
function = 0
select case uMsg  'начинаем обработку сообщений
case WM_DESTROY ' если пользователь закрывает окно
PostQuitMessage(0) ' выходим из программы
exit function
case WM_CREATE ' если сообщение WM_CREATE
hwndEdit = CreateWindowEx _ ' создадим Edit Box
(WS_EX_CLIENTEDGE, _ ' дополнительные стили
EditClass, _ ' класс edit box'а
0, _
WS_CHILD+WS_VISIBLE+WS_BORDER, _ ' стили окна
20, _ ' x
20, _ ' y
300, _ ' ширина
25, _ ' высота
hWnd, _ ' хэндл родительского окна
NULL, _
hModule, _ ' хэндл программы
NULL)
SetFocus(hwndEdit ) ' установим фокус в Edit Box
' Subclass it!
OldWndproc = SetWindowLong _ ' сабклассим Edit Box
(hwndEdit, _ ' хэндл edit Box
GWL_WNDPROC, _ ' Устанавливаем новый адрес для процедуры окна
cPtr(long,@EditWndproc)) ' адрес процедуры
end select
function = DefWindowProc(hWnd,uMsg,wParam,lParam) ' Дефаултная функция обработки окна
end function

function EditWndProc _ ' процедура edit box'а
(byval hEdit as HWND, _ ' хэндл Edit control'а
byval uMsg as UINT, _ ' сообщение
byval wParam as WPARAM, _ ' дополнительный  параметр сообщений
byval lParam as LPARAM) as LRESULT ' дополнительный параметр сообщений

function = 0
dim ch as WPARAM = lobyte(wparam)
select case uMsg ' проверяем сообщения
case WM_CHAR ' если сообщение WM_CHAR
                      if (ch >= 48 and _ ' "0"
ch <= 57) or _ ' "9"
(ch >= 65 and _ ' "A"
ch <= 70) or _ ' "F"
(ch >= 97 and _ ' "a"
ch <= 102) or _ ' "f"
ch = VK_BACK then
if (ch >= 97 and _ ' "a"
ch <= 102) then ' "f"
ch =ch - &h20
end if
*cast(ubyte ptr,@wparam)=ch
function = CallWindowproc _ ' запускаем оригинальную процедуру окна
(cPtr(WNDPROC,OldWndproc), _ ' адрес оригинальной процедуры
hEdit, _ ' хэндл edit box
uMsg, _
wparam, _ ' символ
lparam)
exit function
end if

case WM_KEYDOWN ' сообщение о нажатии клавиши
    if lobyte(wparam) = VK_RETURN then ' если нажали enter
        MessageBox(hEdit,Message,AppName,MB_OK+MB_ICONINFORMATION)
        SetFocus(hEdit) ' установим фокус в edit box
    else
        function = CallWindowproc _ ' запускаем оригинальную процедуру окна
        (cPtr(WNDPROC,OldWndproc), _ ' адрес оригинальной процедуры
        hEdit, _ ' хэндл edit box
        uMsg, _
        wparam, _
        lparam)
exit function
end if
case else
    function = CallWindowproc _ ' запускаем оригинальную процедуру окна
        (cPtr(WNDPROC,OldWndproc), _ ' адрес оригинальной процедуры
       hEdit, _ ' хэндл edit box
        uMsg, _
        wparam, _
        lparam)
end select
end function

'функция WinMain
function WinMain _
(byval hInst as HINSTANCE, _ ' хэндл программы
byval hPrevInst as HINSTANCE, _ 'в win32 всегда 0
byval szCmdLine as LPSTR, _  'указатель на командную строку
byval iCmdShow as integer ) as integer  ' состояние окна при первом появлении

dim wc as WNDCLASSEX ' структура параметров окна
dim wMsg as MSG  ' структура сообщений
dim hWnd as HWND ' хэндл окна

 'структура класса окна wc
 ' заполняем структуру wc
wc.cbSize = SIZEOF( WNDCLASSEX )  ' размер структуры WNDCLASSEX
wc.style = CS_HREDRAW or CS_VREDRAW  ' Стиль окна
wc.lpfnWndProc = @WndProc ' Адрес процедуры окна WndProc
wc.cbClsExtra = NULL  ' резервирование  дополнительных байт за концом структуры
wc.cbWndExtra = NULL
wc.hInstance = hInst  ' хэндл модуля
wc.hbrBackground = cast(HGDIOBJ, COLOR_ApPWORKSPACE) ' Цвет фона
wc.lpszMenuName = NULL ' Хэндл меню
wc.lpszClassName = @ClassName ' класс окна
wc.hIcon = LoadIcon( NULL,IDI_APPLICATION ) ' Хэндл иконки
wc.hIconSm = wc.hIcon 'Хэндл маленькой иконки
wc.hCursor = LoadCursor( NULL,IDC_ARROW) ' Хэндл курсора


' регистрация нашего класса окна
if(RegisterClassEx(@wc) = FALSE) then
MessageBox(0,"Не могу зарегистрировать класс окна","Ошибка",0)
end 1
end if

' Создадим окно
hwnd = CreateWindowEx _
(WS_EX_CLIENTEDGE, _ ' дополнительные стили
@ClassName, _ ' указатель на строку с именем класса окна
@AppName, _ ' указатель на строку с именем окна
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE, _ ' стиль окна
CW_USEDEFAULT, _ ' X
CW_USEDEFAULT, _ ' Y
350, _ ' ширина окна
200, _ ' высота окна
NULL, _ ' хэндл родительского окна
NULL, _ ' хэндл меню
hInst, _ ' хэндл модуля
NULL) ' указатель на структуру данных
while( GetMessage( @wMsg, NULL, 0, 0 ) <> FALSE )  'цикл сообщений
TranslateMessage( @wMsg )
DispatchMessage( @wMsg )
wend
function = wMsg.wParam
end function

Анализ:

OldWndproc = SetWindowLong _
(hwndEdit, _
GWL_WNDPROC, _
cPtr(long,@EditWndproc)

После того, как edit control создан, мы сабклассим его, вызываяSetWindowLong и замещая адрес оригинальной процедуры окна нашим собственным адресом. Заметьте, что мы сохраняем значение адреса оригинальной процедуры, чтобы впоследствии использовать его при вызовеCallWindowproc.Заметьте, что EditWndрroc - это обычная оконная процедура.

case WM_CHAR
                     if (ch >= 48 and _
ch <= 57) or _
(ch >= 65 and _
ch <= 70) or _
(ch >= 97 and _
ch <= 102) or _
ch = VK_BACK then
if (ch >= 97 and _
ch <= 102) then
ch =ch - &h20
end if
*cast(ubyte ptr,@wparam)=ch
function = CallWindowproc _
(cPtr(WNDPROC,OldWndproc), _
hEdit, _
uMsg, _
wparam, _ ' символ
lparam)
exit function
end if

Внутри EditWndProc, мы фильтруем сообщения WM_CHAR. Если введен символв диапазоне 0-9 или a-f, мы передаем его оригинальной процедуре окна.Если это символ нижнего регистра, мы конвертируем его в верхний, отнимая 20h. Заметьте, что если символ не тот, который мы ожидали, мы пропускаем его. Мы не передаем его оригинальной процедуре окна. Поэтому, когда пользователь печатает что-нибудь отличное от 0-9 или a-f, символ непоявляется в edit control'е.

case WM_KEYDOWN
   if lobyte(wparam) = VK_RETURN then
       MessageBox(hEdit,Message,AppName,MB_OK+MB_ICONINFORMATION)
       SetFocus(hEdit)
   else
       function = CallWindowproc _
       (cPtr(WNDPROC,OldWndproc), _
       hEdit, _
       uMsg, _
       wparam, _
       lparam)
exit function
end if

Я хочу продемонстрировать силу сабклассинга через перехват клавишиEnter. EditWndProc проверяет сообщение WM_KEYDOWN, не равно ли оно VK_RETURN(клавиша Enter).
Если это так, она отображает окно с сообщением "Youpressed the Enter key in the text box!". Если это не клавиша Enter, она передает сообщение оригинальной процедуре.

Вы можете использовать сабклассинг окна, чтобы получить контроль наддругими окнами. Эту мощную технику вам следует иметь в своем арсенале.

[C] Iczelion, пер. Aquila.

electrik

Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 36
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург

Посмотреть профиль

Вернуться к началу Перейти вниз

Предыдущая тема Следующая тема Вернуться к началу


 
Права доступа к этому форуму:
Вы не можете отвечать на сообщения