Главная » Статьи » Assembler

Диалоговые окна

Здравствуйте, уважаемые читатели! Долгое время мы с вами ходили вокруг да около, но сегодня мы наконец-то слепим полноценное GUI-приложение!! Но прежде чем начать его лепить, придётся немного поучить теорию…
Я думаю, читатели много раз слышали термин “интерфейс” (interface, что дословно переводится как “междумордие”). Его применяют в описании как hardware, так и software в значении “способ взаимодействия”. С кем/чем же взаимодействует компьютерная программа?

Во-первых, программа взаимодействует с ОС, под которой она запущена, с помощью API (Application Programming Interface). Во-вторых, прога взаимодействует с запустившим её юзером
Пользовательские интерфейсы делят на две большие группы: консольные и графические. С консольными всё понятно: средствами взаимодействия там служат параметры командной строки. А в GUI-приложениях эту роль выполняют элементы управления окна

Посмотрите на типичное окно типичного Windows-приложения:
Все эти edit’ы, button’ы, list’ы, другие многочисленные элементы управления плюс-минус меню - всё это нужно для создания нормального удобного интерфейса. И при программировании окон на Ассемблере можно подойти к этому вопросу с двух сторон
Можно создавать каждую кнопку, каждый edit, каждый static, да и вообще любую пимпу как отдельное окно - дочернее по отношению к главному окну приложения. А можно объявить главное окно приложения диалоговым
Само название этого типа окон говорит о том, что они направлены на взаимодействие с юзером. И такое окно само нарисует на себе все необходимые элементы управления - от нас только требуется указать, какие именно и в каком количестве. Такие элементы управления будут контролами диалогового окна - их описание мы оставим в секции ресурсов. А элементы управления в заголовке окна (кнопки “Свернуть”, “Развернуть”, “Закрыть”) создадутся без нашего участия: они определяются стилем окна

Создаётся диалоговое окно функцией DialogBoxParamA, имеющей следующий
прототип:

int DialogBoxParam(

HINSTANCE hInstance, // хэндл приложения

LPCTSTR lpTemplateName, // идентификатор шаблона

HWND hWndParent, // хэндл окна-владельца

DLGPROC lpDialogFunc, // указатель на оконную процедуру

LPARAM dwInitParam // инициализационная переменная

);

Коротко поясню основные моменты. Идентификатор шаблона - это номер записи в секции ресурсов, характеризующей диалоговое окно. Хэндл окна владельца смело задавайте равным 0 - так как наше приложение не имеет других окон, кроме этого диалога, то владельцем ему будет Рабочий стол Windows (HWND_DESKTOP=0). Последним параметром также передавайте 0, не парьтесь

Эта функция создаёт диалоговое окно согласно информации из секции ресурсов, запускает оконную процедуру нашего диалога и посылает ему сообщение WM_INITDIALOG, которое нужно обязательно обработать, вернув в eax 1. Кроме обработчика WM_INITDIALOG нам понадобится обработчик WM_CLOSE, иначе окно не сможет закрыться
Контролы диалогового окна имеют привычку посылать сообщение WM_COMMAND при изменении их состояния, а кнопки - ещё и сообщение BN_CLICKED (причём хитровывернутым способом - BN_CLICKED через WM_COMMAND). Их тоже нужно обрабатывать, но при этом ещё и различать, какой же контрол послал сообщение. Как же это сделать?

Каждый контрол диалогового окна кроме хэндла должен иметь уникальный ID. Каждое сообщение состоит из двух dword’ов: wParam и lParam. WM_COMMAND в lParam содержит хэндл контрола, в нижнем слове wParam - код уведомления, в ВЕРХНЕМ слове wParam - ID контрола. BN_CLICKED содержит в lParam хэндл кнопки, а её ID - в НИЖНЕМ слове wParam
ID’ы таких часто встречающихся кнопок, как “OK!” (1), “Cancel” (2), “Abort” (3), “Retry” (4), “Ignore” (5), “Yes” (6), “No” (7), заранее определены в инклудах и могут юзаться в программе как константы

