Wywoływanie i obsługa standardowych okien Otwórz i Zapisz oraz zapis do pliku

Autor podstrony: Krzysztof Zajączkowski

Stronę tą wyświetlono już: 2924 razy

Wstęp

Na poprzedniej stronie omawiany został projekt, w którym dodane zostało menu, ikonka, kursor oraz akceleratory oraz dodana została pierwsza kontrolka typu EDIT. Był to początek programu o wdzięcznej nazwie myNotepad i wszystko byłoby pięknie, gdyby nie fakt, że program ten wcale nie wczytuje tekstu z pliku ani też go nie zapisuje. Na tej stronie będę kontynuował poprzednio zaczęty projekt omawiając tym samym sposób wykorzystania dwóch z pośród rodziny okien standardowych systemu Windows. Te standardowe okna istnieją dlatego, że bardzo często różne programy korzystają z tej samej lub podobnej funkcjonalności a więc aby nie powielać ciągle tego samego kodu przez różne programy część obowiązku za dostarczenie pewnych narzędzi spoczywa na systemie. Do takich standardowych okien należą okno dialogowe Otwórz oraz Zapisz, które zasadniczo wykorzystuje większość programów do wczytywania lub zapisywania jakiegoś pliku z dysku twardego komputera.

W WinApi w celu wywołania okna dialogowego Otwórz używa się funkcji GetOpenFileName, natomiast do wywołania okna Zapisz wykorzystuje się funkcję GetSaveFileName. Obie wspomniane wcześniej funkcje przyjmują tylko jeden argument, którym jest wskaźnik do prawidłowo wypełnionej struktury typu OPENFILENAME. Jeżeli struktura ta została prawidłowo wypełniona, wcześniej wymienione funkcje wywołają okna dialogowe.

Modyfikacja kodu programu myNotepad

Czas najwyższy zakasać rękawy i czym prędzej dodać do programu nowe elementy, ale zanim to najpierw na samym początku projektu należy załączyć plik nagłówkowy locale.h:

#include <locale.h>

po czym niezwłocznie w ciele funkcji WinMain na samym początku dodać następujące wywołanie funkcji setlocale:

setlocale(LC_CTYPE, "");

Taki zabieg jest konieczny, aby w oknach dialogowych poprawnie wczytywane były polskie znaki i aby program poprawnie odczytywał polskie znaki w plikach, choć możliwe jest, że skoro wykorzystują w projekcie tryb z unicodem to obsługa polskich znaków powinna być poprawna.

W ciele funkcji hwndProc na samym jej początku konieczne będzie dodanie kilku nowych zmiennych statycznych:

