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:
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.