Чтобы понять, какая кнопка была нажата, делают такой финт ушами: берут сообщение BN_CLICKED, переносят ID кнопки из нижнего слова wParam в верхнее, затем выполняют “логическое И” с проверяемым ID. Например, чтобы проверить, не “OK!” ли нажал юзер, сначала выполняют BN_CLICKED shl 16 (сдвигают биты в структуре сообщения на 16 позиций влево, т.о. нижнее слово wParam встанет на место верхнего), затем and IDOK (если в BN_CLICKED тоже был IDOK, ничего не изменится), а потом командой cmp сравнивают с содержимым верхнего слова wParam сообщения WM_COMMAND. Это на словах сложно, а в коде задаётся одной строкой в обработчике сообщения WM_COMMAND: cmp [wparam],BN_CLICKED shl 16 + IDOK. Если вы поймёте логику этой команды, то вам станет ясно: если была нажата кнопка “OK!”, то результатом сравнения станет равенство. А можете особо и не вникать, просто запомните этот приём - он вам очень пригодится
Диалоговое окно также, как и обычное, крутит цикл обработки сообщений. Чтобы прервать его, нужно вызывать функцию EndDialog, передав ей хэндл диалогового окна и число. Какое число - вы определяете сами, оно будет в eax после выхода из оконной процедуры

Но, само собой, диалоговое окно должно не просто щеголять контролами, а что-то делать. Это реализуется так: обработчики сообщений вызывают EndDialog, передавая ей различные числовые аргументы. Ваша задача - получить это число из eax, понять, обработчик какого сообщения спровоцировал выход, и выполнить необходимые действия. Непонятно? Не беда: сейчас быстренько набросаем простейшее приложение, и всё сразу станет ясно

Я не буду сильно мудрить: моё диалоговое окно будет содержать два edit’а и две кнопки. В edit’ы юзер будет вводить всякую отсебятину, и при нажатии на одну из кнопок выскочит MessageBox с этой отсебятиной в заголовке и тексте сообщения
Чтобы считать юзерскую отсебятину из edit’ов, воспользуемся функцией GetDlgItemTextA:

UINT GetDlgItemText(

HWND hDlg, // хэндл диалогового окна

int nIDDlgItem, // ID контрола

LPTSTR lpString, // указатель на буфер

int nMaxCount // количество считываемых символов

);

Let’s code!!

format PE GUI 4.0
include 'win32axp.inc'
include 'encoding\WIN1251.INC'
;подключаем инклуду с кириллической кодировкой,
;чтобы использовать в приложении русские буквы
ID_ZAGOL = 101
ID_SLOVO = 102
;определяем ID'ы edit'ов
;кнопки я обзову "Жми!!" и "Отмена",
;они обойдутся стандартными ID'ами:
;IDOK и IDCANCEL

.data
slovo db 41 dup 0
zagolovok db 41 dup 0
;буферы по 41 байт для сообщения и заголовка
;заранее забьём нулями

.code
fuck:
invoke GetModuleHandle,0
;в eax - хэндл нашего приложения
invoke DialogBoxParam,eax,69,HWND_DESKTOP,ProceduraDialoga,0
;создаём диалоговое окно согласно шаблону №69,
;который мы сами же и создали в секции ресурсов.
;ответственной за диалог назначаем процедуру
;ProceduraDialoga, которую сами и напишем
inc eax
je exit
;вышеописанные две инструкции проверяют,
;не равен ли eax -1
;если равен - либо провалилось выполнение
;DialogBoxParamA, либо была вызвана
;EndDialog из обработчика сообщения
;WM_CLOSE: в обоих случаях пора на выход.
;если не -1, то EndDialog вызвана из
;обработчика сообщения WM_COMMAND,
;а значит, выполняется нижележащий код.
;Число -1 взято мной от балды (хотя очень удобно),
;вы вправе использовать другие аргументы для
;вызова EndDialog
invoke MessageBox,0,slovo,zagolovok,MB_OK
exit:
invoke  ExitProcess,0
;думаю, в вызове MessageBox и ExitProcess
;вы и сами разберётесь
proc ProceduraDialoga,hwnddlg,msg,wparam,lparam
push ebx
push esi
push edi
;начало процедуры ProceduraDialoga.
;объявили необходимые параметры,
;по древней традиции сохранили
;три регистра.
;далее идут jump'ы на обработчики сообщений.
cmp [msg],WM_INITDIALOG
je Initialization
cmp [msg],WM_COMMAND
je Command
cmp [msg],WM_CLOSE
je Close
xor eax,eax
jmp Finish