LRESULT CALLBACK hwndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){ static RECT wndSize; static HWND hedit; // kontrolka edit do przechowywania tekstu static OPENFILENAME ofn; // struktura związana z obsługą okna otwierania pliku i zapisywania do pliku static TCHAR FileName[MAX_PATH]; // zmienna, która przechowywać będzie nazwę pliku wraz z ścieżką static TCHAR TitleName[MAX_PATH]; // zmienna, która będzie przechowywała nazwę pliku

Jak widać jest tu instancja struktury OPENFILENAME, oraz dwa łańcuchy znaków typu TCHAR*. Opis tych dodatkowych zmiennych zamieściłem w komentarzach powyższego kodu. W komunikacie WM_COMMAND należy dodać następujący kod programu:

TitleName[0] = 0; // ustaiwam, żeby rzadnych śmieci nie było FileName[0] = 0; // to samo co poprzednio ZeroMemory(&ofn,sizeof(ofn)); // zeruję pola struktury ofn.lStructSize = sizeof(OPENFILENAME); // zapisuję rozmiar struktury ofn.hwndOwner = hWnd; // okno rodzić dla okna otwierania lub zapisywania ofn.hInstance = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); // uchwyt instancji ofn.lpstrFilter = L"Plik tekstowy (*.txt)*.txt"; // filtr dostępnych rozszeżeń plików ofn.lpstrFile = FileName; // wskaźnik do bufora nazwy pliku ofn.nMaxFile = MAX_PATH; // rozmiar tego bufora ofn.lpstrFileTitle = TitleName; // wskaźnik do bufora ścieżki do pliku ofn.nMaxFileTitle = MAX_PATH; // rozmiar tego bufora ofn.nFileExtension = 0; ofn.lpstrDefExt = L""; ofn.Flags = OFN_PATHMUSTEXIST;// | OFN_FILEMUSTEXIST; // flagi (ściażka musi istnieć

I tutaj wszystko zostało omówione w komentarzach kodu programu. Teraz w komunikacie WM_COMMAND w switch-u z identyfikatorem pozycji menu ID_PLIK_OTWORZ należy zastąpić dotychczas wstawiony tam MessageBox następującym kodem:

case ID_PLIK_OTWORZ: { if(GetOpenFileName(&ofn)){ // Jeżeli wywołanie okna Otwórz zakończy się powodzeniem i wybrany zostanie plik tekstowy do otworzenia to HANDLE hfile; // uchwyt pliku if(INVALID_HANDLE_VALUE == (hfile = CreateFile(FileName,GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL))){ // jeżeli otwarcie pliku się nie powiedzie MessageBox(hWnd, L"Coś nie tak z plikiem", L"Informacja",MB_OK); // to płacz, że się nie powiodło }else{ // a jak nie to UINT filesize = GetFileSize(hfile, NULL); // pobieraj rozmiar pliku PBYTE buffer = new BYTE[filesize + 2]; // odpowiedni bufor danych rezerwuję DWORD dwRead; // ta zmienna otrzyma informację o liczbie bajtów wczytanych do zmiennej bufora ReadFile(hfile, buffer, filesize, &dwRead, NULL); // wczytywanie danych z pliku CloseHandle(hfile); // zamykanie pliku buffer[filesize] = buffer[filesize + 1] = L''; // zerowanie nadmiarowych elementów wchar_t *text = new wchar_t[filesize + 1]; // tworzenie bufora na dane dla typu wchar_t MultiByteToWideChar(CP_ACP, 0, (LPCSTR)buffer, -1, text, filesize + 1); // zamiana z ASCII (z domyślnym kodowaniem systemowym) na UTF8 SetWindowText(hedit, text); // wczytywanie tekstu do okna hedit delete [] text; // zwalniam bufor danych delete [] buffer; // to samo co poprzednio } } }

W powyższym kodzie wykorzystana została funkcja MultiByteToWideChar a to z tego względu, że kontrolka EDIT wykorzystuje w projekcie tekst unicode, natomiast ja tutaj założyłem, że wczytywane dane są zapisane w standardowym kodowaniu używanym przez system użytkownika.

Podobnie wstawić należy kod dla identyfikatora pozycji w menu ID_PLIK_ZAPISZ:

case ID_PLIK_ZAPISZ: { if(GetSaveFileName(&ofn)){ // jeżeli wyświetlenie okna się powiedzie i użytkownik wybierze plik do zapisu to HANDLE hfile; // uchwyt pliku UINT textsize = GetWindowTextLength(hedit); // pobieram już zawczasu długość tekstu przechowywanego w oknie hedit if(INVALID_HANDLE_VALUE == (hfile = CreateFile(FileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL))){ // jak plik się nie wczyta to MessageBox(hWnd, L"Nie udało się utworzyć pliku",L"Informacja",MB_OK); // płacz, że się nie wczytał }else if(textsize){ // a jak nie i tekst zawarty w kontrolce jest dłuższy niż 0 to wchar_t *text = new wchar_t[textsize + 1]; // rezerwuj pamięć GetWindowText(hedit, text, textsize + 1); // pobieraj dane char *text2 = new char[textsize + 1]; // przygotuj bufor pamięci do konwersji z UTF-8 na ASCII w kodowaniu standardowym Windowsa WideCharToMultiByte(CP_ACP,WC_COMPOSITECHECK,text, textsize,text2,textsize,NULL, NULL); // zamiana z UTF-8 na ASCII DWORD byteorder = 0xFF; // potrzebne do funkcji WriteFile text2[textsize] = ''; // na końcu zero trzeba wstawić WriteFile(hfile, (LPVOID)text2, textsize+1,&byteorder,NULL); // zapis do pliku CloseHandle(hfile); // zamykanie pliku delete [] text2; // zwalnianie pamięci delete [] text; // i to samo } } }

W tej chwili już mamy jako - tako obsłużone wczytywanie danych z pliku i zapisywanie do pliku.

Spis nowych funkcji i struktur użytych w programie

Oto spis nowych funkcji:

Wykorzystane w kodzie struktury:

Propozycje książek