LEKCJA 39: KORZYSTAMY ZE STANDARDOWYCH ZASOBÓW Windows. ________________________________________________________________ W trakcie tej lekcji dowiesz się, jak korzystać z zasobów Windows bez potrzeby wnikania w wiele szczególów technicznych interfejsu aplikacji - Windows API. ________________________________________________________________ Ponieważ nasze programy mogą korzystać ze standardowych zasobów Windows, na początku będziemy posługiwać się okienkami standardowymi. Począwszy od aplikacji WIN3.EXE "rozszerzymy ofertę" do dwu podstawowych typów: * Standardowe główne okno programu (Default main window). To takie właśnie okno, jakie dostały nasze pierwsze aplikacje WIN1.EXE. * Okienkiem dialogowym (Dialog box), a dokładniej najprostszym rodzajem okienek dialogowych - tzw. okienkami komunikatów - Message Box. Zastosowanie okienka dialogowego pozwoli nam na wprowadzenie do akcji klawiszy (buttons). ________________________________________________________________ UWAGA: Niestety nie wszystkie tradycyjne funkcje typu printf(), scanf(), gets() itp. zostały zaimplementowane dla Windows! Pisząc własne programy możesz przekonać się o tym dzięki opisowi funkcji w Help. Funkcję należy odszukać w Help | Index. Oprócz przykładu zastosowania znajdziesz tam tabelkę typu: DOS Unix Windows ANSI C C++ Only cscanf Yes fscanf Yes Yes Yes Yes scanf Yes Yes Yes sscanf Yes Yes Yes Yes [Yes] oznacza "zaimplementowana". Dlatego właśnie w dalszych programach przykładowych dla wersji 3.0 należy np. stosować np. makro getchar() zamiast tradycyjnego getch() zaimplementowane dla Windows już w wersji BC++ 3.0. ________________________________________________________________ Dla przykładu spróbujmy skompilować i uruchomić w środowisku Windows jeden z wcześniejszych programów - tabliczkę mnożenia. Zwróć uwagę na dołączony dodatkowy plik WINDOWS.H i nowy typ wskaźnika. Zamiast zwykłego char *p ... LPSTR p ... LPSTR - to Long Pointer to STRing - daleki wskaźnik do łańcucha tekstowego. Jest to jeden z "ulubionych" typów Windows. /* WIN2.CPP: */ /* - Tablica dwuwymiarowa - Wskazniki do elementów tablicy */ #include #include #include int T[10][10], *pT, i, j, k; char spacja = ' '; LPSTR p1 = " TABLICZKA MNOZENIA (ineksy)\n"; LPSTR p2 = " Inicjujemy i INKREMENTUJEMY wskaznik:\n"; LPSTR p3 = "... nacisnij cokolwiek (koniec)..."; void main() { printf(p1); for (i = 0; i < 10; i++) { for (j = 0; j < 10; j++) { T[i][j] = (i + 1)*(j + 1); if (T[i][j] < 10) cout << T[i][j] << spacja << spacja; else cout << T[i][j] << spacja; } cout << '\n'; } printf(p2); pT = &T[0][0]; for (k = 0; k < 10*10; k++) { if (*(pT+k) < 10) cout << *(pT + k) << spacja << spacja; else cout << *(pT + k) << spacja; if ((k + 1)%10 == 0) cout << '\n'; } printf(p3); getchar(); } Wybraliśmy dla aplikacji standardowe główne okno (Main Window), ponieważ istnieje potrzeba pionowego przewijania okna w celu przejrzenia pełnego wydruku obu tablic. [???] Dlaczego ten tekst jest nierówny??? ________________________________________________________________ Niestety, znaki w trybie graficznym Windows nie mają stałej szerokości (jak było to w trybie tekstowym DOS). Niektóre aplikacje przeniesione ze środowiska DOS będą sprawiać kłopoty. ________________________________________________________________ APLIKACJE DWUPOZIOMOWE. Zastosujemy teraz najprostszy typ okienka dialogowego - okienko kamunikatów (Message Box), nasze następne aplikacje mogą być już nie jedno- a dwupoziomowe. Typowe postępowanie okienkowych aplikacji bywa takie: * program wyświetla w głównym oknie to, co ma do powiedzenia; * aby zadawać pytania stosuje okienka dialogowe, bądź okienka komunikatów; * funkcja okienkowa (u nas MessageBox()) zwraca do programu decyzję użytkownika; * program główny analizuje odpowiedź i podejmuje w głównym oknie stosowne działania. Prześledźmy ewolucję powstającej w taki sposób aplikacji. STADIUM 1. Tekst w głównym oknie. Zaczniemy tworzenie naszej aplikacji tak: /* WINR1.CPP: */ /* Stadium 1: Dwa okienka w jednym programie */ # include # include char *p1 = "Teraz dziala \n funkcja \n MessageBox()"; char *p2 = "START"; int wynik; void main() { printf(" Start: Piszemy w glownym oknie \n"); printf(" ...nacisnij cosik..."); getchar(); MessageBox(0, p1, p2, 0); printf("\n\n\n Hello World dla WINDOWS!"); printf("\n\t...dowolny klawisz... "); getchar(); } Moglibyśmy zrezygnować z metod typowych dla aplikacji DOSowskich i zatrzymania (i zapytania) makrem getchar() (odpowiednik getch() dla Windows). To działanie możemy z powodzeniem powierzyć funkcji okienkowej MessageBox(). Funkcja MessageBox() pobiera cztery parametry: int Message Box(hwndParent, lpszText, lpszTitle, Style) HWND hwndParent - identyfikator macieżystego okna (głównego okna aplikacji). Ponieważ nie wiemy póki co pod jakim numerem (identyfikatorem) Windows zarejestrują naszą aplikację - wpisujemy 0 LPCSTR lpszText - daleki wskaźnik do łańcucha tekstowego wewnątrz okienka. LPCSTR lpszTitle - daleki wskażnik do łańcucha tekstowego - tytułu okienka komunikatu. UINT Style - UINT = unsigned int; numer określający zawartość okienka. int Return Value - identyfikator klawisza, który wybrał użytkownik w okienku komunikatu. [!!!] UWAGA ________________________________________________________________ Deklaracje wskaźników do tekstów powinny wyglądać tak: LPCSTR p1 = "Napis1", p2 = "Tekst2"; ale C++ może samodzielnie dokonać forsowania typów i zamienić typ char* na typ LPCSTR (lub LPSTR). ________________________________________________________________ /* WINR2.CPP: */ /* Stadium 2: Dwa okienka ze zmienną zawarością */ # include # include char *p2, *p1 = "Dopisywanie:"; char napisy[4][20] = { "Borland ", "C++ ", "dla ", "Windows" }; void main() { printf("\n\n\n Hello World dla WINDOWS!"); printf("\n AUTOR: ..................."); for( int i = 0; i < 4; i++) { p2 = &napisy[i][0]; MessageBox(0, p2, p1, MB_OK); printf("\n %s", napisy[i]); } MessageBox(0, "I to juz \n wszystko...", "KONIEC", MB_OK); } W tym stadium stosujemy: - główne okno aplikacji - dwa okienka komunikatów (Dopisywanie i KONIEC) - jeden klawisz - [OK] Łańcuchy tekstowe przeznaczone do pola tekstowego okienka pobierane są z tablicy napisy[4][20] (cztery napisy po max. 20 znaków) przy pomocy wskaźnika p2. MB_OK to predefiniowana stała (Message Box OK - key identifier - identyfikator klawisza [OK] dla okienek komunikatów). /* WINR3.CPP: */ /* Stadium 3: Dwa okienka sterują pętlą */ # include # include char *p2, *p1 = "Dopisywanie:"; char napisy[4][20] = { "Borland ", "C++ ", "dla ", "Windows" }; void main() { printf("\n\n\n Hello World dla WINDOWS!"); printf("\n AUTOR: ..................."); for( int i = 0; i < 4; i++) { p2 = &napisy[i][0]; if( MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL) == IDOK) printf("\n %s", napisy[i]); else printf("\n ...?"); } MessageBox(0, "I to juz \n wszystko...", "KONIEC", MB_OK); } W tym stadium stosujemy: - główne okno aplikacji - dwa okienka komunikatów (Dopisywanie i KONIEC) - dwa klawisze - [OK] i [Anuluj] (OK/Cancel) - jedną ikonę [STOP] Zwróć uwagę, że tym razem sprawdzamy, który klawisz wybrał użytkownik w okienku. Odbywa się to tak: if( MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL) == IDOK) IDOK jest predefiniowaną stałą - kodem klawisza [OK] (ang. OK-key IDentifier - identyfikator klawisza OK). Identyfikatory różnych zasobów Windows są liczbami całkowitymi. Jeśli jesteś dociekliwy Czytelniku, możesz sprawdzić - jaki numer ma klawisz [OK] rozbudowując tekst aplikacji np. tak: int Numer; ... Numer = MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL); printf("\nKlawisz [OK] ma numer: %d", Numer); if(Numer == IDOK) ... Zwróć uwagę na sposób wykorzystania zasobów w funkcji MessageBox(). Identyfikatory zasobów, które chcemy umieścić w okienku są wpisywane jako ostatni czwarty argument funkcji i mogą być sumowane przy pomocy znaku | (ang. ORing), np.: MessageBox(0,..,.., MB_ICONSTOP | MB_OKCANCEL); oznacza umieszczenie ikony STOP i klawiszy [OK] i [Anuluj]. Kod zwracany przez funkcję może być wykorzystywany we wszelkich konstrukcjach warunkowych (switch, case, for, while, if-else, itp.). /* WINR4.CPP: */ /* Stadium 4: Okienka sterują 2 pętlami, przybywa zasobów. */ # include # include char *p2, *p1 = "Dopisywanie:"; char *p3 = "I to by bylo na tyle...\n Konczymy ???"; char *p4 = "UWAGA: KONIEC ?"; char napisy[5][20] = { "Borland ", "C++ ", "dla ", "Microsoft", "Windows" }; main() { printf("\n\n\n Grafoman dla WINDOWS!"); printf("\n AUTOR: (jak wyzej)"); puts("_____________________________\n"); do { for( int i = 0; i < 5; i++) { p2 = &napisy[i][0]; if( MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL) == IDOK) printf("\n %s", napisy[i]); else printf("\n ...?"); } } while (MessageBox(0,p3,p4,MB_ICONQUESTION | MB_OKCANCEL)==IDCANCEL); return 0; } W tym stadium stosujemy: - główne okno aplikacji - dwa okienka komunikatów (Dopisywanie i KONIEC) - dwa klawisze - [OK] i [Anuluj] (OK/Cancel) - dwie ikonki [STOP] i [PYTAJNIK] Tekst jest przewijany w głównym oknie programu i po zakończeniu roboczej części programu i przejściu w stan nieaktywny (inactive) możesz przy pomocy paska przewijania pionowego obejrzeć napisy - historię Twoich zmagań z programem. Zwróć uwagę, że pojemność głównego okna jest ograniczona. Jeśli napisów będzie zbyt dużo, tekst przewinięty poza okno może ulegać obcięciu (ang clip on). Zwróć również uwagę na naprzemienne przekazywanie aktywności (focus) pomiędzy oknami aplikacji: MainWindow <-----> MessageBox Warto w tym momencie zwrócić uwagę na kilka typowych dla okienkowych aplikacji mechanizmów. * Jeśli naciśniemy klawisz na klawiaturze, bądź klawisz myszki, obsługa takiego zdarzenia może następować na dwa sposoby. Najpierw Windows pobierają kod klawisza i dokonują "kolejkowania" (podobnie jak DOS-owski bufor klawiatury). Następnie przekazują kod klawisza aplikacji do obsługi. Jeśli aplikacja czeka na klawisz i potrafi obsłużyć takie zdarzenie (np. funkcja MessageBox(), bądź makro getchar(), czy operator cin >> w programie głównym), obsługa zdarzenia zostaje zakończona. Jeśli aplikacja nie potrafi obsłużyć zdarzenia - obsługa przekazywaba jest stadardowym funkcjom obsługi (Event Handler) Windows. * Kiedy na ekranie pojawia się okienko dialogowe (tu: komunikatów) zostaje mu przekazany tzw. focus - czyli aktywność. Naciśnięcie [Entera] spowoduje zadziałanie tego klawisza w okienku, który właśnie ten focus otrzymał (tu zwykle pierwszego z lewej). * jeśli naciśniemy klawisz specjalny, którego obsługę w sposób standardowy powinny załatwiać Windows - obsługa takiego zdarzenia zostaje przekazana domyślnej funkcji Windows (ang. Default Event Handler). Tak jest w przypadku klawiszy ze strzałkami (przewijanie w oknie), [Tab], [Alt]+[F4], itp. /* WINR5.CPP: */ /* Stadium 5: Zmiana wielkości i nazwy okienka. */ # include # include # include char tytul[80] = "Dopisywanie: "; char *p0, *p2; char *p1 = "UWAGA: Ponawianie proby \n oznacza: WYDRUKUJE I ZAPYTAM"; char *p3 = "I to by bylo na tyle...\n Konczymy ???"; char *p4 = "UWAGA: KONIEC ?"; char napisy[5][20] = { "Borland ", "C++ ", "dla ", "Microsoft", "Windows" }; main() { cout << "\n\n\n Grafoman dla WINDOWS!"; cout << "\n AUTOR: (jak wyzej)"; cout << "\n_____________________________\n"; p0 = &tytul[0]; do { for( int i = 0; i < 5; i++) { p2 = &napisy[i][0]; strcat(p0, p2); int decyzja = MessageBox(0, p1, p0, MB_ICONHAND | MB_ABORTRETRYIGNORE); if (decyzja == IDABORT) break; else if (decyzja == IDRETRY) { cout << "\n " << napisy[i]; i--; } else if (decyzja == IDIGNORE) { cout << "\n ...?"; continue; } } } while (MessageBox(0, p3, p4, MB_ICONQUESTION | MB_OKCANCEL) == IDCANCEL); return 0; } W Stadium 5 zmienia się (rośnie) nagłówek okienka komunikatów. UWAGA: Po wyjściu za ekran nastąpi załamanie programu. Program nie zawiera handlera obsługującego przekroczenia dopuszczalnej długości. Rysunek poniżej przedstawia różne stadia działania opisanych powyżej aplikacji. Jeśli postanowisz napisać praktyczną aplikację dla Windows, jest to zwykle program znacznie dłuższy, w którym trzeba przemyśleć sposób organizacji pętli, wyrażeń warunkowych i sposoby wykorzystania zasobów. [!!!]UWAGA ________________________________________________________________ Okienka mogą być "modalne" i "nie-modlane". Okienko "modalne" to takie okienko, które do momentu jego zamknięcia uniemożliwia użytkownikowi działania w innych oknach (tej samej aplikacji, bądź innych aplikacji) znajdujących się na ekranie. W ramach parametru Styl możesz stosować predefiniowane stałe MB_APPMODAL MB_TASKMODAL itp. określające stopień "modalności" okienka (na poziomie zadania - TASK, aplikacji - APP, itp.). ________________________________________________________________