W systemie Windows, często zachodzi potrzeba skorzystania z okna dialogu definiującego ścieżkę dostępu do pliku. Wyświetlany jest on przede wszystkim podczas operacji wczytania pliku do programu, bądź jego zapisania. - Rys1.
Aby w naszym programie posłużyć się takim oknem dialogowym, należy skorzystać ze struktury OPENFILENAME. Struktura ta jest następnie parametrem odpowiedniej funkcji zapisującej, bądź otwierającej plik do programu.
Tworzymy prosty edytor tekstowy, podobny w funkcjonowaniu do notatnika. Program ma umożliwić otwieranie plików i tworzenie nowych plików poprzez wyczyszczenie pola edycyjnego po wybraniu opcji-nowy plik. Interfejs programu będzie definiowany przez plik zasobów, przedstawiony poniżej. Na początku definiujemy standardowo typ instrukcji procesora, określamy model pamięci jako płaski z wywołaniem procedur typu STDCALL. Dołączamy także odpowiednie biblioteki: user32.lib i kernel32.lib oraz dodatkowo, w porównaniu z poprzednimi programami wykorzystującymi okna, bibliotekę Comdlg32.lib:
.586 ;typ instrukcji procesora
.MODEL
FLAT,STDCALL
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\Comdlg32.lib
Definiujemy stałe określające kontrolki wykorzystywane w programie. Kontrolkami będą poszczególne opcje menu:
ID_NOWY EQU 1010
ID_OTWORZ EQU 1020
ID_ZAKONCZ EQU 1050
IDC_EDT1 EQU 101
Następnie definiujemy wszystkie stałe potrzebne do poprawnego funkcjonowania programu:
OPEN_EXISTING EQU 3
GENERIC_READ EQU 80000000h
GENERIC_WRITE EQU 40000000h
WM_SETICON EQU 80h
WM_COMMAND EQU 111h
WM_SETTEXT EQU 0Ch
WM_GETTEXT EQU 0Dh
WM_CLOSE EQU 10h
WM_INITDIALOG EQU 110h
OFN_ENABLESIZING EQU 00800000h
OFN_FORCESHOWHIDDEN EQU 10000000h
OFN_EXPLORER EQU 00080000h
OFN_PATHMUSTEXIST EQU 00000800h
OFN_OVERWRITEPROMPT EQU 00000002h
OFN_NODEREFERENCELINKS EQU 00100000h
OFN_FILEMUSTEXIST EQU 00001000h
OFN_HIDEREADONLY EQU 00000004h
Pierwsze 8 stałych zostało już omawiane w poprzednich programach. Nowościami są flagi, które przekazujemy w 4-bajtowym polu Flags struktury OPENFILENAME. Flaga OFN_ENABLESIZING pozwala na zmianę wielkości wyświetlanego okna dialogowego. Flaga OFN_FORCESHOWHIDDEN wymusza pokazywanie plików ukrytych i systemowych. Flaga OFN_EXPLORER określa standardowy wygląd okna dialogowego. OFN_PATHMUSTEXIST wymusza wpisywanie tylko prawidłowych ścieżek plików w oknie dialogu. OFN_OVERWRITEPROMPT generuje komunikat jeżeli nadpisujemy jakiś plik podczas zapisu. OFN_NODEREFERENCELINKS zwraca plik, który możemy nadpisywać. OFN_FILEMUSTEXIST określa iż użytkownik może wpisywać nazwy tylko istniejących plików w polu Plik wejścia Name . OFN_HIDEREADONLY ukrywa pole wyboru Read Only. Następnie definiujemy funkcje użyte w programie:
EXTERN lstrlenA@4:NEAR
EXTERN CreateFileA@28:NEAR
EXTERN SendMessageA@16:NEAR
EXTERN SendDlgItemMessageA@20:NEAR
EXTERN EndDialog@8:NEAR
EXTERN GetModuleHandleA@4:NEAR
EXTERN DialogBoxParamA@20:NEAR
EXTERN SetDlgItemTextA@12:NEAR
EXTERN GetOpenFileNameA@4:NEAR
EXTERN CloseHandle@4:NEAR
EXTERN ReadFile@20:NEAR
EXTERN ExitProcess@4:NEAR
Funkcja SetDlgItemTextA@12 wstawia tekst w określoną kontrolkę np. w pole edycyjne. GetOpenFileNameA@4 inicjuje okno dialogowe otwarcia pliku, jej jedynym parametrem jest adres struktury OPENFILENAME. Pozostałe funkcje były już omawiane w poprzednich częściach kursu. Struktura OPENFILENAME jest przedstawiona poniżej:
OPENFILENAMEA STRUC
lStructSize DWORD ? ;długość struktury w bajtach
hwndOwner DWORD ? ;uchwyt do okna które posiada okno dialogowe
hInstance DWORD ? ;uchwyt do modułu który zawiera szablon okna
lpstrFilter DWORD ? ;filtry
lpstrCustomFilter DWORD ?
nMaxCustFilter DWORD ? ;rozmiar filtru w bajtach
nFilterIndex DWORD ? ;uchwyt kursora z funkcji LoadCursor
lpstrFile DWORD ? ;nazwa inicjowana pliku
nMaxFile DWORD ? ;maksymalny rozmiar pliku inicjowanego
lpstrFileTitle DWORD ? ;nazwa wybranego pliku i rozszerzenie
nMaxFileTitle DWORD ? ;rozmiar wybranego pliku
lpstrInitialDir DWORD ? ;algorytm wyboru początkowego katalogu
lpstrTitle DWORD ? ;ciąg tekstowy wyświetlany na pasku tytułowym okna
Flags DWORD ? ;dodatkowe informacje w postaci odpowiednich flag
nFileOffset WORD ? ;liczba bajtów na ścieżkę do pliku
nFileExtension WORD ? ;liczba bajtów na rozszerzenie pliku
lpstrDefExt DWORD ? ;domyślne rozszerzenie pliku
lCustData DWORD ? ;dane dla procedury haka
lpfnHook DWORD ? ;wskaźnik do procedury haka
lpTemplateName DWORD ? ;nazwa okna szablonu źródłowego
OPENFILENAMEA ENDS
OPENFILENAME EQU <OPENFILENAMEA>
Sekcja danych programu wygląda następująco:
.DATA?
OTWORZS OPENFILENAME <?>
HFILEO DD ? ;uchwyt pliku
ReadBytes DD ? ;ilość przeczytanych bajtów
WIELKOSCB DD ?
HINST DD ?
.DATA
NOWY DB 0
PA DB 'IDD_DLG1', 0 ;nazwa dialogu
OTFILTR DB 'Pliki tekstowe', 0, '*.txt', 0
DB 'Wszystkie pliki', 0, '*.*', 0
DB 0
BUFOR DB 60000 DUP (0)
OTCUSTFILTR DB 256 DUP (0)
OTPNAZWA DB 'tekst.txt', 0
DB 256 DUP (0)
OFTYTUL DB 256 DUP (0)
OINITDIR DB 'C:\', 0
ODLGTYTUL DB 'Otwórz plik', 0
Większość danych to dane inicjalizacyjne, które kopiujemy do odpowiednich pól struktury. Następnie przechodzimy do sekcji kodu programu:
.CODE
START:
PUSH 0
CALL GetModuleHandleA@4
MOV [HINST],EAX
PUSH 0
PUSH OFFSET WNDPROC
PUSH 0
PUSH OFFSET PA
PUSH [HINST]
CALL DialogBoxParamA@20
CMP EAX,-1
JNE KOL
KOL:
PUSH 0
CALL ExitProcess@4
;parametry:
;[EBP +14H] LPARAM
;[EBP +10H] WPARAM
;[EBP +0CH] MES
;[EBP +8] HWND
WNDPROC PROC
PUSH EBP
MOV EBP,ESP
PUSH EBX
PUSH ESI
PUSH EDI
CMP DWORD PTR [EBP + 0CH],WM_CLOSE;komunikat związany ze zniszczeniem okna
JNE OMIN
PUSH 0
PUSH DWORD PTR [EBP+ 08H]
CALL EndDialog@8
JMP FINISH
OMIN:
CMP DWORD PTR [EBP + 0CH],WM_COMMAND;komunikat związany z kontrolkami
JNE OMINCOMMAND
Początek to standardowe uruchomienie programu korzystającego z okna dialogowego i zdefiniowanie procedury. W sekcji obsługi zdarzeń kontrolek definiujemy reakcje na poszczególne opcje w menu, które utworzymy w edytorze zasobów:
CMP WORD PTR [EBP + 10H],ID_NOWY ;wybranie opcji 'Nowy'
JNE OMINNOWY
PUSH OFFSET NOWY
PUSH IDC_EDT1
PUSH DWORD PTR [EBP+ 08H]
CALL SetDlgItemTextA@12
OMINNOWY:
Funkcja SetDlgItemTextA@12 za pomocą ustawienia bufora w kontrolce nie zawierającego żadnych znaków, powoduje usunięcie wpisanych wcześniej. Dzięki temu mamy utworzony nowy plik do edycji. Następnie sprawdzamy czy użytkownik nie wybrał opcji 'Otwórz'. Jeśli tak inicjalizujemy odpowiednie pola struktury danymi, które też wcześniej już zdefiniowaliśmy:
CMP WORD PTR [EBP + 10H],ID_OTWORZ ;wybranie opcji 'Otworz'
JNE OMINOTWORZ
MOV OTWORZS.lStructSize,SIZEOF OTWORZS
PUSH DWORD PTR [EBP+ 08H]
POP OTWORZS.hwndOwner
PUSH HINST
POP OTWORZS.hInstance
MOV OTWORZS.lpstrFilter,OFFSET OTFILTR
MOV OTWORZS.lpstrCustomFilter,OFFSET OTCUSTFILTR
MOV OTWORZS.nMaxCustFilter,SIZEOF OTCUSTFILTR
MOV OTWORZS.nFilterIndex,0
MOV OTWORZS.lpstrFile,OFFSET OTPNAZWA
MOV OTWORZS.nMaxFile,256
MOV OTWORZS.lpstrFileTitle,OFFSET OFTYTUL
MOV OTWORZS.nMaxFileTitle,SIZEOF OFTYTUL
MOV OTWORZS.lpstrInitialDir,OFFSET OINITDIR
MOV OTWORZS.lpstrTitle,OFFSET ODLGTYTUL
MOV OTWORZS.Flags,OFN_ENABLESIZING OR \
OFN_EXPLORER OR \
OFN_FORCESHOWHIDDEN OR \
OFN_PATHMUSTEXIST OR \
OFN_OVERWRITEPROMPT OR \
OFN_HIDEREADONLY OR \
OFN_FILEMUSTEXIST OR \
OFN_NODEREFERENCELINKS
MOV OTWORZS.nFileOffset,0
MOV OTWORZS.lpfnHook,0
MOV OTWORZS.lpTemplateName,0
Po wypełnieniu struktury OPENFILENAME. Podajemy ją jako parametr do funkcji GetOpenFileNameA@4 . Wywołując tym samym okno dialogowe w celu otwarcia pliku. Jeżeli funkcja zwróci zero nie otwieramy pliku. W przeciwnym wypadku korzystamy już ze znanych nam funkcji: CreateFileA@28 - w celu otwarcia pliku i ReadFile@20 -odczytania jego zawartości:
PUSH OFFSET OTWORZS
CALL GetOpenFileNameA@4
CMP EAX ,0
JE IST
PUSH 0
PUSH 0
PUSH OPEN_EXISTING
PUSH 0
PUSH 0
PUSH GENERIC_READ
PUSH OFFSET OTPNAZWA
CALL CreateFileA@28
MOV HFILEO,EAX
JE NIEODCZYTUJ
PUSH 0
PUSH OFFSET ReadBytes
PUSH SIZEOF BUFOR
PUSH OFFSET BUFOR
PUSH HFILEO
CALL ReadFile@20
CMP EAX ,0
JE NIEODCZYTUJ
PUSH OFFSET BUFOR
PUSH IDC_EDT1
PUSH DWORD PTR [EBP+ 08H]
CALL SetDlgItemTextA@12
NIEODCZYTUJ:
PUSH HFILEO
CALL CloseHandle@4
IST:
OMINOTWORZ:
Następnie wyświetlamy zawartość pliku za pomocą SetDlgItemTextA@12 i zamykamy plik funkcją CloseHandle@4. Końcowa część kodu programu przedstawia się następująco:
CMP WORD PTR [EBP + 10H],ID_ZAKONCZ ;wybranie opcji 'Zakończ'
JNE OMINZAKONCZ
PUSH 0
PUSH 0
PUSH WM_CLOSE
PUSH DWORD PTR [EBP+ 08H]
CALL SendMessageA@16
OMINZAKONCZ:
OMINCOMMAND:
FINISH:
MOV EAX,0
POP EDI
POP ESI
POP EBX
POP EBP
RET
WNDPROC ENDP
END Start
Uruchomiony program przedstawiony jest na Rys.2.
Całość programu przedstawiona jest na poniższym listingu:
.586 ;typ instrukcji procesora
.MODEL
FLAT,STDCALL
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\Comdlg32.lib
ID_NOWY EQU 1010
ID_OTWORZ EQU 1020
ID_ZAKONCZ EQU 1050
IDC_EDT1 EQU 101
OPEN_EXISTING EQU 3
GENERIC_READ EQU 80000000h
GENERIC_WRITE EQU 40000000h
WM_SETICON EQU 80h
WM_COMMAND EQU 111h
WM_SETTEXT EQU 0Ch
WM_GETTEXT EQU 0Dh
WM_CLOSE EQU 10h
WM_INITDIALOG EQU 110h
OFN_ENABLESIZING EQU 00800000h
OFN_FORCESHOWHIDDEN EQU 10000000h
OFN_EXPLORER EQU 00080000h
OFN_PATHMUSTEXIST EQU 00000800h
OFN_OVERWRITEPROMPT EQU 00000002h
OFN_NODEREFERENCELINKS EQU 00100000h
OFN_FILEMUSTEXIST EQU 00001000h
OFN_HIDEREADONLY EQU 00000004h
EXTERN lstrlenA@4:NEAR
EXTERN CreateFileA@28:NEAR
EXTERN SendMessageA@16:NEAR
EXTERN SendDlgItemMessageA@20:NEAR
EXTERN EndDialog@8:NEAR
EXTERN GetModuleHandleA@4:NEAR
EXTERN DialogBoxParamA@20:NEAR
EXTERN SetDlgItemTextA@12:NEAR
EXTERN GetOpenFileNameA@4:NEAR
EXTERN CloseHandle@4:NEAR
EXTERN ReadFile@20:NEAR
EXTERN ExitProcess@4:NEAR
OPENFILENAMEA STRUC
lStructSize DWORD ? ;długość struktury w bajtach
hwndOwner DWORD ? ;uchwyt do okna które posiada okno dialogowe
hInstance DWORD ? ;uchwyt do modułu który zawiera szablon okna
lpstrFilter DWORD ? ;filtry
lpstrCustomFilter DWORD ?
nMaxCustFilter DWORD ? ;rozmiar filtru w bajtach
nFilterIndex DWORD ? ;uchwyt kursora z funkcji LoadCursor
lpstrFile DWORD ? ;nazwa inicjowana pliku
nMaxFile DWORD ? ;maksymalny rozmiar pliku inicjowanego
lpstrFileTitle DWORD ? ;nazwa wybranego pliku i rozszerzenie
nMaxFileTitle DWORD ? ;rozmiar wybranego pliku
lpstrInitialDir DWORD ? ;algorytm wybory początkowego katalogu
lpstrTitle DWORD ? ;ciąg tekstowy wyświetlany na pasku tytułowym okna
Flags DWORD ? ;dodatkowe informacje w postaci odpowiednich flag
nFileOffset WORD ? ;liczba bajtów na ścieżkę do pliku
nFileExtension WORD ? ;liczba bajtów na rozszerzenie pliku
lpstrDefExt DWORD ? ;domyślne rozszerzenie pliku
lCustData DWORD ? ;dane dla procedury haka
lpfnHook DWORD ? ;wskaźnik do procedury haka
lpTemplateName DWORD ? ;nazwa okna szablonu źródłowego
OPENFILENAMEA ENDS
OPENFILENAME EQU <OPENFILENAMEA>
.DATA?
OTWORZS OPENFILENAME <?>
HFILEO DD ? ;uchwyt pliku
ReadBytes DD ? ;ilość przeczytanych bajtów
WIELKOSCB DD ?
HINST DD ?
.DATA
NOWY DB 0
PA DB 'IDD_DLG1', 0 ;nazwa dialogu
OTFILTR DB 'Pliki tekstowe', 0, '*.txt', 0
DB 'Wszystkie pliki', 0, '*.*', 0
DB 0
BUFOR DB 60000 DUP (0)
OTCUSTFILTR DB 256 DUP (0)
OTPNAZWA DB 'tekst.txt', 0
DB 256 DUP (0)
OFTYTUL DB 256 DUP (0)
OINITDIR DB 'C:\', 0
ODLGTYTUL DB 'Otwórz plik', 0
.CODE
START:
PUSH 0
CALL GetModuleHandleA@4
MOV [HINST],EAX
PUSH 0
PUSH OFFSET WNDPROC
PUSH 0
PUSH OFFSET PA
PUSH [HINST]
CALL DialogBoxParamA@20
CMP EAX,-1
JNE KOL
KOL:
PUSH 0
CALL ExitProcess@4
;parametry:
;[EBP +14H] LPARAM
;[EBP +10H] WPARAM
;[EBP +0CH] MES
;[EBP +8] HWND
WNDPROC PROC
PUSH EBP
MOV EBP,ESP
PUSH EBX
PUSH ESI
PUSH EDI
CMP DWORD PTR [EBP + 0CH],WM_CLOSE;komunikat związany ze zniszczeniem okna
JNE OMIN
PUSH 0
PUSH DWORD PTR [EBP+ 08H]
CALL EndDialog@8
JMP FINISH
OMIN:
CMP DWORD PTR [EBP + 0CH],WM_COMMAND;komunikat związany z kontrolkami
JNE OMINCOMMAND
CMP WORD PTR [EBP + 10H],ID_NOWY ;wybranie opcji 'Nowy'
JNE OMINNOWY
PUSH OFFSET NOWY
PUSH IDC_EDT1
PUSH DWORD PTR [EBP+ 08H]
CALL SetDlgItemTextA@12
OMINNOWY:
CMP WORD PTR [EBP + 10H],ID_OTWORZ ;wybranie opcji 'Otworz'
JNE OMINOTWORZ
MOV OTWORZS.lStructSize,SIZEOF OTWORZS
PUSH DWORD PTR [EBP+ 08H]
POP OTWORZS.hwndOwner
PUSH HINST
POP OTWORZS.hInstance
MOV OTWORZS.lpstrFilter,OFFSET OTFILTR
MOV OTWORZS.lpstrCustomFilter,OFFSET OTCUSTFILTR
MOV OTWORZS.nMaxCustFilter,SIZEOF OTCUSTFILTR
MOV OTWORZS.nFilterIndex,0
MOV OTWORZS.lpstrFile,OFFSET OTPNAZWA
MOV OTWORZS.nMaxFile,256
MOV OTWORZS.lpstrFileTitle,OFFSET OFTYTUL
MOV OTWORZS.nMaxFileTitle,SIZEOF OFTYTUL
MOV OTWORZS.lpstrInitialDir,OFFSET OINITDIR
MOV OTWORZS.lpstrTitle,OFFSET ODLGTYTUL
MOV OTWORZS.Flags,OFN_ENABLESIZING OR \
OFN_EXPLORER OR \
OFN_FORCESHOWHIDDEN OR \
OFN_PATHMUSTEXIST OR \
OFN_OVERWRITEPROMPT OR \
OFN_HIDEREADONLY OR \
OFN_FILEMUSTEXIST OR \
OFN_NODEREFERENCELINKS
MOV OTWORZS.nFileOffset,0
MOV OTWORZS.lpfnHook,0
MOV OTWORZS.lpTemplateName,0
PUSH OFFSET OTWORZS
CALL GetOpenFileNameA@4
CMP EAX ,0
JE IST
PUSH 0
PUSH 0
PUSH OPEN_EXISTING
PUSH 0
PUSH 0
PUSH GENERIC_READ
PUSH OFFSET OTPNAZWA
CALL CreateFileA@28
MOV HFILEO,EAX
JE NIEODCZYTUJ
PUSH 0
PUSH OFFSET ReadBytes
PUSH SIZEOF BUFOR
PUSH OFFSET BUFOR
PUSH HFILEO
CALL ReadFile@20
CMP EAX ,0
JE NIEODCZYTUJ
PUSH OFFSET BUFOR
PUSH IDC_EDT1
PUSH DWORD PTR [EBP+ 08H]
CALL SetDlgItemTextA@12
NIEODCZYTUJ:
PUSH HFILEO
CALL CloseHandle@4
IST:
OMINOTWORZ:
CMP WORD PTR [EBP + 10H],ID_ZAKONCZ ;wybranie opcji 'Zakończ'
JNE OMINZAKONCZ
PUSH 0
PUSH 0
PUSH WM_CLOSE
PUSH DWORD PTR [EBP+ 08H]
CALL SendMessageA@16
OMINZAKONCZ:
OMINCOMMAND:
FINISH:
MOV EAX,0
POP EDI
POP ESI
POP EBX
POP EBP
RET
WNDPROC ENDP
END Start
Plik zasobów programu wygląda następująco:
#include "..\include\resource.h"
#define IDC_EDT1 101
#define ID_NOWY 1010
#define ID_OTWORZ 1020
#define ID_ZAKONCZ 1050
IDD_DLG1 DIALOGEX 10,10,297,190
CAPTION "Edytor"
FONT 8,"MS Sans Serif", 0, 0, 0
MENU 1000
STYLE WS_POPUP|WS_VISIBLE|WS_OVERLAPPEDWINDOW|DS_CENTER|DS_SETFOREGROUND|DS_3DLOOK
EXSTYLE WS_EX_STATICEDGE
BEGIN
CONTROL "",IDC_EDT1,"Edit",WS_VISIBLE|WS_VSCROLL|WS_HSCROLL|ES_WANTRETURN|ES_NOHIDESEL
|ES_AUTOHSCROLL|ES_AUTOVSCROLL|ES_MULTILINE,15,12,256,162,WS_EX_CLIENTEDGE
END
1000 MENU
BEGIN
POPUP "Plik"
BEGIN
MENUITEM "Plik",ID_NOWY
MENUITEM "Otwórz",ID_OTWORZ
MENUITEM SEPARATOR
MENUITEM "Zakończ",ID_ZAKONCZ
END
END
Do przykładu dodać również opcję zapisu do pliku z wyświetlaniem okna dialogowego.