Первое окно
🕛 30.10.2006, 15:54
Первое окно Теперь, когда вы уже не сомневаетесь в своих силах мы приступим к более сложной задаче - создании окна.
Создание окна, обработка событий и реакция на события - вот три основных вопроса, которые мы рассмотрим в этой статье. В дальнейшем мы всегда будем ссылаться на эту статью, ибо она описывает ту основу, на которой держится все система GUI Windows. Постарайтесь понять эту статью и если это у вас получиться, то я думаю, что дальше ничего сложного для вас не будет.
Всем известно, что каждое приложение для Windows начинается с WinMain. Через параметры, переданные этой функции можно было получить командную строку, идентификатор предыдущего экземпляра программы и так далее и тому подобнее. Немного сложнее процедура инициализации приложения происходит в Assembler.
Первым шагом в инициализации приложения под Windows будем считать вызов функции INITTASK. После выполнения эта функция возвращает:
При успешном завершении AX=1
DX содержит nCmdShow, то есть параметр, указывающий на стиль просмотра окна
ES:BX содержит адрес командной строки
SI содержит идентификатор ранее загруженной программы hPrevInstance
DI содержит идентификатор загруженной программы hInstance
После обработки входных параметров нужно инициализировать приложение, используя функцию INITAPP. Единственным параметром этой функции служит идентификатор приложения hInstance.Функция INITAPP возвращает в AX=1, если приложение успешно проинициализировано.
Теперь, когда все сложности по инициализации закончились, мы наконец-то сможем создать наше первое окно на Assembler.
Процесс создания окна можно разделить на несколько этапов :
Заполнение структуры WNDCLASS.
Регистрация класса окна с помощью функции RegisterClass.
Создание окна с помощью функции CreateWindow.
Вывод окна на экран, используя функцию ShowWindow.
Обновление окна с помощью функции UpdateWindow.
Рассмотрим эти этапы подробнее.
Структура WNDCLASS является одним из кирпичей фундамента, на котором построена Windows. В этой структуре содержится вся информация о создаваемом окне. Описание этой структуры находится в файле WINDOWS.INC.
Структура WNDCLASS
clsStyleclsLpfnWndProc : DWORD
clsCbClsExtra : WORD
clsCbWndExtra : WORD
clsHInstance : WORD
clsHIcon : WORD
clsHCursor : WORD
clsHbrBackground : WORD
clsLpszMenuName : DWORD
clsLpszClassName : DWORD
clsStyle : WORD
Элемент clsStyleclsLpfnWndProc определяет адрес функции окна, которая обрабатывает сообщения для окон данного класса.
Элемент clsCbClsExtra задает число байт, которое необходимо дополнительно запросить у Windows под эту структуру, для хранения собственных данных, присоединенных классу.
Элемент clsCbWndExtra задает число байт, которое необходимо дополнительно запросить у Windows для размещения всех структур, создаваемых совместно с данным классом, для хранения собственных данных, присоединенных к окну.
Элемент clsHInstance сообщает Windows о том, кто создает определение класса. Когда завершается последний экземпляр программы, Windows удаляет все связанные с ним определения классов.
Элемент clsHIcon определяет пиктограмму, которая будет использоваться для изображения приложения, например на панели задач. Вы можете создать пиктограмму сами(*) или воспользоваться одной из определенных:
IDI_APPLICATION - стандартная пиктограмма для приложения
IDI_HAND - "стоп"
IDI_QUESTION - "вопросительный знак"
IDI_EXCLAMATION - "восклицательный знак"
IDI_ASTERISK - "информация"
Элемент clsHCursor идентифицирует курсор мыши, используемый в данном окне по умолчанию. Если вы не хотите создавать свой курсор(*), то можно воспользоваться одним, из представляемых Windows:
IDC_ARROW - стрелка
IDC_IBEAM - вертикальная черта
IDC_WAIT - "песочные часы"
IDC_CROSS - крестик
IDC_UPARROW - вертикальная черта
IDC_SIZE - четыре стрелки, указывающие в разные стороны
IDC_ICON - курсор, используемый при drag&drop.
IDC_SIZENWSE - двунаправленная стрелка "северо-запад/юго-восток"
IDC_SIZENESW - двунаправленная стрелка "северо-восток/юго-запад"
IDC_SIZEWE - двунаправленная стрелка "восток-запад"
IDC_SIZENS - двунаправленная стрелка "север-юг"
Элемент clsHbrBackground задает цвет фона для окна. Для его определения можно воспользоваться любой из 20 системных констант цвета :
COLOR_SCROLLBAR - цвет полосы прокрутки
COLOR_BACKGROUND - цвет фона окна
COLOR_ACTIVECAPTION - цвет заголовка активного окна
COLOR_INACTIVECAPTION - цвет заголовка неактивного окна
COLOR_MENU - цвет меню
COLOR_WINDOW - цвет окна
COLOR_WINDOWFRAME - цвет обрамления окна
COLOR_MENUTEXT - цвет текста меню
COLOR_WINDOWTEXT - цвет текста окна
COLOR_CAPTIONTEXT - цвет текста в заголовке окна
COLOR_ACTIVEBORDER - цвет активной границы окна
COLOR_INACTIVEBORDER - цвет неактивной границы окна
COLOR_APPWORKSPACE - цвет рабочего места окна
COLOR_HIGHLIGHT - цвет подсветки
COLOR_HIGHLIGHTTEXT - цвет подсвеченного текста
COLOR_BTNFACE - цвет передней части кнопки
COLOR_BTNSHADOW - цвет тени кнопки
COLOR_GRAYTEXT - цвет "серого" текста"
COLOR_BTNTEXT - цвет текста в кнопке
COLOR_INACTIVECAPTIONTEXT - цвет текста в заголовке неактивного окна
COLOR_BTNHILIGHT - цвет текущей кнопки
ВНИМАНИЕ : При использовании этих констант к их значению обязательно нужно прибавлять единицу, т.к. первое значение для них равно нулю, а ноль является недействительным значением для логического номера кисти.
Элемент clsLpszMenuName - содержит пару сегмент:смещение на имя меню окна, определенное в файле ресурсов(*).
Элемент clsLpszClassName - содержит пару сегмент:смещение на имя класса. Поскольку после регистрации класс становиться доступен всем другим приложениям Windows, имя класса должно быть уникальным.
Элемент clsStyle определяет свойства окна, которые могут комбинироваться при помощи сложения. Если этому параметру присвоить ноль, то Windows автоматически установит значение clsStyle автоматически. Опишем существующие стили окна :
CS_BYTEALIGNCLIENT - оказывает влияние на ширину окна, задавая выравнивание клиентской области на границе байтов
CS_BYTEALIGNWINDOW - устанавливает выравнивание окна по границе байтов
CS_CLASSDC - обеспечивает контекст устройства, используемый совместно всеми окнами класса.
CS_DBLCLKS - служит для установки таймера после получения первого сообщения о нажатии кнопки на мыши
CS_GLOBALCLASS - позволяет приложению создавать окно этого класса, не считаясь со значением параметра clsHInstance
CS_HREDRAW - определяет, что окно будет перерисовываться, как только измениться его горизонтальный размер
CS_VREDRAW - определяет, что окно будет перерисовываться, как только измениться его вертикальный размер
CS_NOCLOSE - запрещает пункт "Close" системного меню
CS_OWNDC - предоставляет частный контекст устройства для каждого окна в данном классе
CS_PARENTDC - устанавливает, что дочерние окна не могут рисовать в родительском окне
CS_SAVEBITS - предписывает системе сохранять копию части экрана, поврежденной окном
Теперь, когда вы ознакомились с полями структуры WNDCLASS можно их мысленно запомнить и, передав адрес структуры в качестве параметра в функцию REGISTERCLASS, произвести регистрацию окна.
После успешной регистрации класса окна мы можем создавать окна этого класса. Для создания окон используется функция CREATEWINDOW. Эта функция очень громоздка, так как содержит большое число параметров, поэтому постараемся объяснить их назначение.
Параметры функции CREATEWINDOW :
lpszClassName : DWORD
lpszWindowName : DWORD
dwStyle : DWORD
x : WORD
y : WORD
nWidth : WORD
nHeight : WORD
hWndParent : WORD
hMenu : WORD
hInstance : WORD
lpParam : DWORD
Параметр lpszClassName содержит адрес строки названия класса окна. В данном случае мы будем использовать только, что зарегистрированный класс окна
Параметр lpszWindowName содержит адрес строки, которая будет идентифицировать окно в панели задач, а также будет видна в заголовке окна.
Параметр dwStyle содержит двойное слово параметров (стилей) окна.
Параметры x,y,nWidth,nHeight содержат геометрические координаты местоположения окна на экране.
Параметр hWndParent содержит идентификатор родительского окна. Если это окно основное, то значение этого параметра должно быть равно 0.
Параметр hMenu содержит идентификатор меню для создаваемого окна.
Параметр hInstance содержит идентификатор приложения, которое создает это окно.
Параметр lpParam содержит дополнительную информацию об окне.
После создания окна его нужно вывести на экран и вставить в очередь обработки сообщений. Используйте функцию SHOWWINDOW, передайте ей в параметрах - идентификатор вашего окна, и стиль просмотра окна. Для вставки окна в очередь обработки сообщений используется функция UPDATEWINDOW с параметром - идентификатор окна.
Теперь, когда наше первое окно создано и видно на экране, надо попробовать поработать с ним. В данный момент наше окно, и приложение, в том числе представляют собой два мертвых груза, и для того, что бы наша программа ожила нужно начать обрабатывать сообщения. Прежде чем привести пример надо разобраться с еще одной немаловажной структурой - MSGSTRUCT. Эта структура описывает формат сообщения Windows. Мы рассмотрим только часть ее элементов, ибо в большинстве случаев остальные совсем не используются.
msHWND : WORD - идентификатор окна HWND, которому было передано сообщение
msMESSAGE : WORD - идентификатор сообщения
msWPARAM : WORD - параметр 1
msLPARAM : DWORD - параметр 2
Как видите структура сообщения Windows проста, однако, процедура ее обработки не очень :
msg MSGSTRUCT <0>
msg_loop:
; Получаем сообщение
call GETMESSAGE,ds offset msg,0,0,0
; Проверяем на наличие сообщения WM_QUIT (AX=0)
cmp ax,0
je end_loop
; Обрабатываем сообщение
call TRANSLATEMESSAGE,ds offset msg
call DISPATCHMESSAGE,ds offset msg
; Обрабатываем следующее сообщение
jmp msg_loop
end_loop:
; Выходим из программы
mov ax,[msg.msWPARAM]
mov ah,4Ch
int 21h
Этот кусок кода должен начать работать стразу после создания главного окна. Он отвечает за получение, перевод и передачу сообщения вашему окну. Если этот обработчик получает сообщение WM_QUIT, то он завершает работу программы. Этот участок вашей программы только передает сообщения вашему окну, но не обрабатывает их, для этого существует функция WndProc, адрес которой мы передали при заполнении структуры WNDCLASS. Именно эта функция разбирает по кусочкам сообщения и передает работу вашим обработчикам.
WndProc PROC
ARG hwnd:WORD,wmsg:WORD,wparam:WORD,lparam:DWORD
cmp [wmsg],WM_DESTROY
; Если получили сообщение WM_DESTROY (получено сообщение "на выход")
je wmdestroy
cmp [wmsg],WM_LBUTTONDOWN
; Если получили сообщение WM_LBUTTONDOWN (нажата левая кнопка мыши)
je wmlbuttondown
cmp [wmsg],WM_CREATE
; Если получили сообщение WM_CREATE (получено сообщение "создать окно")
je wmcreate
cmp [wmsg],WM_RBUTTONDOWN
; Если получили сообщение WM_RBUTTONDOWN (нажата правая кнопка мыши)
je wmrbuttondown
cmp [wmsg],WM_PAINT
; Если получили сообщение WM_PAINT (получено сообщение "нарисуйся")
je wmpaint
; Если мы не обрабатываем ни одно из вышеперечисленных сообщений передаем
; управление стандартному обработчику
jmp defwndproc
Процедура обработки сообщения должна обнулять регистровую пару DX:AX перед выходом из обработчика. Эти регистры используется Windows для получения результатов вызова обработчика.
ВНИМАНИЕ: Если переданное сообщение не обрабатывается обработчиком, обязательно нужно передать управление стандартному обработчику. Для того, что бы это сделать нужно вызвать функция DEFWINDOWPROC, передав ей в параметрах : идентификатор окна, идентификатор сообщения и параметры.
Теперь когда, все рассказано и показано приведем пример рабочей программы :
Файл window.asm
locals
jumps
.model large, WINDOWS PASCAL
; Подключаем файл описания констант и структур API
include windows.inc
; Описываем используемые функции
extrn BEGINPAINT:PROC
extrn CREATEWINDOW:PROC
extrn DEFWINDOWPROC:PROC
extrn DISPATCHMESSAGE:PROC
extrn ENDPAINT:PROC
extrn GETMESSAGE:PROC
extrn GETSTOCKOBJECT:PROC
extrn INITAPP:PROC
extrn INITTASK:PROC
extrn LOADCURSOR:PROC
extrn MESSAGEBOX:PROC
extrn POSTQUITMESSAGE:PROC
extrn REGISTERCLASS:PROC
extrn SHOWWINDOW:PROC
extrn TEXTOUT:PROC
extrn TRANSLATEMESSAGE:PROC
extrn UPDATEWINDOW:PROC
extrn WAITEVENT:PROC
.data
; Для Windows Task Manager'a
freespace db 16 dup(0)
; HINSTANCE нашего приложения
hInstance dw ?
; Параметр просмотра окна
cmdShow dw ?
; Структура PAINTSTRUCT
lppaint PAINTSTRUCT <0>
; Структура сообщения MSGSTRUCT
msg MSGSTRUCT <0>
; Структура описания окна
wc WNDCLASS <0>
; Заголовок окна
lpszTitleName db 'First Window',0
; Название класса окна
lpszClassName db 'ASMCLASS',0
; Выводимая в окно строка
lpszText db 'Hello World'
; Длина строки lpszText
lpszTextLength equ $-lpszText
; Заголовок диалогового окна
lpszCaption db 'Mouse Event',0
; Сообщения диалогового окна
lpszLeftMsg db 'Left button pressed',0
lpszRightMsg db 'Right button pressed',0
.code
.286
; Как говоритья в WarLords "Lets The War Begins"
start:
; Инициализируем сегмент данных
mov ax, @data
mov ds, ax
; Инициализируем задачу и получаем входные параметры
call INITTASK
or ax,ax
; Если инициализация прошла успешно
jnz @@OK
; Если ошибка
jmp @@Fail
@@OK:
; Сохраняем HINSTANCE
mov [hInstance],di
; Сохраняем параметр просмотра окна
mov [cmdShow],dx
; Инициализируем приложение
call INITAPP,hInstance
or ax,ax
jnz @@InitOK
@@Fail:
; Если инициализация завершилась неудачно
mov ax, 4CFFh
int 21h
@@InitOK:
; Заполняем структуру описания окна
mov [wc.clsStyle],0
mov word ptr [wc.clsLpfnWndProc],offset WndProc
mov word ptr [wc.clsLpfnWndProc+2],seg WndProc
mov [wc.clsCbClsExtra],0
mov [wc.clsCbWndExtra],0
mov ax,[hInstance]
mov [wc.clsHInstance],ax
mov [wc.clsHIcon],0
; Загружаем курсор IDC_ARROW и вставляем его в структуру
xor ax,ax
call LOADCURSOR,ax IDC_ARROW
mov [wc.clsHCursor],ax
; Загружаем цвет белого фона и вставляем его в структуру
call GETSTOCKOBJECT,WHITE_BRUSH
mov [wc.clsHbrBackground],ax
mov word ptr [wc.clsLpszMenuName],0
mov word ptr [wc.clsLpszMenuName+2],0
mov word ptr [wc.clsLpszClassName],offset lpszClassName
mov word ptr [wc.clsLpszClassName+2],ds
; Регистрируем структуру описания окна
call REGISTERCLASS,ds offset wc
; Создаем окно
xor ax,ax
mov bx,CW_USEDEFAULT
call CREATEWINDOW,ds offset lpszClassName,ds offset lpszTitleName\ ,WS_OVERLAPPEDWINDOW,ax,bx,bx,bx,bx,ax,ax,\
[hInstance],ax,ax
; Показываем окно
push ax
call SHOWWINDOW,ax,cmdShow
; Обновляем окно
pop ax
call UPDATEWINDOW,ax
; Цикл обработки сообщений
msg_loop:
; Получаем сообщение
call GETMESSAGE,ds offset msg,0,0,0
; Проверяем на наличие сообщения WM_QUIT (AX=0)
cmp ax,0
je end_loop
; Обрабатываем сообщение
call TRANSLATEMESSAGE,ds offset msg
call DISPATCHMESSAGE,ds offset msg
; Обрабатываем следующее сообщение
jmp msg_loop
end_loop:
; Выходим из программы
mov ax,[msg.msWPARAM]
mov ah,4Ch
int 21h
;
; Процедура обработки сообщений
;
WndProc PROC
ARG hwnd:WORD,wmsg:WORD,wparam:WORD,lparam:DWORD
cmp [wmsg],WM_DESTROY
; Если получили сообщение WM_DESTROY (получено сообщение "на выход")
je wmdestroy
cmp [wmsg],WM_LBUTTONDOWN
; Если получили сообщение WM_LBUTTONDOWN (нажата левая кнопка мыши)
je wmlbuttondown
cmp [wmsg],WM_CREATE
; Если получили сообщение WM_CREATE (получено сообщение "создать окно")
je wmcreate
cmp [wmsg],WM_RBUTTONDOWN
; Если получили сообщение WM_RBUTTONDOWN (нажата правая кнопка мыши)
je wmrbuttondown
cmp [wmsg],WM_PAINT
; Если получили сообщение WM_PAINT (получено сообщение "нарисуйся")
je wmpaint
; Если мы не обрабатываем ни одно из вышеперечисленных сообщений передаем
; управление стандартному обработчику
jmp defwndproc
;
; Обработка сообщения WM_PAINT
;
wmpaint:
; Начинаем рисование и получаем указатель на текущий DC
call BEGINPAINT,hwnd,ds offset lppaint
; AX содержит контекст устройства DC, полученный после вызова BEGINPAINT
; Вызываем TEXTOUT для вывода строки lpszText
call TEXTOUT,ax,5,5,ds offset lpszText,lpszTextLength
; Заканчиваем рисование
call ENDPAINT,hwnd,ds offset lppaint
; Обнуляем AX и на выход
xor ax,ax
jmp finish
;
; Обработка сообщения WM_CREATE
;
wmcreate:
; Обнуляем AX и на выход
xor ax,ax
jmp finish
;
; Вызов стандартного обработчика сообщений
;
defwndproc:
call DEFWINDOWPROC,hwnd,wmsg,wparam,lparam
jmp finish
;
; Обработка сообщения WM_DESTROY
;
wmdestroy:
; Вызываем POSTQUITMESSAGE
call POSTQUITMESSAGE,0
; Обнуляем AX и на выход
xor ax,ax
jmp finish
;
; Обработка сообщения WM_LBUTTONDOWN
;
wmlbuttondown:
; Выводим на экран MESSAGEBOX с надписью "Left button pressed"
call MESSAGEBOX,0,ds offset lpszLeftMsg,ds offset lpszCaption,0
; Обнуляем AX и на выход
xor ax,ax
jmp finish
;
; Обработка сообщения WM_RBUTTONDOWN
;
wmrbuttondown:
; Выводим на экран MESSAGEBOX с надписью "Right button pressed"
call MESSAGEBOX,0,ds offset lpszRightMsg,ds offset lpszCaption,0
; Обнуляем AX и на выход
xor ax,ax
jmp finish
;
; "Финишная прямая"
;
finish:
; Обнуляем DX и на выход
xor dx,dx
ret
WndProc ENDP
end start
Файл window.def
NAME WINDOW
EXETYPE WINDOWS
CODE MOVABLE DISCARDABLE
DATA MOVABLE MULTIPLE DISCARDABLE
STACKSIZE 5120
HEAPSIZE 4096
DESCRIPTION 'Copyright(C) 1999 BlackWolf'
Ну, вот и все про окна и сообщения.