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

Stronę tą wyświetlono już: 318 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:

Listing 1
  1. #include <locale.h>

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

Listing 2
  1. 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:

Listing 3
  1. LRESULT CALLBACK hwndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){
  2. static RECT wndSize;
  3. static HWND hedit; // kontrolka edit do przechowywania tekstu
  4. static OPENFILENAME ofn; // struktura związana z obsługą okna otwierania pliku i zapisywania do pliku
  5. static TCHAR FileName[MAX_PATH]; // zmienna, która przechowywać będzie nazwę pliku wraz z ścieżką
  6. 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:

Listing 4
  1. TitleName[0] = 0; // ustaiwam, żeby rzadnych śmieci nie było
  2. FileName[0] = 0; // to samo co poprzednio
  3. ZeroMemory(&ofn,sizeof(ofn)); // zeruję pola struktury
  4. ofn.lStructSize = sizeof(OPENFILENAME); // zapisuję rozmiar struktury
  5. ofn.hwndOwner = hWnd; // okno rodzić dla okna otwierania lub zapisywania
  6. ofn.hInstance = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); // uchwyt instancji
  7. ofn.lpstrFilter = L"Plik tekstowy (*.txt)*.txt"; // filtr dostępnych rozszeżeń plików
  8. ofn.lpstrFile = FileName; // wskaźnik do bufora nazwy pliku
  9. ofn.nMaxFile = MAX_PATH; // rozmiar tego bufora
  10. ofn.lpstrFileTitle = TitleName; // wskaźnik do bufora ścieżki do pliku
  11. ofn.nMaxFileTitle = MAX_PATH; // rozmiar tego bufora
  12. ofn.nFileExtension = 0;
  13. ofn.lpstrDefExt = L"";
  14. 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:

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

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:

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

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:

  • GetOpenFileName - wyświetla okno dialogowe Otwórz, funkcja ta związana jest ściśle z strukturą OPENFILENAME;
  • GetSaveFileName - wyświetla okno dialogowe Zapisz, funkcja ta związana jest ściśle z strukturą OPENFILENAME;
  • CloseHande - zwalnia uchwyt w kodzie programu funkcja została użyta do zamknięcia uchwytu pliku pozyskanego funkcją CreateFile;
  • CreateFile - funkcja wykorzystana do utworzenia pliku i pozyskania jego uchwytu. Z tą funkcją powiązane są funkcje ReadFile, WriteFile oraz CloseHandle;
  • GetWindowLong - umożliwia pozyskiwanie informacji związanych z danym uchwytem okna. W kodzie użyto tej funkcji do pozyskania uchwytu instancji programu typu HINSTANCE;
  • MultiByteToWideChar - zamiana tekstu zapisanego z standardowym kodowaniem ustawionym w systemie na utf-8;
  • ReadFile - czytanie danych z pliku, związane z funkcją CreateFile;
  • setlocale - funkcja ustawia pewne parametry związane z lokalnymi ustawieniami wyświetlania i przetwarzania tekstu;
  • SetWindwText - funkcja ustawia tekst wyświetlany w danym oknie programu. W kodzie funkcji tej użyto do ustawienia tekstu zawartego w kontrolce hedit;
  • WideCharToMultiByte - zamiana z utf-8 na standardowe kodowanie Windowsa;
  • WriteFile - zapisywanie danych do pliku, związane z funkcją CreateFile;

Wykorzystane w kodzie struktury:

  • OPENFILENAME - struktura związana z wywoływaniem okien Otwórz i Zapisz za pomocą funkcji GetOpenFileName oraz GetSaveFileName.

Komentarze