Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00066 008944 11061361 na godz. na dobę w sumie
Tworzenie aplikacji dla Windows. Od prostych programów do gier komputerowych - książka
Tworzenie aplikacji dla Windows. Od prostych programów do gier komputerowych - książka
Autor: Liczba stron: 456
Wydawca: Helion Język publikacji: polski
ISBN: 83-246-1881-3 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> inne - programowanie
Porównaj ceny (książka, ebook, audiobook).

Poznaj tajniki tworzenia aplikacji dla Windows

Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

Tworzenie aplikacji dla Windows. Od prostych programów do gier komputerowych Autor: Pawe³ Borkowski ISBN: 978-83-246-1881-1 Format: 158x235, stron: 456 Poznaj tajniki tworzenia aplikacji dla Windows • Jak okreœliæ po³o¿enie, rozmiar i styl okna? • Jak tworzyæ w¹tki aplikacji za pomoc¹ funkcji CreateThread? • Jak definiowaæ biblioteki? Dev-C++ to zintegrowane œrodowisko programistyczne, którego niew¹tpliwym atutem s¹ tzw. DevPaki, czyli rozszerzenia programu, pozwalaj¹ce korzystaæ z ró¿nych bibliotek, szablonów i narzêdzi. Œrodowisko Dev-C++ wspomaga tak¿e pracê nad nowym projektem Windows — gotowym kodem tworz¹cym okno z obs³ug¹ podstawowych komunikatów. Wszystko to sprawia, ¿e mamy do czynienia z wygodnym i funkcjonalnym œrodowiskiem, zarówno dla pocz¹tkuj¹cych, jak i zaawansowanych programistów. Z ksi¹¿ki „Tworzenie aplikacji dla Windows. Od prostych programów do gier komputerowych” mo¿e skorzystaæ ka¿dy, kto chce nauczyæ siê programowania: zarówno studenci kierunków informatycznych, jak i osoby, które nie maj¹ takiego przygotowania. Podrêcznik kolejno ods³ania poszczególne elementy wiedzy programistycznej — od najprostszych po najbardziej zaawansowane. Dowiesz siê wiêc, jak wprowadzaæ niewielkie zmiany w kodzie, jak projektowaæ aplikacje wielow¹tkowe i definiowaæ biblioteki, jak budowaæ du¿e, sk³adaj¹ce siê z kilku plików projekty, aby na koniec samodzielnie stworzyæ grê komputerow¹. • Instalacja œrodowiska Dev-C++ • Tworzenie narzêdzia pióro • Obs³uga map bitowych • Obs³uga komunikatów myszy i klawiatury • Obiekty steruj¹ce w oknie • Menu i plik zasobów • Projektowanie aplikacji wielow¹tkowych • Biblioteki statyczne i dynamiczne • Multimedia • Programowanie gier Naucz siê programowania i twórz w³asne gry! Spis treści Wstęp .............................................................................................. 7 Część I Dla początkujących ........................................................ 9 Rozdział 1. Instalacja środowiska Dev-C++ ....................................................... 11 Rozdział 2. Pierwszy program ........................................................................... 13 2.1. Przygotowanie edytora ....................................................................................... 13 2.2. Kod wygenerowany przez Dev-C++ .................................................................. 15 2.3. Określanie tytułu okna ....................................................................................... 19 2.4. Określanie położenia i rozmiaru okna ................................................................ 19 2.5. Określanie stylu okna ......................................................................................... 21 2.6. Ćwiczenia ........................................................................................................... 22 Rozdział 3. Rysowanie w oknie ........................................................................ 23 3.1. Obsługa komunikatu WM_PAINT .................................................................... 23 3.2. Zestawienie funkcji graficznych. Program z użyciem funkcji Ellipse i LineTo .................................................................... 25 3.3. Wyświetlanie tekstu w obszarze roboczym okna ............................................... 28 Pierwszy program z użyciem funkcji SetPixel — wykres funkcji sinus ............ 34 3.4. 3.5. Tworzenie pióra ................................................................................................. 39 3.6. Drugi program z użyciem funkcji SetPixel — zbiór Mandelbrota ..................... 42 Trzeci program z użyciem funkcji SetPixel — prosta obsługa bitmap ............... 48 3.7. 3.8. Ćwiczenia ........................................................................................................... 55 Rozdział 4. Obsługa komunikatów myszy .......................................................... 57 Program z obsługą komunikatu WM_MOUSEMOVE ...................................... 57 4.1. 4.2. Obsługa komunikatów WM_LBUTTONDOWN i WM_RBUTTONDOWN — zbiór Mandelbrota po raz drugi .......................... 62 4.3. Obsługa komunikatów WM_MOUSEMOVE, WM_LBUTTONDOWN i WM_RBUTTONDOWN — zbiór Mandelbrota a zbiory Julii ........................ 66 Tajemnica przycisków okna ............................................................................... 75 Ćwiczenia ........................................................................................................... 86 4.4. 4.5. Rozdział 5. Obsługa komunikatów klawiatury .................................................... 87 5.1. Komunikaty klawiatury. Obsługa komunikatu WM_CHAR .............................. 87 5.2. Obsługa komunikatu WM_KEYDOWN. Diagram Feigenbauma ...................... 90 5.3. Ćwiczenia ......................................................................................................... 101 4 Tworzenie aplikacji dla Windows. Od prostych programów do gier komputerowych Rozdział 6. Obiekty sterujące w oknie ............................................................ 103 6.1. Obiekt klasy BUTTON .................................................................................... 103 6.2. Obsługa grafiki za pomocą obiektów klasy BUTTON ..................................... 109 6.3. Obiekt klasy edycji. Automaty komórkowe ..................................................... 116 6.4. Ćwiczenia ......................................................................................................... 128 Rozdział 7. Menu i plik zasobów ..................................................................... 131 7.1. Dodanie menu do okna ..................................................................................... 131 7.2. Obsługa bitmapy z pliku zasobów .................................................................... 138 7.3. Odczytywanie danych z pliku. Edytor bitmap .................................................. 143 7.4. Kopiarka wielokrotnie redukująca ................................................................... 154 7.5. Ćwiczenia ......................................................................................................... 169 Podsumowanie części I ................................................................................................. 169 Część II Dla średniozaawansowanych ....................................... 171 Rozdział 8. Projektowanie aplikacji wielowątkowych ....................................... 173 Tworzenie wątków za pomocą funkcji CreateThread ...................................... 173 Czy praca z wątkami przyspiesza działanie aplikacji? Sekcja krytyczna i priorytety wątków .............................................................. 178 8.3. Wstrzymywanie pracy i usuwanie wątków ...................................................... 190 8.4. Ćwiczenia ......................................................................................................... 199 8.1. 8.2. Rozdział 9. Definiowanie bibliotek .................................................................. 201 Biblioteki statyczne .......................................................................................... 201 9.1. Biblioteki dynamiczne — podejście strukturalne ............................................. 207 9.2. 9.3. Biblioteki dynamiczne — podejście obiektowe ............................................... 220 9.4. Własny temat okna ........................................................................................... 225 9.5. Ćwiczenia ......................................................................................................... 254 Rozdział 10. Multimedia .................................................................................. 255 10.1. Odtwarzanie plików wav — funkcja sndPlaySound ........................................ 255 10.2. Odtwarzanie plików mp3 — biblioteka FMOD ............................................... 258 10.3. Ćwiczenia ......................................................................................................... 269 Podsumowanie części II ............................................................................................... 270 Część III Dla zaawansowanych .................................................. 273 Rozdział 11. Programowanie gier z użyciem biblioteki OpenGL .......................... 275 11.1. Podstawy obsługi biblioteki OpenGL .............................................................. 275 11.2. Prymitywy OpenGL ......................................................................................... 284 11.3. Bufor głębokości, perspektywa — wzorzec kodu do programowania gier (pierwsze starcie) ............................ 298 11.4. Animacja .......................................................................................................... 309 11.5. Poruszanie się po scenie, funkcja gluLookAt i wzorzec kodu do programowania gier (starcie drugie, decydujące) ............... 320 11.6. Tekstury i mipmapy ......................................................................................... 334 11.7. Listy wyświetlania i optymalizacja kodu ......................................................... 349 11.8. Detekcja kolizji ................................................................................................ 361 11.9. Światło, mgła, przezroczystość i inne efekty specjalne ....................................... 368 Uruchamianie gry w trybie pełnoekranowym .................................................. 368 Mgła ................................................................................................................. 371 Przezroczystość ................................................................................................ 374 Światło ............................................................................................................. 384 Spis treści 5 11.10. Jak ustawić stałą prędkość działania programu? .............................................. 390 11.11. Gotowe obiekty OpenGL ................................................................................. 398 11.12. Fonty ................................................................................................................ 406 11.13. Dźwięk na scenie ............................................................................................. 416 11.14. Budujemy grę! ................................................................................................. 419 Wprowadzenie i charakter gry ......................................................................... 419 Dane gracza ...................................................................................................... 420 Strzał z broni .................................................................................................... 423 Pozostali bohaterowie gry ................................................................................ 425 Odnawianie zasobów gracza ............................................................................ 428 Zakończenie programu (gry) ............................................................................ 430 Podsumowanie części III .............................................................................................. 433 Dodatki ..................................................................................... 435 Dodatek A ................................................................................... 437 Dodatek B ................................................................................... 439 Skorowidz .................................................................................... 441 Rozdziaä 8. i Projektowanie aplikacji wielowñtkowych 173 Rozdziaä 8. Projektowanie aplikacji wielowñtkowych 8.1. Tworzenie wñtków za pomocñ funkcji CreateThread JesteĞmy przyzwyczajeni do tego, Īe w systemie Windows moĪemy pracowaü na wielu oknach jednoczeĞnie. Jak duĪe jest to udogodnienie, nie trzeba nikogo przekonywaü. TĊ cechĊ systemu nazywamy wielozadaniowoĞcią. Jej najpoĪyteczniejszym zastosowa- niem jest moĪliwoĞü ukrycia przed pracodawcą pod stosem otwartych okien jednego okienka z uruchomioną ulubioną grą. System Windows pozwala takĪe na coĞ wiĊcej — na wielowątkowoĞü, czyli wykonywanie wielu zadaĔ równoczeĞnie w ramach dziaáania jednego programu. OczywiĞcie, pojĊcia pracy równoczesnej nie naleĪy traktowaü zbyt dosáownie. System dzieli czas pracy procesora i przyznaje po kolei pewną jego czĊĞü w celu obsáugi kolejnego wątku. PoniewaĪ wspomniany proces wykonywany jest bardzo szybko, powstaje iluzja równolegáego dziaáania wątków. Wątek tworzymy za pomocą funkcji CreateThread o nastĊpującej skáadni: HANDLE CreateThread(LPSECURITY_ATTRIBUTES atrybuty_watku, DWORD rozmiar_stosu, LPTHREAD_START_ROUTINE adres_funkcji_watku, LPVOID parametr, DWORD flaga_watku, LPDWORD identyfikator ); Pierwszy argument atrybuty_watku miaá zastosowanie w systemie Windows NT. Od tego czasu nie wykorzystuje siĊ go i moĪemy w jego miejsce wpisaü NULL. Drugi argument okreĞla rozmiar stosu w bajtach. JeĪeli nie mamy pomysáu dotyczącego jego wielkoĞci, wpiszmy zero, co oznacza zadeklarowanie stosu standardowego rozmiaru. W parametrze 174 CzöĈè II i Dla Ĉredniozaawansowanych adres_funkcji_watku powinien znajdowaü siĊ, zresztą zgodnie z pomysáowo nadaną nazwą, adres funkcji, która zawiera kod obsáugi wątku. Deklaracja funkcji musi mieü nastĊpującą postaü: DWORD NazwaFunkcji(LPVOID); Parametr, który moĪemy przekazaü do funkcji wątku, przekazujemy jako czwarty argu- ment funkcji CreateThread. Argument flaga_watku okreĞla chwilĊ uruchomienia wątku. MoĪemy uĪyü jednej z dwóch wartoĞci:  0 — wątek uruchamiany jest w chwili utworzenia,  CREATE_SUSPENDED — uruchomienie wątku jest wstrzymane aĪ do wywoáania funkcji ResumeThread. Ostatni parametr powinien zawieraü wskaĨnik do zmiennej, w której zostanie umiesz- czony identyfikator wątku. JeĪeli uruchomienie wątku powiedzie siĊ, funkcja Create ´Thread zwraca uchwyt utworzonego wątku, w przeciwnym przypadku zwracana jest wartoĞü NULL. Wszystko, co wáaĞnie przeczytaliĞcie, jest niezwykle ciekawe, ale warto juĪ przystąpiü do budowania programu. BĊdzie to aplikacja z dziedziny helmintologii, czyli nauki o robakach (niestety, pasoĪytniczych). WyobraĨmy sobie obszar roboczy naszego okna. KlikniĊcie lewego przycisku myszy powinno wywoáaü nowy wątek — robaka, którego początek niszczycielskiej dziaáalnoĞci bĊdzie siĊ znajdowaá w miejscu, w jakim aktualnie znajduje siĊ kursor myszy. Tworzymy nowy projekt Windows o nazwie Program20.dev. Spójrzmy na prosty program realizujący wspomniane zadanie (plik Program20_main.cpp), a nastĊpnie omówiĊ kluczowe punkty programu. #include windows.h /* Staáe rozmiaru obszaru roboczego okna */ const int MaxX = 640; const int MaxY = 480; /* Zmienne globalne dla obsáugi wątków */ int x, y; /* Funkcja wątku */ DWORD Robak(LPVOID param); LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM); char szClassName[ ] = WindowsApp ; int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nFunsterStil) { HWND hwnd; MSG messages; WNDCLASSEX wincl; Rozdziaä 8. i Projektowanie aplikacji wielowñtkowych 175 wincl.hInstance = hThisInstance; wincl.lpszClassName = szClassName; wincl.lpfnWndProc = WindowProcedure; wincl.style = CS_DBLCLKS; wincl.cbSize = sizeof (WNDCLASSEX); wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION); wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION); wincl.hCursor = LoadCursor (NULL, IDC_ARROW); wincl.lpszMenuName = NULL; wincl.cbClsExtra = 0; wincl.cbWndExtra = 0; wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND; if (!RegisterClassEx ( wincl)) return 0; hwnd = CreateWindowEx ( 0, szClassName, Program20 - aplikacja wielowætkowa , WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, MaxX+8, MaxY+34, HWND_DESKTOP, NULL, hThisInstance, NULL ); ShowWindow (hwnd, nFunsterStil); while (GetMessage ( messages, NULL, 0, 0)) { TranslateMessage( messages); DispatchMessage( messages); } return messages.wParam; } LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, ´LPARAM lParam) { static HDC hdc; PAINTSTRUCT ps; DWORD pointer; switch (message) { case WM_PAINT: hdc = BeginPaint(hwnd, ps); if(x =5 y =5 x MaxX-5 y MaxY-5) Ellipse(hdc, x-5, y-5, x+5, y+5); EndPaint(hwnd, ps); break; case WM_LBUTTONDOWN: x = LOWORD(lParam); 176 CzöĈè II i Dla Ĉredniozaawansowanych y = HIWORD(lParam); CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Robak, hwnd, 0, ´ pointer); break; case WM_DESTROY: PostQuitMessage (0); break; default: return DefWindowProc (hwnd, message, wParam, lParam); } return 0; } DWORD Robak(LPVOID param) { int wx = x, wy = y; //uruchomienie liczb losowych srand(GetTickCount()); //pĊtla nieskoĔczona while(TRUE) { //wylosuj ruch robaka wx += (rand() 5) - 2; wy += (rand() 5) - 2; //sprawdĨ, czy robak znajduje siĊ w dozwolonym obszarze if(wx 5) wx = 5; if(wx MaxX-5) wx = MaxX-5; if(wy 5) wy = 5; if(wy MaxY-5) wy = MaxY-5; //zaáaduj nowe wspóárzĊdne do zmiennej globalnej x = wx; y = wy; //narysuj robaka w nowym poáoĪeniu InvalidateRect((HWND)param, NULL, FALSE); //zaczekaj 1/10 sekundy Sleep(100); } return 0; } Program po uruchomieniu czeka na naciĞniĊcie lewego przycisku myszy. case WM_LBUTTONDOWN: x = LOWORD(lParam); y = HIWORD(lParam); CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Robak, hwnd, 0, pointer); break; JeĪeli procedura okna otrzyma komunikat o zajĞciu tego zdarzenia, tworzy wątek, którego obsáugĊ powierza funkcji Robak. WspóárzĊdne kursora myszy są áadowane do zmien- nych globalnych x i y w celu przekazania ich do funkcji obsáugi wątku. Aplikacja kon- tynuuje pracĊ, a równolegle z nią zaczyna dziaáaü nowy wątek. Spójrzmy na kod funkcji Robak. Rozdziaä 8. i Projektowanie aplikacji wielowñtkowych 177 DWORD Robak(LPVOID param) { int wx = x, wy = y; //uruchomienie liczb losowych srand(GetTickCount()); while(TRUE) { //wylosuj ruch robaka wx += (rand() 5) - 2; wy += (rand() 5) - 2; //sprawdĨ, czy robak znajduje siĊ w dozwolonym obszarze if(wx 5) wx = 5; if(wx MaxX-5) wx = MaxX-5; if(wy 5) wy = 5; if(wy MaxY-5) wy = MaxY-5; //zaáaduj nowe wspóárzĊdne do zmiennej globalnej x = wx; y = wy; //narysuj robaka w nowym poáoĪeniu InvalidateRect((HWND)param, NULL, FALSE); //zaczekaj 1/10 sekundy Sleep(100); } return 0; } Początkowe wspóárzĊdne obiektu zostaáy przekazane przy uĪyciu zmiennych global- nych x i y. int wx = x, wy = y; Pozostaáa czĊĞü kodu umieszczona jest w pĊtli nieskoĔczonej, co oznacza, Īe wątek zostanie zniszczony dopiero w chwili zamkniĊcia aplikacji. Bardzo waĪną cechą robaka jest jego losowy ruch realizowany za pomocą uaktualniania wspóárzĊdnych. wx += (rand() 5) - 2; wy += (rand() 5) - 2; PoniewaĪ w tym zadaniu korzystamy z funkcji rand, która zwraca liczby pseudolosowe, waĪne jest, by wartoĞü inicjalizująca tej funkcji róĪniáa siĊ dla kaĪdego wątku. LiczbĊ początkową dla obliczeĔ funkcji rand przekazujemy za pomocą srand, której argumen- tem jest u nas funkcja GetTickCount podająca liczbĊ milisekund liczoną od chwili uru- chomienia systemu Windows. srand(GetTickCount()); Po obliczeniu nowej wspóárzĊdnej poáoĪenia robaka i sprawdzeniu jej poprawnoĞci wy- muszamy odmalowanie obszaru roboczego okna. InvalidateRect((HWND)param, NULL, FALSE); 178 CzöĈè II i Dla Ĉredniozaawansowanych W ten sposób wątek komunikuje siĊ z aplikacją gáówną i zmusza do zainteresowania siĊ kodem obsáugi komunikatu WM_PAINT. case WM_PAINT: hdc = BeginPaint(hwnd, ps); if(x =5 y =5 x MaxX-5 y MaxY-5) Ellipse(hdc, x-5, y-5, x+5, y+5); EndPaint(hwnd, ps); break; Wizualną czĊĞcią dziaáania wątku jest narysowana w nowym poáoĪeniu elipsa. Przy- káadowy efekt dziaáania programu z uruchomionymi jednoczeĞnie kilkudziesiĊcioma wątkami przedstawiam na rysunku 8.1. Rysunek 8.1. Okno dziaáającej aplikacji Program20 8.2. Czy praca z wñtkami przyspiesza dziaäanie aplikacji? Sekcja krytyczna i priorytety wñtków OdpowiedĨ na pytanie postawione w tytule podrozdziaáu nie jest prosta. Wszystko zaleĪy od rodzaju czynnoĞci realizowanych przez wątek. PamiĊtajmy, Īe kaĪdy utwo- rzony wątek korzysta z czĊĞci czasu pracy procesora. Dlatego báĊdna jest myĞl, Īe skomplikowane obliczenie rozáoĪone na wiele wątków wykona siĊ szybciej niĪ to samo Rozdziaä 8. i Projektowanie aplikacji wielowñtkowych 179 obliczenie w jednowątkowej aplikacji. Natomiast istnieją sytuacje, w których procesor „czeka bezczynnie” lub jest tylko „nieznacznie zajĊty”, wtedy — oczywiĞcie — stoso- wanie wątków jest zasadne. Operowanie wątkami ma przede wszystkim uáatwiü pracĊ nam, czyli programistom. Ilustracją do postawionego pytania bĊdzie aplikacja, w której na dwóch czĊĞciach obszaru roboczego okna wyĞwietlimy zbiór Mandelbrota i jeden ze zbiorów Julii. Wykonanie kaĪdego z tych zadaĔ bardzo mocno obciąĪa procesor i kartĊ graficzną. Nasza aplika- cja w pierwszej kolejnoĞci bĊdzie rysowaáa oddzielnie zbiór Mandelbrota i zbiór Julii, a nastĊpnie obydwa zadania zostaną wykonane równolegáe w dwóch wątkach. Poli- czymy czas wykonania zadaĔ w wersji bez uĪycia wątków i z ich uĪyciem. Tworzymy nowy projekt Program21.dev. PoniewaĪ przewidujemy duĪą iloĞü operacji graficznych, odpowiedni kod umieĞcimy w pliku Program21.h. Wygenerowany przez Dev-C++ kod (plik Program21_main.cpp) modyfikujemy tylko w czterech miejscach. 1. Dodajemy plik nagáówkowy. #include Program21.h 2. Zmieniamy parametry funkcji CreateWindowEx. hwnd = CreateWindowEx ( 0, szClassName, Program21 , WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 648, 340, HWND_DESKTOP, NULL, hThisInstance, NULL ); 3. Dodajemy kod obsáugi komunikatu WM_PAINT. case WM_PAINT: hdc = BeginPaint(hwnd, ps); MojaProcedura(hdc); EndPaint(hwnd, ps); break; 4. Dodajemy kod obsáugi komunikatu WM_LBUTTONDOWN. DziĊki temu moĪliwe bĊdzie áatwe odĞwieĪanie obszaru roboczego okna i tym samym wielokrotne powtórzenie doĞwiadczenia. case WM_LBUTTONDOWN: InvalidateRect(hwnd, NULL, TRUE); break; Plik Program21_main.cpp w caáoĞci wygląda nastĊpująco (jak zwykle, pominiĊte zostaáy generowane przez Dev-C++ komentarze): 180 CzöĈè II i Dla Ĉredniozaawansowanych #include windows.h #include Program21.h LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM); char szClassName[ ] = WindowsApp ; int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nFunsterStil) { HWND hwnd; MSG messages; WNDCLASSEX wincl; wincl.hInstance = hThisInstance; wincl.lpszClassName = szClassName; wincl.lpfnWndProc = WindowProcedure; wincl.style = CS_DBLCLKS; wincl.cbSize = sizeof (WNDCLASSEX); wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION); wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION); wincl.hCursor = LoadCursor (NULL, IDC_ARROW); wincl.lpszMenuName = NULL; wincl.cbClsExtra = 0; wincl.cbWndExtra = 0; wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND; if (!RegisterClassEx ( wincl)) return 0; hwnd = CreateWindowEx ( 0, szClassName, Program21 , WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 648, 340, HWND_DESKTOP, NULL, hThisInstance, NULL ); ShowWindow (hwnd, nFunsterStil); while (GetMessage ( messages, NULL, 0, 0)) { TranslateMessage( messages); DispatchMessage( messages); } return messages.wParam; } Rozdziaä 8. i Projektowanie aplikacji wielowñtkowych 181 LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, ´LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; switch (message) { case WM_PAINT: hdc = BeginPaint(hwnd, ps); MojaProcedura(hdc); EndPaint(hwnd, ps); break; case WM_LBUTTONDOWN: InvalidateRect(hwnd, NULL, TRUE); break; case WM_DESTROY: PostQuitMessage (0); break; default: return DefWindowProc (hwnd, message, wParam, lParam); } return 0; } Kluczową czĊĞü programu, wraz z wywoáywaną w kodzie obsáugi komunikatu WM_PAINT procedurą MojaProcedura, umieĞcimy w pliku Program21.h. ZawartoĞü procedury bĊdą stanowiü: 1. Obliczenie czasu potrzebnego do narysowania zbioru Mandelbrota i Julii za pomocą funkcji wywoáywanych w sposób sekwencyjny. a) Pobranie liczby milisekund, które upáynĊáy od chwili uruchomienia systemu Windows i umieszczenie tej wielkoĞci w zmiennej m1. b) Wywoáanie funkcji rysującej zbiór Mandelbrota. c) Wywoáanie funkcji rysującej zbiór Julii. d) Pobranie liczby milisekund, które upáynĊáy od chwili uruchomienia systemu Windows i umieszczenie tej wielkoĞci w zmiennej m2. e) Obliczenie i wyĞwietlenie róĪnicy m2-m1. Wynik oznacza czas wykonania zadaĔ opisanych w punktach b i c. 2. Obliczenie czasu potrzebnego do narysowania zbioru Mandelbrota i Julii za pomocą funkcji wywoáanych w równolegle pracujących wątkach. a) Pobranie liczby milisekund, które upáynĊáy od chwili uruchomienia systemu Windows, i umieszczenie tej wielkoĞci w zmiennej m1. b) Utworzenie wątku wywoáującego funkcjĊ rysującą zbiór Mandelbrota. c) Utworzenie wątku wywoáującego funkcjĊ rysującą zbiór Julii. d) Oczekiwanie na zakoĔczenie pracy obu wątków. 182 CzöĈè II i Dla Ĉredniozaawansowanych e) Pobranie liczby milisekund, które upáynĊáy od chwili uruchomienia systemu Windows, i umieszczenie tej wielkoĞci w zmiennej m2. f) Obliczenie i wyĞwietlenie róĪnicy m2-m1. Wynik oznacza czas wykonania zadaĔ opisanych w punktach b i c. Popatrzmy na pierwszą wersjĊ procedury MojaProcedura. void MojaProcedura(HDC hdc) { char tab[32]; DWORD pointer; DWORD m, m1, m2; //pobierz iloĞü milisekund od chwili uruchomienia systemu Windows //(czas startu zadania) m1 = GetTickCount(); //wykonaj zadanie numer 1 Zbior_Mandelbrota(hdc); //wykonaj zadanie numer 2 Zbior_Julii(hdc); //pobierz iloĞü milisekund od chwili uruchomienia systemu Windows //(czas zakoĔczenia zadania) m2 = GetTickCount(); //wyĞwietl okres czasu potrzebny do wykonania zadania m = m2 - m1; TextOut(hdc, 10, 250, Czas sekwencyjnego wykonania zadaē: , 35); itoa(m, tab, 10); TextOut(hdc, 300, 250, tab, strlen(tab)); //wyczyĞü fragment okna Rectangle(hdc, 0, 0, 640, 240); //wyzeruj zmienną globalną dla wątków zmienna_stop = 0; //pobierz iloĞü milisekund od chwili uruchomienia systemu Windows //(czas startu zadania) m1 = GetTickCount(); //wywoáaj wątek wykonujący zadanie numer 1 CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Z_M, hdc, 0, pointer); //wywoáaj wątek wykonujący zadanie numer 2 CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Z_J, hdc, 0, pointer); //czekaj na zakoĔczenie pracy wątków do{}while(zmienna_stop 2); //pobierz iloĞü milisekund od chwili uruchomienia systemu Windows //(czas zakoĔczenia zadania) m2 = GetTickCount(); //wyĞwietl okres czasu potrzebny do wykonania zadania m = m2 - m1; TextOut(hdc, 10, 270, Czas równolegđego wykonania zadaē: , 34); itoa(m, tab, 10); TextOut(hdc, 300, 270, tab, strlen(tab)); } Procedura realizuje kolejno przedstawione w punktach zadania. Do obliczenia czasu dziaáania czĊĞci programu uĪywamy znanej juĪ funkcji GetTickCount. Taki sposób licze- nia przedziaáów czasu jest caákiem bezpieczny, gdyĪ zwracana przez funkcjĊ wielkoĞü Rozdziaä 8. i Projektowanie aplikacji wielowñtkowych 183 jest 32-bitowa, co oznacza, Īe moĪemy spodziewaü siĊ zrestartowania licznika dopiero po 49 dniach nieprzerwanej pracy systemu Windows. Funkcje tworzonych w procedurze wątków mają nastĊpującą postaü: DWORD Z_M(LPVOID param) { Zbior_Mandelbrota((HDC)param); //zwiĊksz wskaĨnik zakoĔczenia pracy zmienna_stop++; return 0; } DWORD Z_J(LPVOID param) { Zbior_Julii((HDC)param); //zwiĊksz wskaĨnik zakoĔczenia pracy zmienna_stop++; return 0; } Kody funkcji Zbior_Mandelbrota i Zbior_Julii na razie w naszych rozwaĪaniach nie są istotne. NaleĪy zwróciü uwagĊ na sposób zakoĔczenia funkcji wątków, polegający na inkrementacji zmiennej globalnej. zmienna_stop++; DziĊki temu moĪliwe staje siĊ sprawdzenie zakoĔczenia dziaáania obu wątków za po- mocą pĊtli: do{}while(zmienna_stop 2); MoĪemy przyjąü, Īe mamy pierwszą podstawową wersjĊ aplikacji. Nie jest to jeszcze wersja optymalna. Popatrzmy na rysunek 8.2, na którym przedstawiam rezultat dziaáania programu. Rysunek 8.2. Okno aplikacji Program21 w dziaáaniu (wersja pierwsza, niepoprawna) Na początek naleĪy zauwaĪyü, Īe narysowanie zbiorów Mandelbrota i Julii za pomocą wątków wykonaáo siĊ szybciej niĪ zadanie analogiczne, zrealizowane za pomocą sekwen- cyjnego wywoáania funkcji. Dzieje siĊ tak dlatego, gdyĪ okresy obliczeĔ wykorzystu- jące procesor są przerywane dáugimi okresami obciąĪania karty graficznej, podczas 184 CzöĈè II i Dla Ĉredniozaawansowanych których wyĞwietlany jest piksel o danej wspóárzĊdnej w odpowiednim kolorze. Istnieją wiĊc krótkie przedziaáy czasowe maáego obciąĪenia procesora, które moĪe wykorzystaü dla wáasnych obliczeĔ drugi wątek. Jednak niebezpieczeĔstwo pracy z wątkami polega na tym, Īe system moĪe zarządziü wstrzymanie pracy wątku i przekazanie sterowania drugiemu w najmniej spodziewanym momencie. JeĪeli obydwa wątki korzystają w tym czasie ze wspólnych zasobów systemowych, a tak jest w przypadku operacji graficznych korzystających z obiektów GDI, dane mogą ulec zatraceniu, czego wyni- kiem są niezamalowane obszary, pokazane na rysunku 8.2. Na szczĊĞcie, potrafimy przeciwdziaáaü przerwaniu pracy wątku w chwili dla nas niepo- Īądanej (co za ulga!). Do tego celu sáuĪy sekcja krytyczna, a dokáadniej obiekty sekcji krytycznej. W celu przybliĪenia sensu istnienia tego rewelacyjnego rozwiązania pro- gramistycznego posáuĪĊ siĊ przykáadem. WyobraĨmy sobie bibliotekĊ, w której znajduje siĊ jeden egzemplarz starego manuskryptu. WyobraĨmy sobie takĪe, Īe setka biblio- filów chce w danym momencie ów egzemplarz przeczytaü. MoĪe to robiü na raz tylko jeden fanatyk starego piĞmiennictwa, natomiast reszta, obgryzając ze zdenerwowania palce, moĪe tylko czekaü, aĪ szczĊĞliwy chwilowy posiadacz obiektu zakoĔczy pracĊ. Podobną rolĊ w pracy wątków odgrywa obiekt sekcji krytycznej: w jego posiadanie moĪe w danej chwili wejĞü tylko jeden wątek, natomiast pozostaáe wątki zawieszają pracĊ do czasu przejĊcia obiektu sekcji krytycznej. OczywiĞcie, czekają tylko te wątki, w których zaznaczono koniecznoĞü wejĞcia w posiadanie obiektu sekcji krytycznej w celu wykonania fragmentu kodu. Praca z obiektami sekcji krytycznej jest niezwykle prosta i skáada siĊ z kilku punktów. Oto one. 1. Deklaracja obiektu sekcji krytycznej. Nazwa obiektu jest dowolna, moĪemy zadeklarowaü dowolną liczbĊ obiektów. CRITICAL_SECTION critical_section; 2. Inicjalizacja obiektu sekcji krytycznej. InitializeCriticalSection( critical_section); 3. NastĊpne dwa podpunkty umieszczamy w funkcji wątków, są to: a) przejĊcie obiektu sekcji krytycznej: EnterCriticalSection( critical_section); b) zwolnienie obiektu sekcji krytycznej: LeaveCriticalSection( critical_section); 4. ZakoĔczenie pracy z obiektem sekcji krytycznej powinno byü związane z jego usuniĊciem. DeleteCriticalSection( critical_section); Sposób uĪycia obiektów sekcji krytycznej zobaczymy na przykáadzie kodu poprawionej procedury MojaProcedura i jednej z funkcji wątków Z_M. Rezultat dziaáania tak popra- wionego programu przedstawiam na rysunku 8.3. Rozdziaä 8. i Projektowanie aplikacji wielowñtkowych 185 Rysunek 8.3. Okno aplikacji Program21 dziaáającej z wykorzystaniem obiektu sekcji krytycznej /* Obiekt sekcji krytycznej */ CRITICAL_SECTION critical_section; void MojaProcedura(HDC hdc) { char tab[32]; DWORD pointer, m, m1, m2; //pobierz iloĞü milisekund od chwili uruchomienia systemu Windows //(czas startu zadania) m1 = GetTickCount(); //wykonaj zadanie numer 1 Zbior_Mandelbrota(hdc); //wykonaj zadanie numer 2 Zbior_Julii(hdc); //pobierz iloĞü milisekund od chwili uruchomienia systemu Windows //(czas zakoĔczenia zadania) m2 = GetTickCount(); //wyĞwietl okres czasu potrzebny do wykonania zadania m = m2 - m1; TextOut(hdc, 10, 250, Czas sekwencyjnego wykonania zadaē: , 35); itoa(m, tab, 10); TextOut(hdc, 300, 250, tab, strlen(tab)); //wyczyĞü fragment okna Rectangle(hdc, 0, 0, 640, 240); //wyzeruj zmienną globalną dla wątków zmienna_stop = 0; //zainicjalizuj obiekt sekcji krytycznej InitializeCriticalSection( critical_section); //pobierz iloĞü milisekund od chwili uruchomienia systemu Windows //(czas startu zadania) m1 = GetTickCount(); //wywoáaj wątek wykonujący zadanie numer 1 CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Z_M, hdc, 0, pointer); //wywoáaj wątek wykonujący zadanie numer 2 CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Z_J, hdc, 0, pointer); //czekaj na zakoĔczenie pracy wątków do{}while(zmienna_stop 2); //pobierz iloĞü milisekund od chwili uruchomienia systemu Windows //(czas zakoĔczenia zadania) m2 = GetTickCount(); 186 CzöĈè II i Dla Ĉredniozaawansowanych //usuĔ obiekt sekcji krytycznej DeleteCriticalSection( critical_section); //wyĞwietl okres czasu potrzebny do wykonania zadania m = m2 - m1; TextOut(hdc, 10, 270, Czas równolegđego wykonania zadaē: , 34); itoa(m, tab, 10); TextOut(hdc, 300, 270, tab, strlen(tab)); } DWORD Z_M(LPVOID param) { double a_lewy = -2.0, a_prawy = 2.0; double b_gora = 1.5, b_dol = -1.5; double krokX = (a_prawy - a_lewy)/320.0; double krokY = (b_gora - b_dol)/240.0; int kolor; for(int y=0; y 240; y++) for(int x=0; x 320; x++) { kolor = Kolor(a_lewy+double(x)*krokX, b_gora-double(y)*krokY); //wejdĨ w sekcjĊ krytyczną EnterCriticalSection( critical_section); //ustaw piksel SetPixel((HDC)param, x, y, kolor); //opuĞü sekcjĊ krytyczną LeaveCriticalSection( critical_section); } //zwiĊksz wskaĨnik zakoĔczenia pracy zmienna_stop++; return 0; } PoniewaĪ funkcje graficzne zostaáy w aplikacji zamkniĊte w sekcje krytyczne, nie ma na wykresie obszarów báĊdnych. WyĞwietlane przez program liczby mogą siĊ róĪniü od zaprezentowanych na ilustracji, co jest wynikiem róĪnej mocy obliczeniowej kom- putera, a nawet iloĞcią procesów korzystających w danej chwili z procesora. Ostatnie udoskonalenie programu bĊdzie związane ze zmianą priorytetu wykonywanych wątków. Uruchomione w aplikacji wątki muszą wspóádzieliü czas pracy procesora nie tylko miĊdzy siebie, ale teĪ z wywoáującą je aplikacją gáówną i innymi uruchomionymi w systemie wątkami. To wydáuĪa czas dziaáania wątków. Zmiana tej sytuacji jest moĪliwa za pomocą zmiany priorytetu uruchomionych wątków. Do tego zadania sáuĪy funkcja SetThreadPriority o nastĊpującej deklaracji: BOOL SetThreadPriority(HANDLE uchwyt_watku, int priorytet); Parametr uchwyt_watku powinien zawieraü uchwyt wątku, którego priorytet chcemy zmieniü, a argumentem priorytet okreĞlamy wartoĞü priorytetu wątku. Zestawienie moĪliwych do wyboru wartoĞci priorytetu przedstawiam w tabeli 8.1. W naszej aplikacji uĪyjemy najwyĪszego priorytetu THREAD_PRIORITY_HIGHEST, czego wynikiem bĊdzie prawie dwukrotne przyspieszenie czasu rysowania wykresów zbioru Mandelbrota i Julii. Oto gotowy plik Program21.h. Rozdziaä 8. i Projektowanie aplikacji wielowñtkowych 187 Tabela 8.1. Priorytety uruchamianych wątków Staäa priorytetu WartoĈè priorytetu THREAD_PRIORITY_HIGHEST THREAD_PRIORITY_ABOVE_NORMAL THREAD_PRIORITY_NORMAL THREAD_PRIORITY_BELOW_NORMAL THREAD_PRIORITY_LOWEST +2 +1 0 -1 -2 #include math.h //zmienna globalna do sprawdzenia stanu wątków int zmienna_stop; /* Deklaracja procedur */ void MojaProcedura(HDC); void Zbior_Mandelbrota(HDC); void Zbior_Julii(HDC); /* Funkcje wątków */ DWORD Z_M(LPVOID); DWORD Z_J(LPVOID); /* Obiekt sekcji krytycznej */ CRITICAL_SECTION critical_section; void MojaProcedura(HDC hdc) { char tab[32]; DWORD pointer, m, m1, m2; HANDLE h; //pobierz iloĞü milisekund od chwili uruchomienia systemu Windows //(czas startu zadania) m1 = GetTickCount(); //wykonaj zadanie numer 1 Zbior_Mandelbrota(hdc); //wykonaj zadanie numer 2 Zbior_Julii(hdc); //pobierz iloĞü milisekund od chwili uruchomienia systemu Windows //(czas zakoĔczenia zadania) m2 = GetTickCount(); //wyĞwietl okres czasu potrzebny do wykonania zadania m = m2 - m1; TextOut(hdc, 10, 250, Czas sekwencyjnego wykonania zadaē: , 35); itoa(m, tab, 10); TextOut(hdc, 300, 250, tab, strlen(tab)); //wyczyĞü fragment okna Rectangle(hdc, 0, 0, 640, 240); //wyzeruj zmienną globalną dla wątków zmienna_stop = 0; //zainicjalizuj obiekt sekcji krytycznej InitializeCriticalSection( critical_section); 188 CzöĈè II i Dla Ĉredniozaawansowanych //pobierz iloĞü milisekund od chwili uruchomienia systemu Windows //(czas startu zadania) m1 = GetTickCount(); //wywoáaj wątek wykonujący zadanie numer 1 h = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Z_M, hdc, 0, pointer); SetThreadPriority(h, THREAD_PRIORITY_HIGHEST); //wywoáaj wątek wykonujący zadanie numer 2 h = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Z_J, hdc, 0, pointer); SetThreadPriority(h, THREAD_PRIORITY_HIGHEST); //czekaj na zakoĔczenie pracy wątków do{}while(zmienna_stop 2); //pobierz iloĞü milisekund od chwili uruchomienia systemu Windows //(czas zakoĔczenia zadania) m2 = GetTickCount(); //usuĔ obiekt sekcji krytycznej DeleteCriticalSection( critical_section); //wyĞwietl okres czasu potrzebny do wykonania zadania m = m2 - m1; TextOut(hdc, 10, 270, Czas równolegđego wykonania zadaē: , 34); itoa(m, tab, 10); TextOut(hdc, 300, 270, tab, strlen(tab)); } int Kolor(double a0, double b0) { double an, a = a0, b = b0; int k = 0; while((sqrt(a*a+b*b) 2) (k 256)) { an = a*a - b*b + a0; b = 2.0*a*b + b0; a = an; k++; } return 0xF8*k; } void Zbior_Mandelbrota(HDC hdc) { double a_lewy = -2.0, a_prawy = 2.0; double b_gora = 1.5, b_dol = -1.5; double krokX = (a_prawy - a_lewy)/320.0; double krokY = (b_gora - b_dol)/240.0; for(int y=0; y 240; y++) for(int x=0; x 320; x++) SetPixel(hdc, x, y, Kolor(a_lewy+double(x)*krokX, b_gora-double(y)*krokY)); } int jKolor(double a0, double b0) { double an, a = a0, b = b0; int k = 0; while((sqrt(a*a+b*b) 2) (k 256)) { an = a*a - b*b + 0.373; b = 2.0*a*b + 0.133; Rozdziaä 8. i Projektowanie aplikacji wielowñtkowych 189 a = an; k++; } return 0xF8*k; } void Zbior_Julii(HDC hdc) { double a_lewy = -2.0, a_prawy = 2.0; double b_gora = 1.5, b_dol = -1.5; double krokX = (a_prawy - a_lewy)/320.0; double krokY = (b_gora - b_dol)/240.0; for(int y=0; y 240; y++) for(int x=0; x 320; x++) SetPixel(hdc, 320+x, y, jKolor(a_lewy+double(x)*krokX, b_gora-double(y)*krokY)); } DWORD Z_M(LPVOID param) { double a_lewy = -2.0, a_prawy = 2.0; double b_gora = 1.5, b_dol = -1.5; double krokX = (a_prawy - a_lewy)/320.0; double krokY = (b_gora - b_dol)/240.0; int kolor; for(int y=0; y 240; y++) for(int x=0; x 320; x++) { kolor = Kolor(a_lewy+double(x)*krokX, b_gora-double(y)*krokY); //wejdĨ w sekcjĊ krytyczną EnterCriticalSection( critical_section); //zapal piksel SetPixel((HDC)param, x, y, kolor); //opuĞü sekcjĊ krytyczną LeaveCriticalSection( critical_section); } //zwiĊksz wskaĨnik zakoĔczenia pracy zmienna_stop++; return 0; } DWORD Z_J(LPVOID param) { double a_lewy = -2.0, a_prawy = 2.0; double b_gora = 1.5, b_dol = -1.5; double krokX = (a_prawy - a_lewy)/320.0; double krokY = (b_gora - b_dol)/240.0; int kolor; for(int y=0; y 240; y++) for(int x=0; x 320; x++) 190 CzöĈè II i Dla Ĉredniozaawansowanych { kolor = jKolor(a_lewy+double(x)*krokX, b_gora-double(y)*krokY); //wejdĨ w sekcjĊ krytyczną EnterCriticalSection( critical_section); //ustaw piksel SetPixel((HDC)param, 320+x, y, kolor); //opuĞü sekcjĊ krytyczną LeaveCriticalSection( critical_section); } //zwiĊksz wskaĨnik zakoĔczenia pracy zmienna_stop++; return 0; } Rezultat dziaáania programu przedstawiam na rysunku 8.4. Rysunek 8.4. Okno aplikacji Program21 dziaáającej z wykorzystaniem obiektu sekcji krytycznej i ustawionym najwyĪszym priorytetem pracy wątków 8.3. Wstrzymywanie pracy i usuwanie wñtków Zapewne przynajmniej raz spotkaliĞcie siĊ z irytującym edytorem tekstowym, który — doskonale wiedząc, co chcemy napisaü — z zadziwiającym uporem poprawiaá postaü wpisywanego tekstu. Oto nadszedá czas zemsty i w niniejszym podrozdziale zajmiemy siĊ konstruowaniem podobnego, tylko jeszcze bardziej záoĞliwego edytora. Niezwykle pomocna przy tym bĊdzie umiejĊtnoĞü tworzenia wątków, które niczym duch bĊdą Ğledziáy wpisywany przez uĪytkownika tekst, dokonując jego modyfikacji. Znamy juĪ trzy czynnoĞci związane z wątkami. 1. Utworzenie wątku. Do tego zadania sáuĪy funkcja CreateThread. 2. Obsáuga obiektów sekcji krytycznej. Wykorzystywane do tego celu funkcje to: InitializeCriticalSection, EnterCriticalSection, LeaveCriticalSection i DeleteCriticalSection. 3. Zmiana priorytetu wątku. Do tego zadania sáuĪy funkcja SetThreadPriority. Rozdziaä 8. i Projektowanie aplikacji wielowñtkowych 191 JeĪeli chcemy chwilowo zawiesiü dziaáanie wątku, posáuĪymy siĊ funkcją Suspend ´Thread o nastĊpującej skáadni: DWORD SuspendThread(HANDLE uchwyt_watku); Jedynym parametrem funkcji jest uchwyt wstrzymywanego wątku. Wznowienie pracy wątku jest moĪliwe za pomocą funkcji ResumeThread: DWORD ResumeThread(HANDLE uchwyt_watku); Gdy dziaáalnoĞü wątku zupeánie nam zbrzydáa, moĪemy go usunąü za pomocą funkcji TerminateThread: BOOL TerminateThread(HANDLE uchwyt_watku, DWORD kod_wyjscia); Argument uchwyt_watku to — oczywiĞcie — uchwyt usuwanego wątku. Posáugując siĊ parametrem kod_wyjscia, moĪemy wysáaü kod zakoĔczenia pracy wątku. Ten pa- rametr najczĊĞciej nie bĊdzie nas interesowaá i w jego miejsce bĊdziemy wpisywaü liczbĊ zero. PrzystĊpujemy do budowania aplikacji uwzglĊdniającej nowo poznane funkcje: Sus ´pendThread i ResumeThread. Otwieramy nowy projekt o nazwie Program22.dev. Obszar roboczy okna podzielimy na dwie czĊĞci: w czĊĞci pierwszej umieĞcimy okno potomne klasy edycji, w czĊĞci drugiej — przyciski typu BS_AUTORADIOBUTTON. Okno potomne tworzymy za pomocą funkcji CreateWindow. Okno klasy edycji wykreujemy przy uĪy- ciu nastĊpującego kodu: static HWND hwndEdytora = CreateWindowEx( 0, EDIT , NULL, WS_VISIBLE | WS_CHILD | WS_BORDER | ES_MULTILINE | WS_VSCROLL | WS_HSCROLL, 0, 0, 400, 200, hwnd, NULL, hInst, NULL); NazwĊ klasy okna potomnego podajemy jako drugi parametr funkcji CreateWindow. Natomiast styl okna zdefiniowany za pomocą staáych: WS_VISIBLE | WS_CHILD | WS_BORDER | ES_MULTILINE | WS_VSCROLL | WS_HSCROLL utworzy edytor wielowierszowy o poziomym i pionowym pasku przewijania. Podsta- wowe wáasnoĞci okna klasy edycji to moĪliwoĞü edycji tekstu, jego pobierania i wstawia- nia tekstu nowego. ZaáóĪmy, Īe chcemy pobraü wpisany w oknie tekst. TĊ niezwykáą chĊü moĪemy zaspokoiü na dwa sposoby: wykorzystując funkcjĊ GetWindowText lub wysyáając komunikat WM_GETTEXT. Kod programu, w którym zastosowaliĞmy funkcjĊ GetWindowText, bĊdzie wyglądaü nastĊpująco: const int MaxT = 0xFFFF; char tekst[MaxT]; GetWindowText(hwndEdytora, tekst, MaxT); Analogiczny efekt uzyskamy, wysyáając komunikat WM_GETTEXT: const int MaxT = 0xFFFF; char tekst[MaxT]; SendMessage(hwndEdytora, WM_GETTEXT, MaxT, (LPARAM)tekst); 192 CzöĈè II i Dla Ĉredniozaawansowanych Parametrem MaxT oznaczamy maksymalną liczbĊ pobieranych znaków. Wysáanie tekstu do okna edycji równieĪ moĪemy zrealizowaü na dwa sposoby. Pierw- szym z nich jest uĪycie funkcji SetWindowText: SetWindowText(hwndEdytora, tekst); Drugim sposobem jest wysáanie komunikatu WM_SETTEXT: SendMessage(hwndEdytora, WM_SETTEXT, 0, (LPARAM)(LPCTSTR)tekst); DoĞü káopotliwą wáasnoĞcią klasy edycji jest resetowanie pozycji karetki po kaĪdora- zowym wysáaniu tekstu do okna edycji. Do ustawienia pozycji karetki sáuĪy funkcja SetCaretPos, jednak — zgodnie z opisem Pomocy Microsoft Windows — funkcja dziaáa tylko dla karetek utworzonych przez okno, czyli nie dziaáa dla obiektów klasy edycji. PosáuĪymy siĊ wybiegiem, a dokáadniej wysáaniem komunikatu EM_SETSEL. SendMessage(hwndEdytora, EM_SETSEL, poczatek, koniec); Komunikat EM_SETSEL jest wysyáany w celu zaznaczenia bloku tekstu, począwszy od litery o indeksie poczatek i koĔcu oznaczonym indeksem koniec. Po wykonaniu tego zadania karetka jest ustawiana w punkcie oznaczonym indeksem koniec. UĪycie komu- nikatu w nastĊpujący sposób: SendMessage(hwndEdytora, EM_SETSEL, x, x); jest áatwym sposobem przemieszczania pozycji karetki do pozycji x w oknie edycji. PrzejdĨmy do omawiania kluczowej czĊĞci programu, czyli do obsáugi wątków. W chwili uruchomienia aplikacji tworzone są dwa wątki, z których aktywny moĪe byü tylko jeden lub Īaden. //uruchom pierwszy wątek sprawdzania pisowni watek1 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PisowniaPierwszaDuza, hwndEdytora, 0, p); //uruchom drugi wątek sprawdzania pisowni watek2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PisowniaWszystkieDuze, hwndEdytora, 0, p); //wstrzymaj dziaáanie wątku drugiego SuspendThread(watek2); //ustaw wskaĨnik uruchomionego wątku flaga = 1; Zmienna globalna flaga sáuĪy do zaznaczenia aktywnoĞci wątku. Przeáączanie aktyw- noĞci wątków, zgodnie z konfiguracją przycisków klasy BS_AUTORADIOBUTTON, zostaáo zaimplementowane w kodzie obsáugi komunikatu WM_COMMAND. case WM_COMMAND: switch(LOWORD(wParam)) { case 1001: switch(flaga) { case 2: //wstrzymaj dziaáanie wątku drugiego SuspendThread(watek2); Rozdziaä 8. i Projektowanie aplikacji wielowñtkowych 193 case 3: //początek nowego formatowania ustawiany jest //na ostatni wprowadzony znak GetWindowText(hwndEdytora, tekst, MaxT); pocz_format = strlen(tekst); //ponownie uruchom wątek pierwszy ResumeThread(watek1); break; } flaga = 1; break; case 1002: switch(flaga) { case 1: //wstrzymaj dziaáanie wątku pierwszego SuspendThread(watek1); case 3: //początek nowego formatowania ustawiany jest na ostatni wprowadzony znak GetWindowText(hwndEdytora, tekst, MaxT); pocz_format = strlen(tekst); //ponownie uruchom wątek drugi ResumeThread(watek2); break; } flaga = 2; break; case 1003: switch(flaga) { case 1: //wstrzymaj dziaáanie wątku pierwszego SuspendThread(watek1); break; case 2: //wstrzymaj dziaáanie wątku drugiego SuspendThread(watek2); break; } flaga = 3; break; case 1010: SendMessage(hwnd, WM_DESTROY, 0, 0); break; } break; DziĊki uwzglĊdnieniu zmiennej pocz_poz modyfikacje tekstu nie wywoáują zmian w tekĞcie wpisanym przed wybraniem kolejnego sposobu formatowania. Staáe parametru wParam odpowiadają identyfikatorom okien potomnych klasy BS_AUTORADIOBUTTON. Aktywny wątek co sekundĊ sprawdza wpisany tekst, generując zmiany zgodne z wybra- nym sposobem formatowania. DziĊki zastosowanym wątkom kod pliku Program22_ main.cpp nie jest skomplikowany, w caáoĞci wygląda nastĊpująco: 194 CzöĈè II i Dla Ĉredniozaawansowanych #include windows.h const int MaxT = 0xFFFF; char tekst[MaxT]; /* znacznik uruchomionego wątku*/ int flaga; /* Uchwyty wątków*/ HANDLE watek1, watek2; /* wskaĨnik początku formatowania tekstu */ int pocz_format; /* Deklaracja funkcji wątku */ DWORD PisowniaPierwszaDuza(LPVOID parametr); DWORD PisowniaWszystkieDuze(LPVOID parametr); LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM); char szClassName[ ] = WindowsApp ; int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nFunsterStil) { HWND hwnd; MSG messages; WNDCLASSEX wincl; wincl.hInstance = hThisInstance; wincl.lpszClassName = szClassName; wincl.lpfnWndProc = WindowProcedure; wincl.style = CS_DBLCLKS; wincl.cbSize = sizeof (WNDCLASSEX); wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION); wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION); wincl.hCursor = LoadCursor (NULL, IDC_ARROW); wincl.lpszMenuName = NULL; wincl.cbClsExtra = 0; wincl.cbWndExtra = 0; wincl.hbrBackground = (HBRUSH) COLOR_BTNSHADOW; if (!RegisterClassEx ( wincl)) return 0; hwnd = CreateWindowEx ( 0, szClassName, Program22 , WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 410, 375, HWND_DESKTOP, NULL, hThisInstance, Rozdziaä 8. i Projektowanie aplikacji wielowñtkowych 195 NULL ); ShowWindow (hwnd, nFunsterStil); while (GetMessage ( messages, NULL, 0, 0)) { TranslateMessage( messages); DispatchMessage( messages); } return messages.wParam; } LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, ´LPARAM lParam) { DWORD p; switch (message) { case WM_CREATE: static HINSTANCE hInst = (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE); //utwórz okno edycji static HWND hwndEdytora = CreateWindowEx( 0, EDIT , NULL, WS_VISIBLE | WS_CHILD | WS_BORDER | ES_MULTILINE | WS_VSCROLL | ´WS_HSCROLL, 0, 0, 400, 200, hwnd, NULL, hInst, NULL); CreateWindowEx(0, BUTTON , Opcje formatowania tekstu , WS_VISIBLE | WS_CHILD | BS_GROUPBOX, 5, 210, 220, 110, hwnd, NULL, hInst, NULL); static HWND hwndRadio1 = CreateWindowEx( 0, BUTTON , Duľe pierwsze litery , WS_VISIBLE | WS_CHILD | WS_GROUP | BS_AUTORADIOBUTTON, 10, 240, 170, 20, hwnd, (HMENU)1001, hInst, NULL); static HWND hwndRadio2 = CreateWindowEx( 0, BUTTON , Duľe wszystkie litery , WS_VISIBLE | WS_CHILD | BS_AUTORADIOBUTTON, 10, 260, 170, 20, hwnd, (HMENU)1002, hInst, NULL); static HWND hwndRadio3 = CreateWindowEx( 0, BUTTON , Bez modyfikacji , WS_VISIBLE | WS_CHILD | BS_AUTORADIOBUTTON, 10, 280, 170, 20, hwnd, (HMENU)1003, hInst, NULL); CreateWindowEx(0, BUTTON , Koniec , WS_VISIBLE | WS_CHILD, 260, 230, 110, 80, hwnd, (HMENU)1010, hInst, NULL); //uruchom pierwszy wątek sprawdzania pisowni watek1 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PisowniaPierwszaDuza, hwndEdytora, 0, p); 196 CzöĈè II i Dla Ĉredniozaawansowanych //uruchom drugi wątek sprawdzania pisowni watek2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PisowniaWszystkieDuze, hwndEdytora, 0, p); //wstrzymaj dziaáanie wątku drugiego SuspendThread(watek2); //ustaw wskaĨnik uruchomionego wątku flaga = 1; //ustaw domyĞlną opcjĊ SendMessage(hwndRadio1, BM_SETCHECK, 1001, 0); //ustaw zakres zmian tekstu pocz_format = 0; break; case WM_COMMAND: switch(LOWORD(wParam)) { case 1001: switch(flaga) { case 2: //wstrzymaj dziaáanie wątku drugiego SuspendThread(watek2); case 3: //początek nowego formatowania ustawiany jest na ostatni //wprowadzony znak GetWindowText(hwndEdytora, tekst, MaxT); pocz_format = strlen(tekst); //ponownie uruchom wątek pierwszy ResumeThread(watek1); break; } flaga = 1; break; case 1002: switch(flaga) { case 1: //wstrzymaj dziaáanie wątku pierwszego SuspendThread(watek1); case 3: //początek nowego formatowania ustawiany jest na ostatni //wprowadzony znak GetWindowText(hwndEdytora, tekst, MaxT); pocz_format = strlen(tekst); //ponownie uruchom wątek drugi ResumeThread(watek2); break; } flaga = 2; break; case 1003: switch(flaga) { case 1: //wstrzymaj dziaáanie wątku pierwszego SuspendThread(watek1); break; Rozdziaä 8. i Projektowanie aplikacji wielowñtkowych 197 case 2: //wstrzymaj dziaáanie wątku drugiego SuspendThread(watek2); break; } flaga = 3; break; case 1010: SendMessage(hwnd, WM_DESTROY, 0, 0); break; } break; case WM_DESTROY: PostQuitMessage (0); break; default: return DefWindowProc (hwnd, message, wParam, lParam); } return 0; } DWORD PisowniaPierwszaDuza(LPVOID parametr) { int i, pozycja; //dziaáaj aĪ do odwoáania while(TRUE) { //pobierz tekst okna GetWindowText((HWND)parametr, tekst, MaxT); //popraw tekst i = pocz_format; while(tekst[i]) { //postaw duĪą literĊ, gdy jest to pierwszy znak if((i==pocz_format) (tekst[i] =97 tekst[i] =122)) tekst[i] -= 32; //lub jest to znak po spacji, lub znaku nowej linii if(tekst[i]==32||tekst[i]== \n ) { if(tekst[i+1] =97 tekst[i+1] =122) tekst[i+1] -= 32; //sprawdĨ polskie znaki if(tekst[i+1]== æ ) tekst[i+1] = ä ; if(tekst[i+1]== è ) tekst[i+1] = ç ; if(tekst[i+1]== ú ) tekst[i+1] = ù ; if(tekst[i+1]== đ ) tekst[i+1] = Đ ; if(tekst[i+1]== ē ) tekst[i+1] = Ē ; if(tekst[i+1]== ó ) tekst[i+1] = Ó ; if(tekst[i+1]== ħ ) tekst[i+1] = Ħ ; if(tekst[i+1]== ļ ) tekst[i+1] = Ļ ; if(tekst[i+1]== ľ ) tekst[i+1] = Ľ ; } //zwiĊksz licznik i++; 198 CzöĈè II i Dla Ĉredniozaawansowanych } //wyĞlij poprawiony tekst do okna edycji SetWindowText((HWND)parametr, tekst); //ustaw karetkĊ na koniec tekstu SendMessage((HWND)parametr, EM_SETSEL, i, i); //zaczekaj Sleep(1000); } return 0; } DWORD PisowniaWszystkieDuze(LPVOID parametr) { int i, pozycja; //dziaáaj aĪ do odwoáania while(TRUE) { //pobierz tekst okna GetWindowText((HWND)parametr, tekst, MaxT); //popraw tekst i = pocz_format; while(tekst[i]) { //postaw duĪą literĊ w miejsce maáej if(tekst[i] =97 tekst[i] =122) tekst[i] -= 32; //sprawdĨ polskie znaki if(tekst[i]== æ ) tekst[i] = ä ; if(tekst[i]== è ) tekst[i] = ç ; if(tekst[i]== ú ) tekst[i] = ù ; if(tekst[i]== đ ) tekst[i] = Đ ; if(tekst[i]== ē ) tekst[i] = Ē ; if(tekst[i]== ó ) tekst[i] = Ó ; if(tekst[i]== ħ ) tekst[i] = Ħ ; if(tekst[i]== ļ ) tekst[i] = Ļ ; if(tekst[i]== ľ ) tekst[i] = Ľ ; //zwiĊksz licznik i++; } //wyĞlij poprawiony tekst do okna edycji SetWindowText((HWND)parametr, tekst); //ustaw karetkĊ na koniec tekstu SendMessage((HWND)parametr, EM_SETSEL, i, i); //zaczekaj Sleep(1000); } return 0; } Rozdziaä 8. i Projektowanie aplikacji wielowñtkowych 199 Okno gáówne dziaáającej aplikacji prezentujĊ na rysunku 8.5. W celu zilustrowania dzia- áania programu wykorzystaáem fragment wspaniaáej powieĞci Arkadija i Borysa Stru- gackich Poniedziaáek zaczyna siĊ w sobotĊ1. Rysunek 8.5. Okno gáówne aplikacji Program22 8.4. çwiczenia ûwiczenie 13. Zaprojektuj aplikacjĊ, w której w obszarze roboczym okna wyĞwietlany bĊdzie czas systemowy. W lewej czĊĞci obszaru okna czas bĊdzie wyĞwietlany w postaci cyfrowej, prawą czĊĞü okna zajmie tarcza zegara o klasycznej postaci (patrz rysunek 8.6). Do obsáugi zegarów uĪyj dwóch wątków. Rysunek 8.6. Okno gáówne aplikacji Cwiczenie13 ûwiczenie 14. Uruchomione w aplikacji Program22 wątki sáuĪyáy do automatycznego poprawiania pisowni w edytorze tekstowym. Zaprojektuj podobną aplikacjĊ dziaáającą w edytorze graficznym, w której uruchomiony wątek bĊdzie zamieniaá narysowane linie krzywe w linie proste (sposób dziaáania programu zilustrowano na rysunku 8.7). 1 Arkadij Strugacki, Borys Strugacki, Poniedziaáek zaczyna siĊ w sobotĊ, táum. Irena Piotrowska, Warszawa 1970, s. 17. 200 CzöĈè II i Dla Ĉredniozaawansowanych Rysunek 8.7. Ilustracja dziaáania programu Cwiczenie14
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Tworzenie aplikacji dla Windows. Od prostych programów do gier komputerowych
Autor:

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: