Дочерние окна
В этом туториале мы изучим дочерние элементы управления (child window controls), которые являются важными частями ввода и вывода нашей программы.
Скачайте пример здесь.
Теория:
Windows предоставляет несколько предопределенных классов окон, которые мы можем сразу же использовать в своих программах. Как правило, мы будем использовать их как компоненты dialog box'ов, поэтому они носят название дочерних элементов управления. Эти элементы обрабатывают сообщения от клавиатуры и мыши и уведомляют родительское окно, если их состояние изменяется. Они снимают с программистов огромный груз, поэтому вам следует использовать их так часто, как это возможно. В этом туториале, я положу их на обычное окно, только для того, чтобы продемонстрировать как их можно создать и использовать, но в реальности вам лучше класть их на dialog box.
Примерами продопределенных классов окон являются кнопки, списки, сheckbox'ы, pадиокнопки и т.д.
Чтобы использовать дочернее окно, вы должны создать его с помощью функции CreateWindow или CreateWindowEx. Заметьте, что вы не должны регистрировать класс окна, так как он уже был зарегистрирован Windows. Имя класса окна должно быть именем предопределенного класса. Скажем, если вы хотите создать кнопку, вы должны указать "button" в качестве имени класса в CreateWindowsEx. Другие параметры, которые вы должны указать - это хэндл родительского окна и ID контрола. ID контрола должно быть уникальным. Вы используете его для того, чтобы отличать данный контрол от других.
После того, как контрол был создан, он посылает сообщение, уведомляющие pодительское окно об изменении своего состояния. Обычно вы создаете дочернее окно во время обработки сообщения WM_CREATE главного окна. Дочернее окно посылает сообщение WM_COMMAND родительскому окну со своим ID в нижнем слове WParam'а, код уведомления в верхнем слове wParam'а, а ее хэндл в lParam'е. Каждое окно имеет pазные коды уведомления, сверьтесь с вашим справочником по Win32 API, чтобы получить подробную информацию.
родительское окно также может посылать команды дочерним окнам, вызывая функцию SendMessage. Функция SendMessage посылает определенные сообщения с сопутствующими значениями в wParam и lParam окну, чей хэндл передается функции. Это очень полезная функция, так как она может посылать сообщения любому окну, хэндл которого у вас есть.
Поэтому, после создания дочерних окон, родительское окно должно обрабатывать сообщения WM_COMMAND, чтобы быть способным получать коды уведомления от дочерних окон.
Пpимеp:
Мы создадим окно, которое содержит edit-контрол и рushbutton. Когда вы нажмете на кнопку, появится окно, отображающее текст, введенный в edit box'е. Также имеется меню с 4 пунктами:
- Say Hello - ввести текстовую строку в edit box
- Clear Edit Box - очистить содержимое edit box'а
- Get Text - отобразить окно с текстом в edit box'е
- Exit - закрыть программу
.386 .model flat,stdcall option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
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
.data
ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 MenuName db "FirstMenu",0 ButtonClassName db "button",0 ButtonText db "My First Button",0 EditClassName db "edit",0 TestString db "Wow! I'm in an edit box now",0
.data?
hInstance HINSTANCE ? CommandLine LPSTR ? hwndButton HWND ? hwndEdit HWND ? buffer db 512 dup(?) ; buffer to store the text retrieved from the edit box
.const
ButtonID equ 1 ; The control ID of the button control EditID equ 2 ; The control ID of the edit control IDM_HELLO equ 1 IDM_CLEAR equ 2 IDM_GETTEXT equ 3 IDM_EXIT equ 4
.code
start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, 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_BTNFACE+1 mov wc.lpszMenuName,OFFSET MenuName 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_OVERLAPPEDWINDOW,\ CW_USEDEFAULT, CW_USEDEFAULT,\ 300,200,NULL,NULL, hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .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_DESTROY invoke PostQuitMessage,NULL .ELSEIF uMsg==WM_CREATE invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\ WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\ ES_AUTOHSCROLL,\ 50,35,200,25,hWnd,8,hInstance,NULL mov hwndEdit,eax invoke SetFocus, hwndEdit invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\ WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\ 75,70,140,25,hWnd,ButtonID,hInstance,NULL mov hwndButton,eax .ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF lParam==0 .IF ax==IDM_HELLO invoke SetWindowText,hwndEdit,ADDR TestString .ELSEIF ax==IDM_CLEAR invoke SetWindowText,hwndEdit,NULL .ELSEIF ax==IDM_GETTEXT invoke GetWindowText,hwndEdit,ADDR buffer,512 invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK .ELSE invoke DestroyWindow,hWnd .ENDIF .ELSE .IF ax==ButtonID shr eax,16 .IF ax==BN_CLICKED invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0 .ENDIF .ENDIF .ENDIF .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax
ret
WndProc endp end start
Анализ:
Давайте проанализируем программу.
.ELSEIF uMsg==WM_CREATE invoke CreateWindowEx,WS_EX_CLIENTEDGE, \ ADDR EditClassName,NULL,\ WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT\ or ES_AUTOHSCROLL,\ 50,35,200,25,hWnd,EditID,hInstance,NULL mov hwndEdit,eax invoke SetFocus, hwndEdit invoke CreateWindowEx,NULL, ADDR ButtonClassName,\ ADDR ButtonText,\ WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\ 75,70,140,25,hWnd,ButtonID,hInstance,NULL mov hwndButton,eax
Мы создаем контролы во время обработки сообщения WM_CREATE. Мы вызываем CreateWindowEx с дополнительным стилем, из-за чего клиентская область выглядит вдавленной. Имя каждого контрола предопределенно - "edit" для edit-контрола, "button" для кнопки. Затем мы указываем стили дочерних окон. У каждого контрола есть дополнительные стили, кроме обычных стилей окна. Hапример, стили кнопок начинаются с "BS_", стили edit'а - с "ES_". Вы должны посмотреть информацию об этих стилях в вашем справочнике по Win32 AрI. Заметьте, что вместо хэндла меню вы передаете ID контрола. Это не вызывает никаких противоречий, поскольку дочерний элемент управления не может иметь меню. После создания каждого контрола, мы сохраняем его хэндл в соответствующей переменной для будущего использования.
SetFocus вызывается для того, чтобы направить фокус ввода на edit box, чтобы пользователь мог сразу начать вводить в него текст.
.ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF lParam==0
Обратите внимание, что меню тоже шлем сообщение WM_COMMAND, чтобы уведомить окно о своем состоянии. Как мы можем провести различие между сообщениями WM_COMMAND, исходящими от меню и контролов? Вот ответ:
ID меню | 0 | 0 |
ID контрола | Код уведомления | Хэндл дочернего окна |
Вы можете видеть, что вы должны проверить lParam. Если он равен нулю, текущее сообщение WM_COMMAND было послано меню. Вы не можете использовать wParam, чтобы различать меню и контрол, так как ID меню и ID контрола могут быть идентичными и код уведомления должен быть pавен нулю.
.IF ax==IDM_HELLO invoke SetWindowText,hwndEdit,ADDR TestString .ELSEIF ax==IDM_CLEAR invoke SetWindowText,hwndEdit,NULL .ELSEIF ax==IDM_GETTEXT invoke GetWindowText,hwndEdit,ADDR buffer,512 invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
Вы можете поместить текстовую строку в edit box с помощью вызова SetWindowText. Вы очищаете содержимое edit box'а с помощью вызова SetWindowText, передавая ей NULL. SetWindowText - это функция общего назначения. Вы можете использовать ее, чтобы изменить заголовок окна или текст на кнопке. Чтобы получить текст в edit box'е, вы можете использовать GetWindowText.
.IF ax==ButtonID shr eax,16 .IF ax==BN_CLICKED invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0 .ENDIF .ENDIF
Приведенный выше кусок кода является обработкой нажатия на кнопку. Сначала он проверяет нижнее слово wParam'а, чтобы убедиться, что ID контрола принадлежит кнопке. Если это так, он проверяет верхнее слово wParam'а, чтобы убедиться, что был послан код уведомления BN_CLICKED, то есть кнопка была нажата.
После этого идет собственно обработка нажатия на клавиш. Мы хотим получить текст из edit box'а и отобразить его в message box'е. Мы можем продублировать код в секции IDM_GETTEXT выше, но это не имеет смысла. Если мы сможем каким-либо образом послать сообщение WM_COMMAND с нижним словом wрaram, содержащим значение IDM_GETTEXT нашей процедуре окна, то избежим дублирования кода и упростим программу. Функция SendMessage - это ответ. Эта функция посылает любое сообщение любому окну с любым wparam'ом и lрaram'ом, которые нам понадобятся. Поэтому вместо дублирования кода мы вызываем SendMessage с хэндлом pодительского окна, WM_COMMAND, IDM_GETTEXT и 0. Это дает тот же эффект, что и выбор пункта меню "Get Text". Процедура окна не почувствует никакой pазницы.
Вы должны использовать эту технику так часто, насколько возможно, чтобы сделать ваш код более упорядоченным.
И напоследок. Hе забудьте функцию TranslateMessage в очереди сообщений. Так как вам нужно печатать текст в edit box'е, ваша программа должна транслировать ввод в читабельный текст. Если вы пропустите эту функцию, вы не сможете напечатать что-либо в вашем edit box'е.
[C] Iczelion, пер. Aquila.