Command:
cmp [wparam],BN_CLICKED shl 16 + IDCANCEL
;про этот финт я вам уже говорил:
;если нажата кнопка "Отмена",
;то пора на выход
je Close
cmp [wparam],BN_CLICKED shl 16 + IDOK
;если что-то нажато, но не кнопка "Жми!!"
;(мало ли что может глюкануть:) ),
;то обработаем это как сообщение
;WM_INITDIALOG
jne Initialization
;если нажата "Жми!!",
;читаем текст из edit'ов
invoke GetDlgItemText,[hwnddlg],ID_ZAGOL,zagolovok,40
invoke GetDlgItemText,[hwnddlg],ID_SLOVO,slovo,40
;копировать будем по 40 символов,
;более длинные строки будут обрезаны.
;в буферах останется по одному null-байту,
;который в случае длинных строк
;сыграет роль завершающего нуля.
;вызываем EndDialog, передав ей число 1
invoke EndDialog,[hwnddlg],1
jmp Finish

Initialization:
xor eax,eax
inc eax
;обработать WM_INITDIALOG просто:
;нужно всего лишь обеспечить
;в eax 1 на выходе из
;оконной процедуры
Finish:
pop edi
pop esi
pop ebx
ret

Close:
invoke EndDialog,[hwnddlg],-1
;если нас закрывают или жмут "Отмена",
;вызываем EndDialog, передав ей -1
jmp Finish

endp
;конец оконной процедуры

section '.rsrc' resource data readable
;секция ресурсов
directory RT_DIALOG,dialogs
;директория с диалогами.
;у нас диалог всего один. Описываем его:
;присваиваем номер 69 (чисто от балды),
;делаем его "двуязычным" (English и
;язык в системе по умолчанию),
;даём имя rsrc_dialog (от балды)
resource dialogs,69,LANG_ENGLISH+SUBLANG_DEFAULT,rsrc_dialog
;описываем его подробнее: указываем
;заголовок окна, размеры и стили.
;цифры - это координаты верхнего левого угла,
;ширина и высота. Не забывайте, что за 0;0
;принимается верхний левый угол экрана
dialog rsrc_dialog,'Диалог',70,70,190,90,WS_CAPTION+WS_POPUP+WS_SYSMENU+DS_MODALFRAME
;теперь описываем отдельные контролы.
;Для static сперва указывается отображаемый текст, затем
;цифры: первая у всех static'ов, что я видел
;равна -1, далее идут координаты и размеры
dialogitem 'STATIC','Введи слово/фразу для заголовка:',-1,10,10,190,8,WS_VISIBLE
dialogitem 'EDIT','',ID_ZAGOL,10,20,170,13,WS_VISIBLE+WS_BORDER+WS_TABSTOP
;edit'у обязательно назначаем ID
dialogitem 'STATIC','Введи слово/фразу для сообщения:',-1,10,40,190,8,WS_VISIBLE
dialogitem 'EDIT','',ID_SLOVO,10,50,170,13,WS_VISIBLE+WS_BORDER+WS_TABSTOP
;кнопки лично я обозвал "Жми!!" и "Отмена",
;мог бы и ID'ы им персональные назначить.
;вот только лень было это делать:)
;потому обошёлся стандартными IDOK и IDCANCEL
dialogitem 'BUTTON','Жми!!',IDOK,85,70,45,15,WS_VISIBLE+WS_TABSTOP+BS_DEFPUSHBUTTON
dialogitem 'BUTTON','Отмена',IDCANCEL,135,70,45,15,WS_VISIBLE+WS_TABSTOP+BS_PUSHBUTTON
enddialog
;закончили секцию ресурсов,
;заканчиваем программу
.end fuck

Компилируем, запускаем… Работает! И русские буквы везде отображает правильно! Не правда ли, Ассемблер теперь стал как-то ближе?

В последующих статьях мы продолжим писать GUI-приложения, изучать элементы управления окон, оснащать .exe-шники разнообразными ресурсами, научимся создавать меню… Будет интересно, оставайтесь с нами!!!



Источник: http://rfteam.110mb.com/
Категория: Assembler | Добавил: -=Hellsing=- (22.06.2009) | Автор: Adrax
Просмотров: 4652 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]