Урок 3. Создаем диалоговое окно для определения всех доступных режимов.
Как определить все доступные полноэкранные режимы и при этом сделать так, чтобы пользователь смог выбирать в каком режиме работать приложению ? В этом нам поможет диалоговое окно.
Если вы уже знаете, что такое диалоговое окно и представляете, как оно выглядит, как его создать и как с ним работать, то все равно советую вам почитать эту статью. Тем же, кто плохо понимает, что это за зверь такой, следует дополнительно посмотреть статьи на эту тему.
Готовы ? Тогда вперед !
Диалоговое окно в нашем приложении можно реализовать двумя путями. Первый путь - это встроить его в само приложение. Второй путь - это реализовать его как самостоятельное приложение. Что выбрать ? Оба варианта имеют свои плюсы и минусы.
Среди плюсов первого варианта то, что оно находится внутри нашего приложения. При этом нам нужно написать не такой уж большой обьем кода. Также все настройки, выбранные пользователем, доступны немедленно. Среди минусов то, что при каждом запуске приложения пользователю приходиться выбирать настройки.
Реализация второго варианта несколько сложнее. Нужно создавать два экзешника. Один для диалогового окна, другой - собственно для приложения. При этом все настройки, произведенные пользователем, придется записывать в файл. Но зато появляется возможность переделывать Setup снова и снова, не трогая при этом приложение, ведь всё равно все настройки сохраняются в файле.
В итоге каждый вариант подходит для определенных задач. Например, для какой - нибудь демки встроенное диалоговое окно самое то. А вот для игрушки - наоборот. Можно также придумать какой нибудь гибрид. Я долго размышлял какой вариант использовать для урока и в конце концов решил сделать оба, чтобы у вас была возможность сравнить. Это потребовало довольно много времени, и как следствие, урок был готов гораздо позже, чем было запланировано. Мы с вами рассмотрим только второй вариант, так как он немного сложнее.
По плану нам требуется: определить все доступные оконные режимы, определить все доступные полоноэкранные режимы, запихать всю эту информацию в диалоговое окно, вывести его на экран, обработать все действия пользователя, сохранить настройки в файле и при выходе из Setup запустить само приложение если требуется. Вроде бы целая куча работы, но на самом деле все гораздо проще.
Начнем с файла ресурсов. Берем любой редактор, пара минут и все готово. Кратко рассмотрим по частям, как он должен выглядеть.
#define DS_CENTER 0x00000800L
#define DS_MODALFRAME 0x00000080L
#define WS_EX_TOOLWINDOW 0x00000080L
#define WS_GROUP 0x00020000L
#define WS_EX_STATICEDGE 0x00020000L
#define BS_AUTORADIOBUTTON 0x00000009L
#define BS_FLAT 0x00008000L
#define CBS_DROPDOWNLIST 0x00000003L
#define WS_VSCROLL 0x00200000L
#define WS_TABSTOP 0x00010000L
#define WS_DISABLED 0x08000000L
#define ID_OK 10
#define ID_CANCEL 11
#define ID_SAVE 12
#define IDC_STATIC -1
#define IDC_WINDOWED 20
#define IDC_FULLSCREEN 21
#define IDC_SCREENMODESFS 22
#define IDC_SCREENMODESW 23
#define IDC_HERZ 24
Здесь у нас константы, в частности, номера от 10 до 24 - это идентификаторы элементов нашего диалогового окна. Они будут нам передаваться в сообщениях от Windows, так мы сможем узнать с каким элементом в данный момент работает пользователь.
SetupDialog DIALOG LOADONCALL MOVEABLE DISCARDABLE 0, 0, 175, 91
STYLE DS_MODALFRAME | DS_CENTER
EXSTYLE WS_EX_TOOLWINDOW
CAPTION " Настройка параметров запуска"
FONT 8, "Verdana"
Здесь имя диалога, его вид и размер, параметры вывода на экран, текст заголовка и используемый шрифт.
BEGIN
CONTROL "В окошке",IDC_WINDOWED,"Button",BS_AUTORADIOBUTTON | BS_FLAT | WS_TABSTOP ,12,14,80,14
COMBOBOX IDC_SCREENMODESW,92,14,71,50,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
CONTROL "Полноэкранное",IDC_FULLSCREEN,"Button",BS_AUTORADIOBUTTON | BS_FLAT | WS_TABSTOP,12,30,80,12
COMBOBOX IDC_SCREENMODESFS,12,43,71,120,CBS_DROPDOWNLIST | WS_VSCROLL | WS_DISABLED | WS_TABSTOP
LTEXT "Частота в герцах",IDC_STATIC,94,33,65,10
COMBOBOX IDC_HERZ, 92, 43, 71, 80, CBS_DROPDOWNLIST | WS_VSCROLL | WS_DISABLED | WS_TABSTOP
GROUPBOX "", IDC_STATIC, 6,3,163,61
DEFPUSHBUTTON "Запуск", ID_OK,118,69,51,17,0,WS_EX_STATICEDGE
PUSHBUTTON "Выход", ID_CANCEL,62,69,51,17,0,WS_EX_STATICEDGE
PUSHBUTTON "Сохранить", ID_SAVE,6,69,51,17,0,WS_EX_STATICEDGE
END
Между строками BEGIN и END располагаются строки, описывающие элементы расположенные на диалоговом окне. Здесь определены два RadioButton, три ComboBox, одно текстовое поле, одна рамка и три простых кнопки.
А вот и исходный текст Setup.exe:
.686
.MMX
.XMM
.MODEL FLAT, STDCALL
OPTION CASEMAP:none
INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDE \masm32\include\user32.inc
INCLUDE \masm32\include\gdi32.inc
INCLUDE \masm32\include\shell32.inc
INCLUDE \masm32\DirectX81\d3d8.inc
INCLUDELIB \masm32\lib\kernel32.lib
INCLUDELIB \masm32\lib\user32.lib
INCLUDELIB \masm32\lib\gdi32.lib
INCLUDELIB \masm32\lib\shell32.lib
INCLUDELIB \masm32\directx81\d3d8.lib
SetupDialog PROTO :DWORD,:DWORD,:DWORD,:DWORD
DW2A PROTO :DWORD, :DWORD
.DATA ;------------------------------------------
szDialogName db "SetupDialog",0
szAppName db "Tutorial03.exe",0
szFileName db "Settings.ini",0
szOperation db "open",0
WinModeTable dw 320,240, 640,480, 800,600, 1024,768, 1280,1024, 1600,1200
WinModeStrTable dd OFFSET szMode1 , OFFSET szMode2
dd OFFSET szMode3 , OFFSET szMode4
dd OFFSET szMode5 , OFFSET szMode6
szMode1 db "320x240",0
szMode2 db "640x480",0
szMode3 db "800x600",0
szMode4 db "1024x768",0
szMode5 db "1280x1024",0
szMode6 db "1600x1200",0
.DATA? ;-----------------------------------------
pd3d dd ? ; Указатель на Direct3D
d3ddm D3DDISPLAYMODE > ; Для получения информации о доступных режимах
d3dpp D3DPRESENT_PARAMETERS > ; Структура для создания Direct3DDevice8
WinStyle dd ?
CmbWindow dd ? ; ID ComboBox оконных режимов
cmbFullScreen dd ? ; ID ComboBox полноэкранных режимов
cmbHerz dd ? ; ID ComboBox герц
FullModeTable dd 50 dup (?) ; Для хранения доступных полноэкранных режимов
FullModeBPPTable db 50 dup (?) ; Для хранения форматов поверхности.
FullModeHerzTable db 50 dup (?) ; Для хранения частоты в Герцах.
FullModeStrBuffer db 16 dup (?) ; Буфер для формирования строки
AppPath db 260 dup (?) ; Буфер для пути к Setup.exe
hfile dd ?
brr dd ?
.CODE ;------------------------------------------
Start:
Invoke DialogBoxParam,400000h,ADDR szDialogName,NULL,OFFSET SetupDialog,NULL
Invoke ExitProcess,0
Ret
SetupDialog proc hDlg:HWND , iMsg:DWORD , wParam:WPARAM , lParam:LPARAM
;----------------------------------------------------------------------
mov eax, iMsg
cmp eax, WM_INITDIALOG ; Обрабатывая это сообщ. надо ОБЯЗАТЕЛЬНО вернуть EAX=1
je wmInitDialog
cmp eax, WM_COMMAND
je wmCommandDialog
xor eax, eax
ret
wmInitDialog:
;--------------
pushad
invoke CheckRadioButton, hDlg , 20 , 21 , 20
invoke GetDlgItem, hDlg , 23
mov cmbWindow, eax
invoke GetDlgItem, hDlg , 22
mov cmbFullScreen, eax
invoke GetDlgItem, hDlg , 24
mov cmbHerz, eax
invoke Direct3DCreate8, D3D_SDK_VERSION
mov pd3d, eax
d3d8 GetAdapterDisplayMode, pd3d, D3DADAPTER_DEFAULT, ADDR d3dpp
lea esi, d3dpp.BackBufferWidth
mov eax, [esi]
add eax, [esi+4]
mov ebx, [esi+12]
mov [esi+8], ebx
mov DWORD PTR [esi+12], 3
xor ebx, ebx
; Проверка доступных оконных режимов и заполнение списка
getNextMode:
push ebx
push eax
invoke SendMessage,cmbWindow,CB_ADDSTRING,0,[WinModeStrTable + ebx*4]
pop eax
pop ebx
movzx ecx, [WinModeTable+ebx*4]
movzx edx, [WinModeTable+ebx*4+2]
add ecx, edx
inc ebx
cmp eax, ecx
ja getNextMode
; В итоге в ComboBox занесены строки доступных нам оконных режимов
; от минимальных до текущих размеров экрана включительно
; Проверка доступных полноэкранных режимов и заполнение списка
d3d8 GetAdapterModeCount, pd3d, D3DADAPTER_DEFAULT
dec eax
lea esi, FullModeTable
xor ebx, ebx
mov edx, ebx
mov ecx, eax
getMode:
push ecx
push esi
push ebx
push edx
d3d8 EnumAdapterModes, pd3d, D3DADAPTER_DEFAULT, ecx, ADDR d3ddm
mov eax, d3ddm.Width1
mov ecx, d3ddm.Height
mov edi, eax
add edi, ecx
add edi, d3ddm.Format
pop edx
pop ebx
pop esi
cmp edx, edi
je noAdd
mov edx, d3ddm.RefreshRate
mov BYTE PTR [FullModeHerzTable+ebx], dl
mov edx, d3ddm.Format
mov [FullModeBPPTable+ebx], dl
push edx
push edi
pop edx
pop edi
mov [esi+ebx*4], eax
mov [esi+ebx*4+2], ecx
inc ebx
push ebx
push esi
push edx
; Формирование строки и добавление ее в Combobox
invoke DW2A, eax, ADDR FullModeStrBuffer
mov BYTE PTR [ebx], 'x'
inc ebx
invoke DW2A, ecx, ebx
mov BYTE PTR [ebx], 'x'
inc ebx
mov ecx, 16
cmp edi, D3DFMT_X8R8G8B8
jne nextFormat
mov ecx, 32
nextFormat:
cmp edi, D3DFMT_A8R8G8B8
jne nextFormat2
mov ecx, 32
nextFormat2:
cmp edi, D3DFMT_R8G8B8
jne exitCompare
mov ecx, 24
exitCompare:
invoke DW2A, ecx , ebx
mov BYTE PTR [ebx], 0
invoke SendMessage, cmbFullScreen , CB_ADDSTRING , 0 , ADDR FullModeStrBuffer
pop edx
pop esi
pop ebx
noAdd:
pop ecx
dec ecx
test ecx, ecx
jne getMode
; В итоге в ComboBox занесены строки доступных нам полноэкранных режимов
; с разными форматами поверхности и самым высоким числом Герц для каждого режима
invoke SendMessage, cmbWindow , CB_SETCURSEL , 0 , 0
invoke SendMessage, cmbFullScreen , CB_SETCURSEL , 0 , 0
invoke SendMessage, hDlg, WM_COMMAND , 22 + CBN_SELCHANGE shl 16, 0
popad
xor eax, eax
inc eax
ret
wmCommandDialog:
;----------------
movzx eax, WORD PTR wParam ; Здесь нам нужно получить HIWORD и LOWORD из wParam
mov ecx, wParam ; HIWORD - код уведомления от элемента диалога
; LOWORD - ID элемента диалога
xor ebx, ebx
cmp eax, 10 ; Получаем если нажали кнопку Запуск
je cmStartProgram
cmp eax, 12 ; Получаем если нажали кнопку Сохранить
je cmSaveData
cmp eax, 11 ; Получаем если нажали кнопку Закрыть
je cmCloseProgram
cmp eax, 20 ; Получаем если нажали RadioButton "В окошке"
je cmRadioWin
cmp eax, 21 ; Получаем если нажали RadioButton "Полноэкранное"
je cmRadioFull
cmp ecx, 22 + CBN_SELCHANGE shl 16
je cmHerz
xor eax, eax
ret
cmRadioFull:
inc ebx
cmRadioWin:
invoke EnableWindow, cmbFullScreen , ebx
invoke EnableWindow, cmbHerz , ebx
xor ebx, 1
invoke EnableWindow, cmbWindow , ebx
xor eax, eax
ret
cmHerz:
invoke SendMessage, cmbHerz , CB_RESETCONTENT , 0 , 0
invoke SendMessage, cmbFullScreen , CB_GETCURSEL , 0 , 0
movzx ebx, BYTE PTR [FullModeHerzTable+eax]
invoke DW2A, ebx , ADDR FullModeStrBuffer
invoke SendMessage, cmbHerz , CB_ADDSTRING , 0 , ADDR FullModeStrBuffer
invoke SendMessage, cmbHerz , CB_SETCURSEL , 0 , 0
xor eax, eax
ret
cmSaveData:
invoke IsDlgButtonChecked, hDlg , 20
test eax, eax
je EnableFull
invoke SendMessage, cmbWindow , CB_GETCURSEL , 0 , 0
lea esi, WinModeTable
shl eax, 2
add esi, eax
xor ecx, ecx
mov edx, ecx
push [esi]
pop cx
pop dx
mov d3dpp.BackBufferWidth, ecx
mov d3dpp.BackBufferHeight, edx
mov d3dpp.Windowed, 1
mov WinStyle, WS_SYSMENU
jmp Create
EnableFull:
invoke SendMessage, cmbFullScreen , CB_GETCURSEL , 0 , 0
movzx ebx, BYTE PTR [FullModeHerzTable+eax]
mov d3dpp.FullScreen_RefreshRateInHz, ebx
movzx ebx, BYTE PTR [FullModeBPPTable+eax]
mov d3dpp.BackBufferFormat, ebx
lea esi, FullModeTable
shl eax, 2
add esi, eax
push [esi]
xor ecx, ecx
mov edx, ecx
mov d3dpp.Windowed, ecx
pop cx
pop dx
mov d3dpp.BackBufferWidth, ecx
mov d3dpp.BackBufferHeight, edx
mov WinStyle, WS_POPUP
Create:
mov d3dpp.SwapEffect, D3DSWAPEFFECT_FLIP
invoke CreateFile , ADDR szFileName , GENERIC_WRITE, NULL, \
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL
mov hfile, eax
invoke WriteFile , hfile , ADDR d3dpp , 56 , ADDR brr , NULL
invoke CloseHandle , hfile
xor eax, eax
ret
cmStartProgram:
invoke EndDialog, hDlg , 0
d3d8 Release, pd3d
invoke GetModuleFileName , 0 , ADDR AppPath , 260
lea edi,AppPath
lea edi,[edi+eax-6]
@@:
mov al,[edi]
dec edi
cmp al,'\'
jne @B
inc edi
inc edi
cld
lea esi, szAppName
mov ecx, SIZEOF szAppName
shr ecx, 2
rep movsd
mov ecx, [SIZEOF szAppName ]
and ecx, 3
rep movsb
invoke ShellExecute , NULL , ADDR szOperation , ADDR AppPath , \
NULL , NULL , SW_SHOWNORMAL
xor eax, eax
ret
cmCloseProgram:
invoke EndDialog, hDlg , 0
d3d8 Release, pd3d
xor eax, eax
ret
SetupDialog endp
Еще после SetupDialog находится текст функции DW2A. Я не стал ее тут приводить, т.к. она является слегка измененной dw2a из библиотеки masm32. Интересующиеся всегда смогут посмотреть ее в исходнике.
Теперь все разберем по косточкам.
WinModeTable dw 320,240, 640,480, 800,600, 1024,768, 1280,1024, 1600,1200
WinModeStrTable dd OFFSET szMode1 , OFFSET szMode2
dd OFFSET szMode3 , OFFSET szMode4
dd OFFSET szMode5 , OFFSET szMode6
szMode1 db "320x240",0
szMode2 db "640x480",0
szMode3 db "800x600",0
szMode4 db "1024x768",0
szMode5 db "1280x1024",0
szMode6 db "1600x1200",0
Массив WinModeTable содержит размеры шести определенных нами окошек. Он также нужен нам для сравнения с текущими размерами рабочего стола. Массив WinModeStrTable содержит указатели на текстовые строки. Эти строки впоследствии будут занесены в ComboBox оконных режимов.
FullModeTable dd 50 dup (?) ; Для хранения доступных полноэкранных режимов
FullModeBPPTable db 50 dup (?) ; Для хранения форматов поверхности.
FullModeHerzTable db 50 dup (?) ; Для хранения частоты в Герцах.
FullModeStrBuffer db 16 dup (?) ; Буфер для формирования строки
В массиве FullModeTable число 50 является количеством всех полноэкранных режимов, которые мы можем поместить. Я, на всякий случай, сделал его с запасом. Вообще, когда мы спрашиваем Direct3D о количестве доступных режимов он возвращает нам огромное их число, куда входят режимы с одинаковым разрешением, но с разным числом герц и форматом поверхности. Например, на моей видюхе он выдает 90 доступных режимов. Чтобы не возиться с разной частотой я решил определять только те режимы, в которых число Герц максимально. В результате мы ничего не теряем, т.к. при выборе настроек включать частоту в 60, 70 и т.п. Герц все равно никто не будет. Взяв на вооружение этот факт, мы сокращаем число режимов до двух - трех десятков (Примечание: В Win98 Direct3D выдает вместо числа Герц ноль, поэтому, если занести в структуру D3DPRESENT_PARAMETERS определенную частоту, а не ноль, то создать устройство не удастся). В массиве FullModeStrBuffer мы будем формировать строку вида: 1024х768х32 и затем заносить ее в ComboBox.
Invoke DialogBoxParam,400000h,ADDR szDialogName,NULL,OFFSET SetupDialog,NULL
Наш диалог из ресурсов вызываем вот такой строкой где 400000h - это hInstance.
wmInitDialog:
;--------------
pushad
invoke CheckRadioButton, hDlg , 20 , 21 , 20
invoke GetDlgItem, hDlg , 23
mov cmbWindow, eax
invoke GetDlgItem, hDlg , 22
mov cmbFullScreen, eax
invoke GetDlgItem, hDlg , 24
mov cmbHerz, eax
Ну вот добрались до самого главного: определения доступных режимов. Вначале, устанавливаем отметку на RadioButton "В окошке" и снимаем с "Полноэкранное". Затем получаем ID всех наших ComboBox'ов. ( Примечание: При выходе из обработки сообщения INIT_DIALOG, когда мы уже возвращаем EAX=1 и передаем управление системе, в Win98 происходила ошибка. На мой взгляд, причина кроется в каких - то регистрах, которые мы изменили в процессе работы. Используя PUSHAD при входе и POPAD при выходе, этот баг можно ликвидировать. Что касается XP, то ему, похоже, до лампочки есть PUSHAD, POPAD или нет. )
invoke Direct3DCreate8, D3D_SDK_VERSION
mov pd3d, eax
d3d8 GetAdapterDisplayMode, pd3d, D3DADAPTER_DEFAULT, ADDR d3dpp
lea esi, d3dpp.BackBufferWidth
mov eax, [esi]
add eax, [esi+4]
mov ebx, [esi+12]
mov [esi+8], ebx
mov DWORD PTR [esi+12], 3
xor ebx, ebx
; Проверка доступных оконных режимов и заполнение списка
getNextMode:
push ebx
push eax
invoke SendMessage,cmbWindow,CB_ADDSTRING,0,[WinModeStrTable + ebx*4]
pop eax
pop ebx
movzx ecx, [WinModeTable+ebx*4]
movzx edx, [WinModeTable+ebx*4+2]
add ecx, edx
inc ebx
cmp eax, ecx
ja getNextMode
Создаем Direct3D, получаем текущие параметры рабочего стола посредством GetAdapterDisplayMode. Так как мы помещаем данные в D3DPRESENT_PARAMETERS, нам придется провести небольшую манипуляцию, занося формат поверхности с четвертого поля структуры на третье. В четвертое поле заносим число BackBuffer равное трем. Сделав это, часть D3DPRESENT_PARAMETERS мы уже сформировали. Далее идет цикл на сравнение суммы текущих ширины и высоты с суммой размеров записанных нами в WinModeTable. Одновременно с этим заполняем ComboBox.
Справка
GetAdapterDisplayMode передается два параметра.
1. Номер адаптера. D3DADAPTER_DEFAULT - адаптер по умолчанию.
2. Адрес структуры D3DDISPLAYMODE. ( См. предыдущие уроки )
Метод возвращает D3D_OK и заполняет структуру данными если все прошло успешно.
Если нет, то D3DERR_INVALIDCALL - недействительный вызов.
d3d8 GetAdapterModeCount, pd3d, D3DADAPTER_DEFAULT
dec eax
Далее получаем количество всех доступных полноэкранных режимов и уменьшаем его на единицу, т.к. нумерация начинается с нуля.
Справка
GetAdapterModeCount передается только номер адаптера.
Метод возвращает кол-во всех доступных режимов если все прошло успешно.
Если нет, то ноль.
lea esi, FullModeTable
xor ebx, ebx
mov edx, ebx
mov ecx, eax
getMode:
push ecx
push esi
push ebx
push edx
d3d8 EnumAdapterModes, pd3d, D3DADAPTER_DEFAULT, ecx, ADDR d3ddm
mov eax, d3ddm.Width1
mov ecx, d3ddm.Height
mov edi, eax
add edi, ecx
add edi, d3ddm.Format
pop edx
pop ebx
pop esi
cmp edx, edi
je noAdd
mov edx, d3ddm.RefreshRate
mov BYTE PTR [FullModeHerzTable+ebx], dl
mov edx, d3ddm.Format
mov [FullModeBPPTable+ebx], dl
push edx
push edi
pop edx
pop edi
mov [esi+ebx*4], eax
mov [esi+ebx*4+2], ecx
inc ebx
С загрузки адреса массива начинается цикл сравнения и заполнения его доступными полноэкранными режимами. Каждый раз, уменьшая номер режима и вызывая EnumAdapterModes, мы спрашиваем данные у Direct3D. Затем берем полученные ширину и высоту, складываем их и прибавляем к этой сумме формат поверхности. Сравниваем с предыдущим значением суммы и, если таковой у нас нет ( это значит новый режим ), заносим всю информацию по разным массивам, и начинаем формировать строку в буфере, чтобы ее можно было занести в Combobox. Соответственно, если эта сумма есть, то ничего заполнять нам не нужно.
Справка
EnumAdapterModes передается три параметра.
1. Номер адаптера.
2. Номер режима. От 0 и выше.
3. Адрес структуры D3DDISPLAYMODE
Метод возвращает D3D_OK и заполняет структуру данными если все прошло успешно.
Если нет, то D3DERR_INVALIDCALL - недействительный вызов.
push ebx
push esi
push edx
; Формирование строки и добавление ее в Combobox
invoke DW2A, eax, ADDR FullModeStrBuffer
mov BYTE PTR [ebx], 'x'
inc ebx
invoke DW2A, ecx, ebx
mov BYTE PTR [ebx], 'x'
inc ebx
mov ecx, 16
cmp edi, D3DFMT_X8R8G8B8
jne nextFormat
mov ecx, 32
nextFormat:
cmp edi, D3DFMT_A8R8G8B8
jne nextFormat2
mov ecx, 32
nextFormat2:
cmp edi, D3DFMT_R8G8B8
jne exitCompare
mov ecx, 24
exitCompare:
invoke DW2A, ecx , ebx
mov BYTE PTR [ebx], 0
invoke SendMessage, cmbFullScreen , CB_ADDSTRING , 0 , ADDR FullModeStrBuffer
pop edx
pop esi
pop ebx
noAdd:
pop ecx
dec ecx
test ecx, ecx
jne getMode
; В итоге в ComboBox занесены строки доступных нам полноэкранных режимов
; с разными форматами поверхности и самым высоким числом Герц для каждого режима
Здесь у нас идет формирование строки. Функцией DW2A преобразуем значения в текст, добавляем иксы. Проверяем ID формата и то что получилось добавляем в качестве окончания строки. Теперь нам только осталось занести эту строку туда куда нужно и все. Это мы и делаем, посылая сообщение СomboBox. А далее снова на начало цикла. На первый взгляд все отлично, но есть здесь одно маленькое но - проверка на формат. Осуществляя сравнение с 24 и 32 битным форматом мы считаем все другие форматы 16-битными. Как мне кажется, это не столь важно. Сколько я ни запускал программу на разных видюхах, будь то ATI или NVIDIA они мне выдавали только 2 формата: D3DFMT_A8R8G8B8 и D3DFMT_R5G6B5 ( вроде эти :) ). Глубже лезть и выяснять, почему такое есть, мне неохота.
invoke SendMessage, cmbWindow , CB_SETCURSEL , 0 , 0
invoke SendMessage, cmbFullScreen , CB_SETCURSEL , 0 , 0
invoke SendMessage, hDlg, WM_COMMAND , 22 + CBN_SELCHANGE shl 16, 0
Последними командами устанавливаем выбранными первые строки в каждом из ComboBox и обработка WM_INITDIALOG завершена. Но впереди не менее увлекательное занятие: сделать так, чтобы все работало как единое целое. Приступаем к рассмотрению обработки события WM_COMMAND. В нем мы проверяем ID всех элементов и, в зависимости от результата, переходим на нужную метку.
cmRadioFull:
inc ebx
cmRadioWin:
invoke EnableWindow, cmbFullScreen , ebx
invoke EnableWindow, cmbHerz , ebx
xor ebx, 1
invoke EnableWindow, cmbWindow , ebx
xor eax, eax
ret
Здесь у нас идет включение одних ComboBox и выключение других. Срабатывает это дело только, когда тыкнуть мышкой на одну из RadioButton.
cmHerz:
invoke SendMessage, cmbHerz , CB_RESETCONTENT , 0 , 0
invoke SendMessage, cmbFullScreen , CB_GETCURSEL , 0 , 0
movzx ebx, BYTE PTR [FullModeHerzTable+eax]
invoke DW2A, ebx , ADDR FullModeStrBuffer
invoke SendMessage, cmbHerz , CB_ADDSTRING , 0 , ADDR FullModeStrBuffer
invoke SendMessage, cmbHerz , CB_SETCURSEL , 0 , 0
xor eax, eax
ret
А это срабатывает, когда нам нужно обновить содержимое ComboBox, содержащего информацию о частоте. Мы все стираем, берем номер выбранного элемента в ComboBox, содержащего доступные полноэкранные режимы, и по нему получаем из массива частоту. Преобразуем ее к удобному варианту и заносим в ComboBox.
cmSaveData:
invoke IsDlgButtonChecked, hDlg , 20
test eax, eax
je EnableFull
invoke SendMessage, cmbWindow , CB_GETCURSEL , 0 , 0
lea esi, WinModeTable
shl eax, 2
add esi, eax
xor ecx, ecx
mov edx, ecx
push [esi]
pop cx
pop dx
mov d3dpp.BackBufferWidth, ecx
mov d3dpp.BackBufferHeight, edx
mov d3dpp.Windowed, 1
mov WinStyle, WS_SYSMENU
jmp Create
EnableFull:
invoke SendMessage, cmbFullScreen , CB_GETCURSEL , 0 , 0
movzx ebx, BYTE PTR [FullModeHerzTable+eax]
mov d3dpp.FullScreen_RefreshRateInHz, ebx
movzx ebx, BYTE PTR [FullModeBPPTable+eax]
mov d3dpp.BackBufferFormat, ebx
lea esi, FullModeTable
shl eax, 2
add esi, eax
push [esi]
xor ecx, ecx
mov edx, ecx
mov d3dpp.Windowed, ecx
pop cx
pop dx
mov d3dpp.BackBufferWidth, ecx
mov d3dpp.BackBufferHeight, edx
mov WinStyle, WS_POPUP
Create:
mov d3dpp.SwapEffect, D3DSWAPEFFECT_FLIP
invoke CreateFile , ADDR szFileName , GENERIC_WRITE, NULL, \
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL
mov hfile, eax
invoke WriteFile , hfile , ADDR d3dpp , 56 , ADDR brr , NULL
invoke CloseHandle , hfile
xor eax, eax
ret
Окончательное формирование D3DPRESENT_PARAMETERS, плюс сохранение всех настроек в файле. Поздравляю 90% работы позади. Сюда мы попадаем, если пользователь нажал кнопку "Сохранить". Ну значит проверяем какая RadioButton выделена и идем по нужному пути. Если "В окошке", то поле Windowed равно 1 и стиль окна WS_SYSMENU. Если "Полноэкранное", то берем номер выбранной строки в ComboBox полноэкранных режимов и уже от этого пляшем. Хватаем из массивов размеры, формат поверхности, частоту и в структуру все укладываем. Поле Windowed равно нулю, а стиль окна будет WS_POPUP. Окончательный штришок в виде SwapEffect и можно все записывать в файл.
Если мы хотим чтобы из диалога вызывалась еще и программа, то нужно написать следующее.
cmStartProgram:
invoke EndDialog, hDlg , 0
d3d8 Release, pd3d
invoke GetModuleFileName , 0 , ADDR AppPath , 260
lea edi,AppPath
lea edi,[edi+eax-6]
@@:
mov al,[edi]
dec edi
cmp al,'\'
jne @B
inc edi
inc edi
cld
lea esi, szAppName
mov ecx, SIZEOF szAppName
shr ecx, 2
rep movsd
mov ecx, [SIZEOF szAppName ]
and ecx, 3
rep movsb
invoke ShellExecute , NULL , ADDR szOperation , ADDR AppPath , \
NULL , NULL , SW_SHOWNORMAL
xor eax, eax
ret
Закрываем диалог. Освобождаем все связанное с Direct3D. Затем получаем путь по которому лежит наш Setup посредством GetModuleFileName. На всякий случай размер буфера под путь равен 260 байт ( если не ошибаюсь это максимальный размер пути в окошках ). Вот мы получили путь, но в нем присутствует и имя нашего Setup. Как его убрать ? Делаем цикл для проверки на '\' ,начиная от конца пути. Найдя его, копируем на место следующего за ним имя файла, который хотим запустить. А дальше все просто. ShellExecute с командой open в качестве параметра, и приложение запущено по нашему приказу, а работа самого Setup завершена.
Осталось только написать текст самого приложения. В нем реализовать загрузку сохраненных настроек и на их основе запускать Direct3D. Описывать здесь я это не буду, лучше гляньте исходник.
Ну все, пора закругляться, а то и так много понаписал, да и чего - то под конец статьи совсем туго соображать стал.
PS: В следующей статье ждите полноценное трехмерное приложение с матрицами, векторами, полигонами и прочей математикой.
Да и не забудьте глянуть в папку Include там лежат файлы d3d.inc и d3dcaps.inc окончательной версии.
Исходник обоих вариантов прилагается (1, 2).
Все вопросы мыльте сюда mybox@aib.ru
[C] keYMax