1 WSTĘP. Źródło : http://www.winapi.rox.pl/ 1.1 Wymagania i terminologia



Podobne dokumenty
Procedura okna: LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);

Programowanie na poziomie sprzętu. Programowanie w Windows API

Programowanie w języku C++ z użyciem Windows API

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

Wskaźnik może wskazywać na jakąś zmienną, strukturę, tablicę a nawet funkcję. Oto podstawowe operatory niezbędne do operowania wskaźnikami:

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

1 Podstawy c++ w pigułce.

Rozdział II. Praca z systemem operacyjnym

Czym są właściwości. Poprawne projektowanie klas

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

Jak napisać program obliczający pola powierzchni różnych figur płaskich?

Projektowanie i programowanie aplikacji biznesowych. Wykład 2

Niezwykłe tablice Poznane typy danych pozwalają przechowywać pojedyncze liczby. Dzięki tablicom zgromadzimy wiele wartości w jednym miejscu.

Podstawy programowania skrót z wykładów:

Laboratorium 1 Temat: Przygotowanie środowiska programistycznego. Poznanie edytora. Kompilacja i uruchomienie prostych programów przykładowych.

Informacje ogólne. Karol Trybulec p-programowanie.pl 1. 2 // cialo klasy. class osoba { string imie; string nazwisko; int wiek; int wzrost;

1 Podstawy c++ w pigułce.

PROE wykład 2 operacje na wskaźnikach. dr inż. Jacek Naruniec

Wykład 8: klasy cz. 4

Programowanie obiektowe

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu.

JAVA W SUPER EXPRESOWEJ PIGUŁCE

Programowanie Strukturalne i Obiektowe Słownik podstawowych pojęć 1 z 5 Opracował Jan T. Biernat

Podstawy Programowania Obiektowego

Wprowadzenie do biblioteki klas C++

Cwiczenie nr 1 Pierwszy program w języku C na mikrokontroler AVR

Przydziały (limity) pojemności dyskowej

Podstawy programowania. Wykład: 12. Struktury, unie, pola bitowe. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.

Java. język programowania obiektowego. Programowanie w językach wysokiego poziomu. mgr inż. Anna Wawszczak

Praktycznie całe zamieszanie dotyczące konwencji wywoływania funkcji kręci się w okół wskaźnika stosu.

1 Wskaźniki. 1.1 Główne zastosowania wskaźników

Podstawy programowania, Poniedziałek , 8-10 Projekt, część 1

Informatyka II. Laboratorium Aplikacja okienkowa

Lab 9 Podstawy Programowania

Instrukcja laboratoryjna cz.3

Język ludzki kod maszynowy

Jeśli chcesz łatwo i szybko opanować podstawy C++, sięgnij po tę książkę.

Pytania sprawdzające wiedzę z programowania C++

Podstawy Informatyki. Inżynieria Ciepła, I rok. Wykład 10 Kurs C++

Podstawy programowania w C++

Stawiamy pierwsze kroki

Zasady programowania Dokumentacja

Wskaźniki w C. Anna Gogolińska

Komputery I (2) Panel sterowania:

Obiektowy PHP. Czym jest obiekt? Definicja klasy. Składowe klasy pola i metody

SYSTEMY OPERACYJNE I SIECI KOMPUTEROWE

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

5.4. Tworzymy formularze

Część 4 życie programu

Programowanie dla początkujących w 24 godziny / Greg Perry, Dean Miller. Gliwice, cop Spis treści

Ok. Rozbijmy to na czynniki pierwsze, pomijając fragmenty, które już znamy:

1. Wartość, jaką odczytuje się z obszaru przydzielonego obiektowi to: a) I - wartość b) definicja obiektu c) typ oboektu d) p - wartość

W dowolnym momencie można zmienić typ wskaźnika.

Wykład VII. Programowanie. dr inż. Janusz Słupik. Gliwice, Wydział Matematyki Stosowanej Politechniki Śląskiej. c Copyright 2014 Janusz Słupik

Tworzenie prezentacji w MS PowerPoint

Uniwersytet Zielonogórski Instytut Sterowania i Systemów Informatycznych. Ćwiczenie 3 stos Laboratorium Metod i Języków Programowania

Deklaracja struktury w C++

Strona główna. Strona tytułowa. Programowanie. Spis treści. Sobera Jolanta Strona 1 z 26. Powrót. Full Screen. Zamknij.

Podstawy technologii cyfrowej i komputerów

ZASADY PROGRAMOWANIA KOMPUTERÓW

5.2. Pierwsze kroki z bazami danych

Instalacja systemu zarządzania treścią (CMS): Joomla

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki

Diagram klas UML jest statycznym diagramem, przedstawiającym strukturę aplikacji bądź systemu w paradygmacie programowania obiektowego.

Wykład 1: Wskaźniki i zmienne dynamiczne

I - Microsoft Visual Studio C++

Programowanie w języku C++

Makropolecenia w PowerPoint Spis treści

Podstawy programowania w języku C dla środowiska Windows

Nr: 15. Tytuł: Kancelaris w systemie Windows 8 i Windows 8.1. Data modyfikacji:

Część XV C++ Ćwiczenie 1

/* dołączenie pliku nagłówkowego zawierającego deklaracje symboli dla wykorzystywanego mikrokontrolera */ #include <aduc834.h>

C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów

Praca w środowisku Visual Studio 2008, Visual C

Wykład 9: Metody wirtualne i polimorfizm

LeftHand Sp. z o. o.

Wykład 5: Klasy cz. 3

Wykład 9: Polimorfizm i klasy wirtualne

Podstawy informatyki. Elektrotechnika I rok. Język C++ Operacje na danych - wskaźniki Instrukcja do ćwiczenia

Szablony klas, zastosowanie szablonów w programach

Utworzenie pliku. Dowiesz się:

Szablony funkcji i klas (templates)

Dr inż. Grażyna KRUPIŃSKA. D-10 pokój 227 WYKŁAD 7 WSTĘP DO INFORMATYKI

Widoczność zmiennych Czy wartości każdej zmiennej można zmieniać w dowolnym miejscu kodu? Czy można zadeklarować dwie zmienne o takich samych nazwach?

Programowanie C++ Wykład 2 - podstawy języka C++ dr inż. Jakub Możaryn. Warszawa, Instytut Automatyki i Robotyki

JAVA. Java jest wszechstronnym językiem programowania, zorientowanym. apletów oraz samodzielnych aplikacji.

Politechnika Poznańska Wydział Budowy Maszyn i Zarządzania

Podręcznik użytkownika programu. Ceremonia 3.1

Techniki programowania INP001002Wl rok akademicki 2018/19 semestr letni. Wykład 3. Karol Tarnowski A-1 p.

Zacznijmy więc pracę z repozytorium. Pierwsza konieczna rzecz do rozpoczęcia pracy z repozytorium, to zalogowanie się w serwisie:

Wskaźniki i dynamiczna alokacja pamięci. Spotkanie 4. Wskaźniki. Dynamiczna alokacja pamięci. Przykłady

Jak zawsze wyjdziemy od terminologii. While oznacza dopóki, podczas gdy. Pętla while jest

Dodanie nowej formy do projektu polega na:

Przewodnik... Tworzenie Landing Page

Makropolecenia w Excelu

CZĘŚĆ A PIERWSZE KROKI Z KOMPUTEREM

PROE wykład 3 klasa string, przeciążanie funkcji, operatory. dr inż. Jacek Naruniec

2 Przygotował: mgr inż. Maciej Lasota

Transkrypt:

1 Źródło : http://www.winapi.rox.pl/ 1 WSTĘP 1.1 Wymagania i terminologia Zakładam, że do tej pory dobrze poznałeś języki C/C++. Potrafisz zrobić całkiem ciekawą aplikację wyświetlaną w środowisku konsolowym. Może nadszedł czas na coś więcej? Pisanie większych programów w tym interfejsie graficznym może być uciążliwe dla użytkownika korzystającego z naszego programu. W końcu klikanie na oknie jest dużo wygodniejsze i praktyczniejsze niż wypisywanie poleceń tekstowych. Programy działające pod systemem Windows mają swoje charakterystyczne zasady działania, które postaram się bliżej przedstawić w miarę najprzejrzystszym sposobem. Programy pisane z użyciem interfejsu WinAPI nie są przenośne na inne platformy oprócz Windows. Windows API nie ma przenośności tak jak biblioteki standardowe C/C++, nazwy używanych funkcji dostępne są tylko pod Windows. Co prawda pod Linuksem istnieją programy emulujące programy Windows, ale ich użycie jest ograniczone. W niniejszej publikacji zaprezentuję Windows API w językach C oraz C++. Nie będę tłumaczył elementów pochodzących z języków C i C++, dlatego dla pełnego zrozumienia działania mechanizmów oraz kodów tu zawartych, czytelnik musi posiadać podstawową wiedzę na temat programowania w języku C, czyli strukturalną część języka C++. Nie wymagam od użytkownika znajomości bibliotek standardowych C i C++, ale jedynie wbudowanych elementów w składnię języka i umiejętności takich jak: Znajomość podstawowych typów zmiennych wbudowanych w składnię C i C++ Znajomość podstawowych operatorów wbudowanych w składnię C i C++ Posługiwanie się operatorami bitowymi (binarnymi) C i C++ (np. & ~) Znajomość instrukcji warunkowych, w tym opanowanie instrukcji typu switch Biegłe posługiwanie się pętlami while i for Biegłe opanowanie posługiwania się wskaźnikami i dynamicznym przydziałem pamięci(głównie operatory new i delete) Podstawową znajomość na temat konwencji wywołań funkcji pascalowych ( stdcall) i typu C ( cdecl) Biegłe posługiwanie się tablicami, przydzielonymi statycznie i dynamicznie Znajomość teorii na temat kodowania znaków ASCII i UNICODE, w tym znajomość typów char i wchar_t Biegłe posługiwanie się strukturami i uniami Znajomość podstawowych poleceń preprocesora (np. include, define, ifdef, itd.) Biegłe posługiwanie się dwoma systemami liczbowymi: dziesiątkowym(dec) i szesnastkowym(hex) Rozumienie technicznego działania pamięci, oraz ułożenia w niej bajtowych wartości zmiennych.

2 Większość Windows API napisana jest w C, dlatego nie jest wymagana znajomość programowania obiektowego w C++, niemniej jednak są nowsze elementy rozszerzające funkcjonalność Windows API, np. GDI+ i DirectShow, które napisane są już z użyciem obiektowych klas C++. Dlatego zakładam, także, że czytelnik powinien znać podstawy programowania obiektowego: Tworzenie i niszczenie obiektów klas C++ Praca z klasami C++, rozróżnienie zasięgów Wywoływanie metod klas (przez wskaźnik oraz statycznie) Teorię dziedziczenia klas Po opanowaniu powyższego materiału, można rozpocząć poznawać interfejs Windows API. W przypadku nieopanowania, któregoś z powyższych punktów, czytelnik może nie rozumieć prezentowanych mechanizmów i kodów. Terminologia, której będę używał, a nie zawsze dla każdego, jest jednoznaczna: Bufor zarezerwowany fragment pamięci przez program, którego może używać, zwykle w formie tablicy, mając na niego wskaźnik. Bufor może być przeznaczony do przechowywania tekstu, ale także do innego typu danych. Obiekt mówiąc o obiektach najczęściej będzie mi chodzić o obiekty systemowe Windows, z którymi pracuje się w Windows API, a nie o obiekty klas języka C++. Kody źródłowe prezentowanych programów w tej publikacji, nie są w trybie domyślnego kodowania UNICODE. Jeżeli twoje środowisko domyślnie tworzy projekt z kodowaniem UNICODE, należy je wyłączyć, w przeciwnym razie wystąpią błędy podczas kompilacji. W Dev C++ ver. 4.9.9.2 domyślnie nie ma kodowania UNICODE, więc nie trzeba nic robić. W Visual C++ 2008 Express kodowanie UNICODE jest domyślnie włączane, dlatego po stworzeniu projektu należy je wyłączyć w opcjach projektu(alt+f7): pole Character Set ustawić na wartość Not Set.

3 1.2 Czym jest Windows API? Windows API zyskał tytuł interfejs. Interfejs w programowaniu to obiekt, który pośredniczy pomiędzy dwoma obiektami, które o sobie nic nie wiedzą, a odwołują się tylko przez wspomniany interfejs. Znajomość interfejsów w C++(właściwie to ich imitowania) nie jest wymagana do zrozumienia natury Windows API, ale odbiegając od tematu, jeśli nie wiesz znasz tej techniki warto zapoznać się z nią zapoznać. Wracając do tematu interfejsów, WinAPI spełnia także rolę takiego interfejsu i właśnie dlatego, temu API można nadać tytuł interfejs. Windows API pośredniczy pomiędzy nami, programistami piszącymi pod Windows, a samym systemem Windows. Powyższa definicja WinAPI na pewno nie jest wystarczająca. Trzeba sobie dokładniej wyjaśnić czym od strony technicznej jest Windows API. Dla kompilatora Windows API to zestaw plików nagłówkowych oraz bibliotek statycznych. W nich są zawarte przeróżne elementy języka C używane w Windows API. Owe API było pisane w języku C, ale język C++ jest zgodny z C, więc WinAPI bez problemu może być kompilowane w kompilatorach języka C++. 1.3 Pliki Windows API Jak już wspomniałem w skład Windows API wchodzą przede wszystkim pliki *.h i *.lib Jest ich bardzo dużo, dlatego nie będę ich wszystkich wypisywał. Co najwyżej wspomnę o najważniejszych: windows.h jest to główny plik nagłówkowy, który dołącza resztę najczęściej używanych plików nagłówkowych WinAPI. Dołączenie go już daje możliwość kompilacji aplikacji Windows. windef.h w tym pliku znajdują się deklaracje typów używanych w WinAPI. winbase.h znajdują się tu funkcje pochodzące z jądra Windows, wszystko co niezbędne do działania programu w systemie. winuser.h w tym pliku znajduje się implementacja interfejsu graficznego użytkownika, czyli dzięki niemu możemy pracować w okienkach Windows. wingdi.h plik, dzięki któremu mamy dostęp do standardowej biblioteki graficznej Windows, czyli możemy pomazać po oknie. wincon.h implementacja konsoli w systemie Windows. winnls.h obsługa wielu języków (national language support). winreg.h obsługa rejestru Windows. mmsystem.h multimedialna implementacja Windows. winsock.h implementacja komunikacji międzykomputerowej(internet). Oczywiście to tylko podstawowe pliki nagłówkowe, jak powiedziałem jest ich znacznie więcej. Kolejną rzeczą, bez której istnienie plików nagłówkowych nie miało by sensu, to biblioteki statyczne WinAPI. Ich też jest wiele, ale już nie tyle co plików nagłówkowych. Podstawowe pliki nagłówkowe mają swoje odpowiedniki w bibliotekach, np: winbase.h kernel32.lib winuser.h user32.lib wingdi.h gdi32.lib mmsystem.h winmm.lib Jeżeli środowisko(ide) w którym piszesz, wspomaga tworzenie aplikacji Windows, przy kompilacji projektu typu WinAPI, z pewnością dołączy podstawowe biblioteki statyczne Windows API. Weźmy pod lupę Visual C++ Express 2008. Przy tworzeniu programu z użyciem Windows API, domyślnie dołącza biblioteki:

4 Sami dołączać biblioteki musimy tylko gdy korzystamy z elementów Windows API rzadziej wykorzystywanych. W tym miejscu warto zwrócić uwagę na wersję plików nagłówkowych, które posiadamy. W najnowszą wersję Windows API można zaopatrzyć się pobierając pakiet ze strony Microsoft o nazwie Windows SDK. W nim będą znajdować się między innymi pliki Windows API. 1.4 Środowisko wspierające Windows API W poprzednim podrozdziale wspomniałem na temat środowiska wspierającego tworzenie projektów pod Windows (WinAPI). Środowisko takie automatycznie dołącza linkerowi podstawowe biblioteki statyczne Windows API, zwykle ma także gotowy szablon projektu Windows API. Przełączając pomiędzy projektem konsolowym a okienkowym(winapi), środowisko automatycznie ustawia w aplikacji pokazywanie bądź nie konsoli przy uruchomieniu. Projekt tworzony jako Windows API nie powinien podczas uruchamiania pokazywać konsoli. Zwykle środowisko w którym piszemy posiada własne pliki nagłówkowe i biblioteki statyczne Windows API, dzięki czemu nie musimy ich oddzielnie pobierać. Przykładowe programy w tej prezentacji są kompilowane w Visual C++ 2008 Express. 1.5 Działanie aplikacji Windows Czas powiedzieć parę słów na temat działania aplikacji w systemie Windows. Pisząc programy z użyciem interfejsu Windows API, korzystamy z funkcji systemu Windows. W plikach nagłówkowych są zdefiniowane funkcje WinAPI, wszystkie te funkcje binarnie znajdują się w plikach systemu, w tzw. bibliotekach DLL. Spójrzmy w systemowy katalog system32(bądź system na Windows starszym niż 2000), co widzimy? Gąszcz plików. Ale większość to pliki o rozszerzeniu *.dll, co to za pliki? Dokładniej sobie o nich pomówimy w dalszej części, jednak teraz powiem, że są to pliki z których korzystają aplikacje, w tych plikach(bibliotekach DLL) są ciała funkcji, z których mogą korzystać inne programy i tak się właśnie dzieje. Windows daje nam cały wachlarz przeróżnych funkcji, które możemy używać w swoich programach. Poznawanie Windows API będzie przede wszystkim polegać właśnie na poznaniu tych funkcji, które oferuje nam system Windows. Co ciekawe biblioteki statyczne które dołączamy do naszego programu, mają swoje odpowiedniki w bibliotekach DLL, kernel32.dll, user32.dll, gdi32.dll Czy te nazwy nie brzmią znajomo? Rzeczywiście biblioteki statyczne Windows API są odpowiednikami bibliotek DLL systemu i łączą nasz program z systemem Windows. Program, który napiszemy przy pomocy WinAPI, będzie w rzeczywistości wywoływał funkcje znajdujące się w bibliotekach systemu Windows. Przypomnij sobie teraz jak mówiłem, o tym, że WinAPI jest interfejsem. Widzisz związek? Pliki Windows API łączą nasz program z bibliotekami systemu Windows, czyli samym Windows em, stąd można śmiało powiedzieć, że Windows API jest interfejsem pomiędzy programem, a systemem operacyjnym. 1.6 Ewolucja Windows Jak wszystko na tym świecie, Windows nieustannie ewoluuje, wraz z kolejnymi wydaniami Windows dochodzą nowe możliwości. Jeżeli zmienia się Windows, razem z nim musi zmieniać się jego API. Najogólniej rzecz wygląda w ten sposób, że z każdą kolejną wersją Windows, dochodzą nowe funkcje i parametry jakie mogą przyjmować. W skutek czego w naszych bibliotekach Windows a siedzi jeszcze sporo funkcji z pierwszych 16bit-owych

5 Windows ów, Microsoft wszystko zachowuje w celu zgodności ze starszymi programami, niemniej jednak ostrzega, że takie stare elementy mogą zostać usunięte w najbliższych wydaniach Windows, dlatego z punktu widzenia programisty powinniśmy używać nowszych elementów Windows API. W ostatnich latach największa różnica nastąpiła pomiędzy Windows 98, a Windows 2000, gdyż w Windows 2000 wprowadzono wiele zmian w jądrze systemu, znacząco zmieniło się zarządzenie pamięcią i procesami przez Windows. Natomiast generalnie zmiany pomiędzy kolejnymi wydaniami systemów Windows nie są aż tak duże, aby zwykłe programy stawały się niekompatybilne z wcześniejszymi wersjami. Zwykle zmiany w kolejnych wydaniach systemów dotyczą jakiegoś konkretnego specyficznego zagadnienia i nie maja wpływu na działanie zwykłych programów. Generalnie zachowana jest zasada kompatybilności, polegająca na tym, że program pisany pod starszy system, np. Windows 2000, powinien poprawnie działać na nowszym, np. Windows XP. Dlatego, pisząc program pod najnowszy system np. Windows Vista, powinniśmy zakładać, że powinien on poprawnie działać na kolejnej wersji systemu Windows, której może jeszcze nie być. Co do działania nowszych programów, na starszych systemach, wszystko zależy od programu, jeżeli używa on elementów, np. funkcji, które jeszcze nie są dostępne na tym starszym systemie, nie zostanie uruchomiony lub nie będzie poprawnie działał. 1.7 Flagi Flaga jest pojęciem, z którym bardzo często spotykamy się w Windows API. Otóż, flagi to wartości sterujące, które przyjmuje funkcja(steruje funkcją). Postaram się je wytłumaczyć na przykładzie. Załóżmy, że mamy jakąś przykładową funkcję pokazującą okno, np. Show() : void Show(unsigned int flagi); Ma ona 1 parametr typu unisgned int, parametr ten będzie określał sposób działania funkcji, każdy bit będzie oznaczał jakiś stan opcji, włączony lub wyłączony. Załóżmy, że bit pierwszy będzie pokazywał okno z paskiem tytułu, drugi bit będzie określał czy na oknie ma być jakiś obrazek, bit trzeci tego parametru ustawiony na 1 będzie oznaczał pokazanie zmaksymalizowanego okna. Dla wygody definiujemy maski tych bitów: #define SHOW_PASEK_TYTULU 0x1 //bit pierwszy #define SHOW_POKAZUJ_OBRAZEK 0x2 //bit drugi #define SHOW_POKAZ_ZMAKSYMALIZOWANE 0x4 //bit trzeci Teraz podczas użycia funkcji wystarczy określić, których opcji(bitów) chcemy użyć. Dla przykładu powiedzmy, że chcemy pokazać okno z paskiem tytułu i zmaksymalizowane, łączymy maski operatorem : Show(SHOW_PASEK_TYTULU SHOW_POKAZ_ZMAKSYMALIZOWANE); Mianem flag w WinAPI określamy właśnie te wartości masek, które mają swoje zdefiniowane nazwy. Dzięki nazwom nie musimy pamiętać liczbowych wartości, które trzeba podawać, a wystarczy wpisać tylko nazwę flagi. Jak się przekonamy flag używa się w większości funkcji WinAPI. Broń Boże nie należy flag uczyć się na pamięć, w każdej chwili mamy dostępną dokumentację Windows API na stronie MSDN Library (biblioteka dla programistów oprogramowania Microsoft), z której należy korzystać. W większości przypadków, wystarczy wpisać nazwę funkcji w wyszukiwarce google, a pierwszym wynikiem powinna być dokumentacja funkcji w MSDN Library.

6 1.8 Uchwyty i obiektowa natura Windows API Jak już wcześniej wspomniałem WinAPI napisane jest w języku C, dlatego nie znajdziemy tam klas znanych z C++. Chociaż C jest językiem tylko strukturalnym, programiści piszący API Windows a uczynili coś dziwnego, stworzyli obiektowy interfejs w nie obiektowym języku. Programując w WinAPI będziemy tworzyć wirtualne obiekty w systemie. System daje nam całkiem spory wachlarz przeróżnych obiektów. Nawet każdy uruchomiony program to typ obiektu istniejącego w systemie, każdy program jest pewnym wirtualnym obiektem w Windows API. Co ciekawe z poziomu aplikacji Windows API nie ma dostępu do takiego obiektu, nie można dostać się do jego wewnętrznych danych. Porównując to do klas C++, sam obiekt jest częścią prywatną dla programisty. Skoro nie ma dostępu do obiektu, to jak go używać? Dobre pytanie. Windows API jest wyposażony w mechanizm uchwytów. Uchwyt to nic innego jak zwykły numer utworzonego obiektu w systemie, gdyż każdy utworzony obiekt w Windows API posiada swój niepowtarzalny uchwyt(numer, identyfikator). Funkcje Windows API używają właśnie uchwytów, do identyfikowania obiektów do których mają się odnosić. Praca z obiektami w programie wygląda w ten sposób, że my jako programiści, w naszym programie posiadamy uchwyt do obiektu(zwykłą liczbę) oraz mamy do dyspozycji funkcje Windows API, które służą do sterowania tymi obiektami, a jako jeden z parametrów(zwykle pierwszy) pobierają uchwyt obiektu z którym mają pracować. W C++ wywołuje się metody dla obiektów, w Windows API sprawa wygląda podobnie, tyle, że mamy zwykłe funkcje, które pobierają uchwyt obiektu. Zakładając, że mamy jakiś obiekt, np. okno i chcemy zmienić jego napis na pasku tytułu(tytuł okna). W C++ wyglądało by to tak, że mamy obiekt okna, oraz obiekt ten posiada metodę do modyfikowania tytułu okna: okno.zmientytul("nowy tytuł okna"); Natomiast w Windows API, będziemy mieli do dyspozycji funkcję do modyfikowania okna, oraz uchwyt okna(liczbę) któremu chcemy zmienić tytuł, funkcja w pierwszym parametrze pobierze uchwyt do modyfikowanego okna: ZmienTytul(okno,"Nowy tytuł okna"); Jak widać pomimo tego, że C jest językiem strukturalnym, programowanie w nim możemy nazwać wręcz obiektowym, które jest w kodzie zrealizowane w strukturalny sposób. Oto zobrazowany sposób pracy z obiektami w Windows API.

7 Jako programiści z poziomu(warstwy) programu, możemy utworzyć dowolny obiekt w Windows, poprzez wywołanie odpowiedniej funkcji WinAPI, w skutek czego dostajemy do naszego programu jedynie uchwyt stworzonego obiektu(liczbę), a stworzonym obiektem możemy sterować jedynie poprzez funkcje API do tego przeznaczone, które potrzebują uchwytu obiektu, którym mają sterować. Ten obrazek doskonale oddaje fakt, tego że Windows API jest interfejsem(pośrednikiem) pomiędzy aplikacjami, a obiektami w systemie Windows. Pomówmy teraz o obiektach bardziej konkretnie. Jak wiesz jednym z obiektów w systemie jest program. Windows oferuje całe mnóstwo innych obiektów, poznawanie WinAPI będzie polegać na poznawaniu obiektów oferowanych przez Windows, oraz funkcji dzięki, którym można z nimi pracować. Pierwszym obiektem, który dokładnie poznasz będzie obiekt okna Windows. Każde okno wyświetlane które widzisz na pulpicie to nic innego jak obiekt systemowy, który tworzy program i nim steruje. A oto kilka przykładowych obiektów, które poznasz: Okno Windows Menu okna Ikona Kursor Moduł(program) 1.9 Model zdarzeniowy aplikacji Windows Kolejną nowością w stosunku do programów konsolowych jest sposób działania, aplikacji Windows(model). Programy konsolowe z reguły pisze się według modelu liniowego. Polega on na tym, że cały kod programu jest realizowany po kolei, od początku do końca funkcji main(), wszystkie operacje są realizowane w sposób ciągły. Nawet gdy zrobimy pętlę w funkcji main(), która będzie wykonywała się dopóki użytkownik nie zażyczy sobie jej przerwania, to kod w pętli jest także stały, więc będzie się wykonywać zawsze ta sama czynność. Taki model wykonywania programu nie jest efektywny, dlatego w aplikacji Windows, z pomocą funkcji Windows API realizuje się inny model. Pewnie jesteś ciekawy jak można inaczej wykonywać program jeśli nie liniowo? Otóż w programowaniu zdarzeniowym istnieje takie pojęcie jak zdarzenie(ang. event ). Pisanie programu w tym modelu opiera się na obsłudze zdarzeń, które dzieją się podczas działania programu. Dla przykładu takim zdarzeniem może być kliknięcie użytkownika, czy naciśnięcie przycisku na klawiaturze. Teraz pytanie, które powinno się nasunąć to: ile może być takich zdarzeń? Właściwie to sam nie jestem w stanie odpowiedzieć na to pytanie, szczególnie, że wraz z kolejnymi wydaniami Windows pojawiają się nowe, ponadto użytkownik sam może definiować swoje własne zdarzenia, które mogą się dziać w systemie. Programista pisząc program w tym modelu, nie ma wpływu na kod, który będzie się wykonywał, gdyż wszystko zależy od użytkownika, przecież nie jesteśmy w stanie przewidzieć ile razy użytkownik kliknie myszą, czyli ile razy wykona się kod dla tego zdarzenia. Cała obsługa zdarzeń w aplikacji jest realizowana w pętli, w której znajdują się funkcje WinAPI realizujące obsługę zdarzeń. Dokładne techniczne omówienie tego modelu będzie później, na razie należy widzieć, że dzięki tej pętli nasz program obsługuje zdarzenia, które trafiają do naszego programu. Schemat budowy programu według tego modelu składa się z trzech etapów: Inicjalizacja programu Obsługiwanie zdarzeń Zwolnienie zasobów Te trzy etapy to schemat budowy funkcji głównej WinMain()(odpowiednik main() z konsoli), po uruchomieniu naszego programu, czyli wejścia w funkcję WinMain(), program najpierw powinien wykonywać kod tworzący obiekty niezbędne do działania programu, czyli np.

8 okno naszego programu. Etap ten wykonuje się tak jak w konsoli liniowo. Nazywa się go inicjalizacją programu. Po stworzeniu wszystkiego co niezbędne w programie, w WinMain() rozpoczyna się wykonywanie pętli, którą przed chwilą przedstawiłem. Jest to drugi etap życia programu i od teraz pętla będzie wykonywać kod zdarzeń które zaistnieją w naszym programie. Wykonywanie tego etapu(tej pętli) będzie trwać prawie przez cały czas istnienia naszego programu. Jeżeli użytkownik zażyczy sobie zamknąć program, zostanie przerwana pętla wykonywania zdarzeń i rozpocznie się ostatni etap życia WinMain(), czyli wykonywanie kodu pomiędzy pętlą, a wyjściem z WinMain(). Etap ten także jest realizowany liniowo. W tym miejscu powinniśmy zwolnić zasoby, których używał nasz program, spokojnie pozwalając mu na zamknięcie. 2 PODSTAWY 2.1 Typy zmiennych Windows API Omawianie API Windows a niewątpliwie należy rozpocząć od zapoznania się z nowymi zdefiniowanymi typami. Wielu początkujących programistów widząc pierwszy raz kod stworzenia okna w Windows API, otwiera szeroko oczy i zastanawia się czy to co widzą to C++, który do tej pory znali? Jedną z przyczyn takiego stanu rzeczy jest właśnie fakt istnienia własnych typów Windows API. Z definicji w WinAPI wszystkie typy mają mieć nazwy dużymi literami, dlatego nazwom typów z C/C++ nadano nazwy z dużych liter. W plikach nagłówkowych Windows API będzie można odnaleźć takie deklaracje: typedef int INT; typedef short SHORT; typedef long LONG; typedef float FLOAT; typedef double DOUBLE; typedef char CHAR;//znak kodowania ASCII typedef wchar_t WCHAR;//znak kdowoania UNICODE-16 #define VOID void //w przypadku typu void i const użyta jest makrodefinicja #define CONST const Dodatkowo dla wygody programistów C i C++ zdefiniowano krótsze nazwy dla typów bez znaku(unsigned): typedef unsigned int UINT; //unsigned jest krótkim U typedef unsigned short WORD; //wartość 16-bitowa, tzw. słowo typedef unsigned short USHORT; typedef unsigned long DWORD; //ten typ nazywa się dwusłowo (double word) typedef unsigned long ULONG; typedef unsigned char BYTE; //tutaj użyto słowa bajt typedef unsigned char UCHAR; W odmienny sposób w WinAPI zdefiniowany jest typ logiczny bool : typedef int BOOL; #define TRUE 1 #define FALSE 0 typedef BYTE BOOLEAN;

9 Jak widać typ BOOL to wcale nie bool z C++. Dlaczego tak? Prawdopodobnie w celach optymalizacyjnych, procesor będzie szybciej pracował na typie int niż jednobajtowym bool. Natomiast w celu użycia jednobajtowego bool zdefiniowano typ BOOLEAN. Oczywiście true i false także mogą być pisane z dużych liter. #define NULL 0 Wartości 0 nadano także charakterystyczną nazwę NULL. Jest ona symbolicznie(umownie) używana jako wartość pustego wskaźnika, oraz pustego uchwytu(co to jest będzie za chwilę). W Windows API zdefiniowano całą gamę nazw dla słówka kluczowego C++ stdcall, czyli pascalowej konwencji wywołania funkcji. Wszystkie funkcje WinAPI używają właśnie tej konwencji, a oto definicje stdcall, które można napotkać w Windows API: #define WINAPI stdcall #define CALLBACK stdcall #define APIPRIVATE stdcall #define PASCAL stdcall #define APIENTRY WINAPI #define WINAPI_INLINE WINAPI #define WINAPIV cdecl //definicja konwencji C W WinAPI zdefiniowano także typy wskaźnikowe. Przyjęło się zasadę, że typy wskaźnikowe zaczynają się na P (pointer - wskaźnik) lub LP : typedef int* PINT; typedef int* LPINT; typedef unsigned int* PUINT; typedef UINT* LPUINT; typedef long* LPLONG; typedef LONG* PLONG; typedef FLOAT* PFLOAT; typedef CHAR *PCHAR, *LPCH, *PCH; //LPCH i PCH rzadko się spotyka typedef CHAR *LPSTR, *PSTR, *NPSTR; //wskaźniki są umownie buforami tekstu typedef CONST CHAR *LPCSTR, *PCSTR; //wskaźniki na stałe bufory tekstu typedef WCHAR *PWCHAR, *LPWCH, *PWCH; //odpowiedniki UNICODE typedef WCHAR *LPWSTR, *PWSTR, *NWPSTR; typedef CONST WCHAR *LPCWCHAR, *PCWCHAR; typedef BOOL* PBOOL; typedef BOOL* LPBOOL; typedef BOOLEAN* PBOOLEAN; typedef DWORD* PDWORD; typedef DWORD* LPDWORD; typedef WORD* PWORD; typedef WORD* LPWORD; typedef BYTE* PBYTE; typedef BYTE* LPBYTE; typedef void* PVOID; typedef void* LPVOID; typedef CONST void* LPCVOID; Osobiście, jak i pewnie większość programistów nie preferuję używania typów wskaźnikowych z WinAPI, używając dobrze nam znanego * jako typu wskaźnikowego, niemniej jednak warto wiedzieć o typach wskaźnikowych WinAPI. Kolejną grupą typów są tzw. uchwyty. W Windows API tworzymy pewne abstrakcyjne obiekty, do których odwołujemy się przez uchwyty do nich. Wspomniane uchwyty to po prostu wartości(identyfikatory) tych obiektów. W nazewnictwie uchwytów panuje zasada, że typy uchwytowe rozpoczynają się na H (handle - uchwyt). Poznawanie WinAPI to nic innego jak nauka posługiwania się takimi obiektami.

10 typedef void* HANDLE; //uchwyt do nieokreślonego obiektu Każdy rodzaj obiektu, posiada swój typ uchwytu. W trakcie poznawania obiektów Windows API, poznasz kolejne typy uchwytów. Obiekty Windows API o których mowa, nie mają nic wspólnego z obiektami klas języka C++. Windows API napisane jest w języku C, więc nie będzie mowy o obiektowych klasach w stylu C++. Używanie typów z Windows API nie jest przymusowe, możemy cały czas używać typów z C++, gdyż dopiero poznane typy WinAPI, są tak naprawdę tylko nowymi nazwami, a kompilatorowi jest kompletnie obojętne czy użyjemy typu int(z C/C++) czy INT(z WinAPI). Niemniej jednak należy zapoznać się z powyższymi definicjami typów Windows API, aby wiedzieć jak wyglądają ich odpowiedniki w C/C++. 2.2 Makra Windows API W plikach nagłówkowych WinAPI zdefiniowane są makra, które chcę omówić już na samym początku nauki. Pierwsza makrodefinicja, którą przedstawię to WIN32_LEAN_AND_MEAN. Należy ją definiować przed dołączeniem nagłówka windows.h. Definicja ta skraca czas kompilacji projektu z windows.h, przez nie dołączanie rzadziej używanych plików nagłówkowych. #define WIN32_LEAN_AND_MEAN //musi być przed windows.h #include <windows.h> WIN32_LEAN_AND_MEAN daje nieduże przyspieszenie. Obecnie istnieją inne, dużo skuteczniejsze sposoby na przyspieszanie kompilacji, np. technika użycia precompiled headers, która zmniejsza czas kompilacji prawie do minimum. Aczkolwiek nadal warto stosować definicję WIN32_LEAN_AND_MEAN. W Windows API istnieją również makra definiujące wersje systemu pod jakim domyślnie ma działać nasz program. Definicje te należy umieszczać również przed dołączeniem plików WinAPI(windows.h). Dla przykładu jeśli zdefiniujmy sobie makro, które określi wersje dla Windows 98, to jeżeli funkcja której chcemy użyć nie istnieje jeszcze w Win98, spowoduje to, że nie będzie ona dostępna także w definicjach plików Windows API, w skutek czego dostaniemy błąd debbugera, mówiący, że nie może znaleźć nazwy(funkcji której chcemy). Makrodefinicje te powinny określać wersję minimalną, na jakiej powinien pracować program. Makro WINVER określa wersję systemu Windows, natomiast makro _WIN32_IE określa wersję zainstalowanego Internet Explorer. Oto wartości, które odpowiadają poszczególnym wersjom systemu Windows: wersja systemu Windows Windows 95 / 98 / ME Windows 2000 Windows XP / Server 2003 Windows XP SP2 / Server 2003 SP1 Windows Vista / Server 2008 wartość WINVER 0x400 0x500 0x501 0x502 0x600

11 A oto wartości _WIN32_IE dla wersji Internet Explorer: wersja Internet Explorer Internet Explorer 5.0 Internet Explorer 5.01 Internet Explorer 5.5 Internet Explorer 6.0 Internet Explorer 6.0 SP1 Internet Explorer 6.0 SP2 Internet Explorer 7.0 wartość _WIN32_IE 0x500 0x501 0x550 0x600 0x601 0x603 0x700 W ramach przykładu załóżmy, że chcemy aby aplikacja działała na minimalnym systemie Windows 2000, oraz Internet Explorer 6.0 SP1: #define WINVER 0x500 #define _WIN32_IE 0x601 //makrodefinicje muszą być przed dołączeniem windows.h #include <windows.h> Ostatnią grupą pożytecznych makr, o których należy wiedzieć, są makra operujące na zmiennych. Pierwsze makra, które przedstawię to: MAKEWORD i MAKELONG. Służą one do tworzenia wartości zmiennej z dwóch innych mniejszych zakresem wartości. Na pierwszy ogień weźmy MAKEWORD. BYTE byte1, byte2; //dwie bajtowe liczby WORD word=makeword(byte1,byte2); //połączenie dwóch bajtów w jedną wartość Załóżmy, że mamy dowolne dwie liczby, typu jedno-bajtowego (BYTE = unsigned char). MAKEWORD możemy użyć jak funkcji. Jego działanie spowoduje połączenie dwóch wartości jedno-bajtowych w jedną dwu-bajtową, w ten sposób, że bajt odpowiadający mniejszym wartościom jest podawany jako pierwszym(byte1), natomiast bajt w WORD odpowiadający większym wartościom jako drugi parametr makra. W rzeczywistości MAKEWORD jest jedynie makrodefinicją odpowiednich przesunięć bitowych. Drugim niemal identycznym makrem jest MAKELONG. MAKELONG różni się od MAKEWORD jedynie tym, że operuje na 2x większych wartościach. Jako argumenty przyjmuje dwie wartości 16-bitowe, zwracając wartość 32-bitową. WORD word1=0x0566, word2=0x1233; LONG lng=makelong(word1,word2); //wartość lng będzie wynosić 0x12330566

12 Istnieje grupa makr działających odwrotnie do MAKEWORD i MAKELONG. Gdy te makra tworzą wartość z dwóch mniejszych wartości, inne z większej wartości wyciągają mniejsze porcje danych. WORD word=0x1428; BYTE lobyte=lobyte(word); //lobyte == 0x28 BYTE hibyte=hibyte(word); //hibyte == 0x14 Mając wartość 16-bitową(2-bajtową) makro LOBYTE zwróci wartość pierwszego bajta, opisującego niższe wartości, natomiast HIBYTE zwróci wartość drugiego bajta, opisującego wyższe wartości. Z wartości 32-bitowej można także wyciągnąć dwie wartości 16-bitowe makrami: LOWORD i HIWORD. DWORD dword=0xf0ac1428; WORD loword=loword(dword);//loword == 0x1428 WORD hiword=hiword(dword);//hiword == 0xf0ac Makra te okazują się być przydatne w wielu zastosowaniach. 2.3 Funkcja główna Pisząc w C++ z pewnością przyzwyczailiśmy się już do funkcji main(), wejściowej i wyjściowej w naszym programie. Windows API posiada własną funkcję główną, zastępującą main() z konsoli. Jej nazwa to WinMain(). Powinna być zbudowana w ten sposób: int WINAPI WinMain(HINSTANCE hinst,hinstance hpinst,lpstr lpcmd,int nshow) //kod programu return 0; Na pierwszy rzut oka wygląda dość skomplikowanie, ale zapewniam, że taka nie jest. Pierwszą rzeczą, która nam się rzuca w oczy, to większa ilość parametrów niż main(). Na pierwszy ogień bierzemy typ HINSTANCE. Cóż to za tajemniczy typ? Jego definicja: typedef HANDLE HINSTANCE; Typ ten zaczyna się na H, więc jest to uchwyt do obiektu, który tytułuje się INSTANCE (instancja). W roli wyjaśnień instancja aplikacji(modułu) to jej identyfikator w systemie Windows. Każdy uruchomiony program(moduł) dostaje swój oddzielny numer. Można więc powiedzieć, ze HINSTANCE to uchwyt aplikacji uruchomionej w systemie Windows. Jak widać uruchomione aplikacje(programy) w systemie z poziomu kodu to zwykłe obiekty Windows API.

13 Ale wróćmy do naszego pierwszego parametru hinst typu HINSTANCE w funkcji WinMain(), otóż jest uchwytem do naszej aplikacji, która właśnie została uruchomiona. Z parametru tego możemy odczytać uchwyt(identyfikator) naszej funkcji WinMain(programu) w systemie. Po co nam uchwyt naszej własnej aplikacji? Otóż czasami jest wymagany jako parametr do innych funkcji, dlatego warto go zachować jako zmienna globalna(lub przekazywać go do funkcji przez parametr). Uchwyt naszego programu(histance) w każdej chwili możemy także uzyskać wywołując funkcję GetModuleHandle() z parametrem NULL. hinst=getmodulehandle(null); Ale to nie koniec, drugi parametr WinMain() jest także typu HINSTANCE, ale obecnie się go nie używa. Jest on pozostałością z czasów Windows ów 16-bitowych. Obecnie ten parametr zawsze powinien wynosić NULL. Trzeci parametr WinMain() jest typu LPSTR. Czy pamiętasz co to za typ? Jego odpowiednik C++ to nic innego jak: char*, wskaźnik na tablice znaków(tekst). Tekst z tego parametru jest ciągiem znaków wprowadzonym jeszcze przed uruchomieniem naszego programu. W innym rozdziale dokładniej wyjaśnię jak się posługiwać w praktyce tym parametrem. Jeśli znasz znaczenie parametrów konsolowej funkcji main(), zdradzę, że ten parametr pełni identyczna rolę. Ostatni parametr WinMain() jest typu int. Argument ten określa sposób wyświetlania okna, zdefiniowany przez użytkownika. Jak wykorzystywać ten parametr zobaczymy już niebawem. Na razie może zobaczmy jak użytkownik komputera może wpływać na ten parametr. Mając skrót na aplikację, we właściwościach, na zakładce Skrót znajduje się pole Uruchom, to pole nie robi nic innego, jak określa wartość ostatniego parametru WinMain() podczas uruchamiania programu. Zwróć uwagę jeszcze na słówko WINAPI, pomiędzy int, a WinMain. Już o nim wspominałem, gdy mówiłem o typach, jest to definicja pascalowej konwencji wywołania funkcji(odpowiednik z C++: stdcall). Funkcje Windows API pracują właśnie w tej konwencji i system spodziewa się, że wejściowa WinMain() także będzie używać pascalowego wywołania funkcji, dlatego WinMain() musi być poprzedzone WINAPI( stdcall). Jeśli chodzi o wartość zwracaną przez WinMain() sytuacja tutaj się nie zmieniła w stosunku do main(), powinniśmy zwrócić 0, gdy nasz program zakończy się powodzeniem. Tym sposobem przebrnęliśmy przez nową funkcję główną programu. Jak widzisz jej budowa nie jest tak skomplikowana, jakby się mogło wydawać na początku. Po prostu

14 WinMain() przekazuje nam jeszcze oprócz tego co main(), identyfikator naszego programu w systemie Windows, oraz sposób pokazania okna i to cała tajemnica WinMain(). 2.4 Budowa okna Windows Czym byłby Windows bez swoich okien? Na czym byśmy dziaj klikali? Nie da się ukryć, że okno Windows jest podstawowym obiektem każdej aplikacji. Jako użytkownik systemu Windows nie sposób nie spotkać się z oknami i ich obsługą. Działające programy tworzą swoje okna, które wyglądają inaczej, pomimo tego, wszystkie one mają cechy wspólne, pochodzące z systemu. Dla użytkownika programu okno to główny element, dzięki któremu, komunikuje się z większością aplikacji. Natomiast z punktu widzenia programisty okno to kolejny obiekt systemu Windows, pracujący w programie. Wygląd okna Windows: Pasek tytułu charakterystyczny element okien. Jego wygląd zależy od ustawienia stylu graficznego w systemie. Pasek umożliwia przesuwanie okna użytkownikowi za pomocą myszki. Ikona grafika indywidualnie ustawiana z programu o wymiarach 16x16 pikseli. Zawsze znajduje się z lewej strony paska tytułu. Tytuł każde okno posiada swój własny ciąg znaków, określany jako tytuł okna, gdyż wyświetlany jest na pasku tytułu. Tytuł pełni także funkcję identyfikacyjną w systemie, posiadając jego tytuł, możemy odnaleźć go w systemie. Przyciski nawigacji przyciski znajdujące się na pasku tytułu, zawsze po jego prawej stronie. Rozróżniamy 3 podstawowe przyciski: minimalizacji, maksymalizacji i zamknięcia. Służą do modyfikowania stanu okna. Opcjonalnie istnieje jeszcze przycisk pomocy oznaczony jako?. Menu okno może być wyposażone w menu, z reguły tylko okna główne programu posiadają menu. Krawędź okno może posiadać krawędzie boczne, krawędź dolną, oraz jeżeli okno nie posiada paska tytułu, także górną. Krawędź może być jedynie elementem graficznym, jak również może posiadać właściwość zmiany rozmiarów okna. Powierzchnia robocza to powierzchnia przeznaczona dla programisty. Na niej powinien utworzyć resztę elementów przeznaczonych do pracy na oknie. Powierzchnia robocza często jest nazywana obszarem klienta.

15 Programista podczas tworzenia(z późniejszą możliwością zmian) określa cechy tworzonego okna, sposób jego działania i elementy z których będzie się składać. Najprostsze graficznie okno nie musi mieć żadnych elementów, może to być pusta przestrzeń robocza. 2.5 Hierarchizacja okien Niemal zawsze w systemie istnieje jednocześnie wiele okien. Wskutek tego musi istnieć pewna ich hierarchia dziedziczenia, nie każdy użytkownik komputera zdaje sobie sprawę z tego faktu, ale nieświadomie na pewno się z nim spotkał. Każde okno posiada swojego rodzica, czyli inne okno, które jest nad nim nadrzędne. Zależność pomiędzy rodzicem, a jego potomkiem polega na sposobie wyświetlania okna potomnego. Otóż tylko powierzchnia okna rodzica(nadrzędnego) jest miejscem w którym może być wyświetlane okno potomne. Okno główne posiada okno potomne o tytule Okno potomne, natomiast okno potomne posiada kolejne okno potomne. Jak widać na rysunku powierzchnia okna rodzica ogranicza obszar wyświetlania okna potomnego. Obrazowo można opisać to tak, że okno potomne będzie znajdować się tylko w środku, we wnętrzu okna rodzica. Oknem głównym w systemie jest sam pulpit. Jest to okno najniżej w hierarchii, po którym mogą dziedziczyć kolejne okna. Okno główne programu zwykle jest właśnie potomkiem pulpitu, tzn. można przesuwać je po pulpicie.

16 W systemie istnieje jeszcze inna hierarchia wynikająca z istnienia pierwszej, otóż jest to hierarchia okien równorzędnych. Ponieważ istnieje możliwość(co zdarza się często) utworzenia kilku okien potomnych z jednego okna rodzica, okna te w hierarchii dziedziczenia są równe, dlatego trzeba je, także ułożyć w hierarchii kolejności wyświetlania na ich wspólnym oknie rodzica. Hierarchia ta określa, które okna mają być najpierw wyświetlane. Dzięki tej hierarchii ułożenie okien wygląda bardziej trójwymiarowo, dlatego mówi się o niej jak o trzecim wymiarze Z okien. Modyfikując poprzedni rysunek hierarchii według dziedziczenia, trzeba dodać hierarchię wyświetlania dla okien równorzędnych(mających tego samego rodzica). Samo ułożenie tej hierarchii, można porównywać do stosu kartek leżących na sobie, każda kartka ma swoja pozycję w tym stosie, kartka na samej górze(najwyżej w hierarchii) jest widoczna w całości przed innymi kartkami, natomiast na samym dole(najniżej) będzie przykryta kartkami przed nią, tak samo zachowują się okna. Hierarchię dziedziczenia okien można modyfikować tylko podczas tworzenia i niszczenia okien, natomiast hierarchia wymiaru Z okien, zmienia się nieustannie. Wystarczy kliknąć na okno, a przeskoczy ono na najwyższe miejsce, stając się tym najbardziej widocznym, w swojej grupie. Hierarchię wymiaru Z okien można jeszcze rozdzielić na dwie oddzielne grupy, otóż jest podział na okna zwykłe, oraz okna ze statusem zawsze na wierzchu. Okna zawsze na wierzchu są zawsze przed oknami zwykłymi.

17 Okno zwykłe może maksymalnie przejść na najwyższe miejsce w ustawieniu okien zwykłych, okno zwykłe nie może nigdy przejść przed jakiekolwiek okno zawsze na wierzchu, natomiast okna zawsze na wierzchu, nigdy nie mogą schować się za oknami zwykłymi. 2.6 Klasa okna Po porcji teorii, przyszedł czas na rozpoczęcie tworzenia kodu pierwszego prawdziwego okna. Tworzenie okna nie jest rzeczą skomplikowaną, ale dość długą w zapisie. Otóż istnieją w systemie wirtualne klasy okien. Taka klasa określa wbudowane cechy okna, które z reguły podczas działania programu nie powinny się zmieniać. Jest ona w pewnym sensie szablonem okna. Tworząc taką klasę, może ona stać się klasą widzianą globalnie w całym systemie, dzięki czemu inne programy będą mogły także z niej korzystać, ale zwykle tworzy się ją jedynie o zasięgu lokalnym, czyli będzie widziana tylko w naszym programie. Programowo w Windows API taką klasę reprezentuje struktura WNDCLASSEX. struct UINT cbsize; UINT style; WNDPROC lpfnwndproc; int cbclsextra; int cbwndextra; HINSTANCE hinstance; HICON hicon; HCURSOR hcursor; HBRUSH hbrbackground; LPCSTR lpszmenuname; LPCSTR lpszclassname; HICON hiconsm; WNDCLASSEX, *PWNDCLASSEX; UINT cbsize Pole cbsize określa rozmiar tej struktury w bajtach. Zawsze należy przypisać tam wartość zwracaną przez operator sizeof() dla tej struktury. Takie pole określające rozmiar, występuje bardzo często w strukturach Windows API. Istnieje ono dlatego, żeby ustalić z ilu pól zbudowana struktura, ponieważ WNDCLASSEX nie zawsze miało te wszystkie pola. Wraz z postępem, ewolucją systemu Windows, dochodzą nowe pola do różnych struktur, wskutek czego program może być kompilowany dla Windows, w którym jeszcze nie było tych wszystkich pól, stąd jest potrzeba określenia długości struktury. UINT style

18 W tym polu określa się style okien zbudowanych z tej klasy. Style te dotyczą działania okna. Każdy styl zapisuje się za pomocą odpowiedniej flagi(mówiłem o nich wcześniej), a flagi(style) można łączyć operatorem sumy bitowej. Style zostaną omówione w następnym dziale. WNDPROC lpfnwndproc; Jest to wskaźnik na funkcję, obsługującą zdarzenia okien zbudowanych z tej klasy. Działanie tego pola jest kluczowe dla całej klasy okna. Domyślną funkcją w systemie obsługującą każdy komunikat w sposób domyślny, jest funkcja o nazwie DefWindowProc. int cbclsextra; W tym polu określa się wielkość dodatkowych danych dla tworzonej klasy okna. W rozdziale o zaawansowanej pracy z oknami nauczymy się jak się z tego pola korzysta. Na razie będziemy przypisywać mu zawsze wartość 0. int cbwndextra; To pole jest podobne w działaniu do powyższego, ale okno określa ilość dodatkowych danych dla każdego okna utworzonego z tej klasy. Dopóki nie będziemy wiedzieli jak z niego korzystać, przypisywać mu będziemy wartość 0. HINSTANCE hinstance; Ten typ mam nadzieję jest już doskonale znany, to uchwyt do programu. To pole określa w jakim programie znajduje się funkcja obsługująca zdarzenia. Powinno podawać się uchwyt do własnego programu, pobrany z parametru z WinMain() lub pobrany funkcją GetModuleHandle(). HICON hicon; HICON to uchwyt do obiektu ikony. To pole określa ikonę okien utworzonych z tej klasy. Ustawiając na NULL system automatycznie użyje domyślnej ikony. Dopóki nie poznamy obsługi zasobów, będziemy tu przypisywać NULL. HCURSOR hcursor; HCURSOR jest uchwytem do obiektu kursora. Każda klasa okien może dysponować swoim własnym kursorem. Będziemy tu przypisywać wartość zwróconą przez LoadCursor(NULL,IDC_ARROW), dokładniej funkcję poznamy przy omawianiu zasobów. HBRUSH hbrbackground; HBRUSH jest kolejnym uchwytem, tym razem do graficznego obiektu pędzla. Z tym obiektem zapoznamy się dopiero podczas omawiania graficznej części Windows API. Pole to określa pędzel jakim będzie rysowane tło okna utworzone z tej klasy. Można także użyć domyślnych wartości pochodzących z systemu. Domyślną wartością systemowego tła jest flaga: COLOR_BACKGROUND(flagę tą trzeba rzutować na HBRUSH). LPCSTR lpszmenuname; Ten parametr to nazwa menu, które będzie pokazywane na oknach utworzonych z tej klasy. Na razie będziemy podawali NULL, co oznacza, że nie wiążemy żadnego menu z klasą okna. LPCSTR lpszclassname;

19 Ten parametr określa nazwę tworzonej klasy okna. Klasy okna nie są typowymi obiektami Windows API, więc nie posiadają swoich uchwytów, a do identyfikacji w systemie używa się ich nazw. HICON hiconsm; Opcjonalnie oprócz dużej ikony z pola hicon, można także używać także małej ikony, lub podać NULL, wtedy wszędzie zostanie użyta ikona z pola hicon. Po wypełnieniu tej struktury. Należy ją zarejestrować w systemie, czyli zgłosić istnienie takiej klasy okien. Robi się to funkcją RegisterClassEx(): ATOM RegisterClassEx(CONST WNDCLASSEX *lpwcx); Funkcja przyjmuje wskaźnik na strukturę WNDCLASSEX, którą rejestruje w systemie. Zwracany ATOM jest identyfikatorem(innym niż uchwyt) klasy okna. Jeśli nie wynosi NULL, rejestracja okna przebiegła poprawnie, nasza klasa okna istnieje w systemie. Od odrejestrowywania zarejestrowanej już klasy w systemie, służy funkcja UnregisterClass(), jednak nie ma potrzeby jej wywoływania, gdyż wraz z wyjściem z programu system automatycznie odrejestruje zarejestrowane klasy. BOOL UnregisterClass(LPCTSTR lpclassname,hinstance hinstance); Parametr lpclassname to nazwa zarejestrowanej klasy, a hinstance to uchwyt do programu, który zarejestrował klasę, czyli parametr podany w polu hinstance struktury WNDCLASSEX. A oto przykładowy kod rejestracji klasy w systemie: //rejestracja klasy okna WNDCLASSEX wndcls;//struktura z klasą okna wndcls.cbsize=sizeof(wndclassex);//rozmiar struktury wndcls.style=0;//bez styli wndcls.lpfnwndproc=defwindowproc;//domyślna obsługa zdarzeń okna wndcls.cbclsextra=0;//brak dodatkowych danych wndcls.cbwndextra=0;//tu także wndcls.hinstance=hinst;//uchwyt programu, pobrany z parametru WinMain() wndcls.hicon=null;//ikona domyślna z systemu wndcls.hcursor=loadcursor(null,idc_arrow);//kursor także wndcls.hbrbackground=(hbrush)color_background;//domyslne tło okien wndcls.lpszmenuname=null;//brak menu wndcls.lpszclassname="example_classname";//nazwa klasy okna wndcls.hiconsm=null;//brak oddzielnej małej ikony RegisterClassEx(&wndcls);//rejestracja klasy 2.7 Tworzenie okna Mając zarejestrowaną klasę, okna można utworzyć systemowy obiekt okna. Jak wcześniej powiedziałem sama klasa okna jest jedynie szablonem, natomiast dopiero okno Windows jest obiektem. Ale okno powstaje na podstawie klasy okien. Porównując tworzenie okna z programowaniem obiektowym w C++, można powiedzieć, że klasa okna Windows jest tylko deklaracją klasy C++, a okno Windows jest jej obiektem utworzonym w pamięci. Funkcja tworząca okno to CreateWindowEx(). Przyjmuje aż 12 parametrów: HWND CreateWindowEx(DWORD dwexstyle,lpcstr lpclassname,lpcstr lpwindowname, DWORD dwstyle, int x,int y,int nwidth, int nheight,hwnd hwndparent, HMENU hmenu,hinstance hinstance,lpvoid lpparam);

20 DWORD dwexstyle Pierwszy parametr to rozszerzone style tworzonego okna. O stylach będzie w następnym rozdziale. LPCSTR lpclassname Ten parametr to nazwa klasy okna, z której zostanie utworzone okno. To ta klasa, którą wcześniej rejestrowaliśmy. Z takiej klasy możemy utworzyć dowolną ilość okien. LPCSTR lpwindowname Trzeci parametr określa nazwę(tytuł) okna. Tytuł ten będzie wyświetlany na pasku tytułu. DWORD dwstyle Ten parametr jest kombinacją flag określających podstawowe style wyglądu okna. Style będą omówione w następnym rozdziale. int x, int y Te 2 parametry określają miejsce w którym ma pojawić się okno. Wartości x i y są współrzędnymi układu pikseli na oknie rodzica(pulpicie), w którym będzie lewy-górny róg tworzonego okna. Wartości te mają znaczenie tylko w momencie stworzenia okna, wystarczy, aby użytkownik przesunął okno, a współrzędne te zmienią się. Domyślną wartością jest CW_USEDEFAULT, wtedy system wybierze miejsce pojawienia się okna. int nwidth, int nheight Te wartości określają wymiary całego okna, szerokość(nwidth) i wysokość(nheight) w pikselach. HWND hwndparent Jest to uchwyt do okna rodzica tworzonego okna. Podanie wartości NULL lub HWND_DESKTOP, spowoduje, że okno będzie potomkiem pulpitu. HMENU hmenu Uchwyt do menu okna. Wartość NULL oznacza brak menu. HINSTANCE hinstance Uchwyt programu do którego ma należeć okno. LPVOID lpparam Dane które może przekazać programista. NULL oznacza brak takich danych. Zwracana wartość HWND Funkcja zwraca uchwyt do utworzonego okna. Jeśli zostanie zwrócone NULL, okno nie zostało poprawnie utworzone. Funkcja CreateWindowEx() posiada makro o nazwie CreateWindow(). Obecnie funkcja CreateWindow() w plikach nagłówkowych WinAPI nie istnieje i jest tylko makrem na CreateWindowEx() bez pierwszego parametru(jest ustawiony na 0). Okno można po utworzeniu w dowolnym momencie zniszczyć(usunąć z pamięci). Robi to funkcja o nazwie DestroyWindow().

21 BOOL DestroyWindow(HWND hwnd); HWND hwnd Uchwyt do niszczonego okna. Jeśli okno posiada okna potomne, najpierw one zostaną zniszczone. Zwracana wartość BOOL Jeśli okno zostało usunięte poprawnie zwraca TRUE, jeśli wystąpił błąd FALSE. 2.8 Pętla zdarzeniowa We wprowadzeniu do tematu modelu zdarzeniowego aplikacji, zaprezentowałem schemat budowy aplikacji w modelu zdarzeniowym, podzieliłem go na 3 części: inicjalizację, przetwarzanie zdarzeń i zwalnianie zasobów. W części inicjalizacji nalezy tworzyć okna i to co jest niezbędne do dalszego działania programu. Następnie następuje część zdarzeniowa, i tutaj zatrzymamy się na dłużej. Jak to działa? Dobre pytanie. Otóż nie jest to żadna magia. Istnieje pewna pętla, która przetwarza wszystkie zdarzenia, które wystąpią w naszym programie. Może ona wyglądać różnie, klasycznie konstruuje się ją w ten sposób: MSG msg; while(getmessage(&msg,null,0,0)) TranslateMessage(&msg); DispatchMessage(&msg); Pierwsze co widzimy to typ MSG. Otóż jest to struktura, która przechowuje informacje o zdarzeniu(komunikacie), który wystąpił w danej chwili. typedef struct HWND hwnd; UINT message; WPARAM wparam; LPARAM lparam; DWORD time; POINT pt; MSG, *PMSG; HWND hwnd Każdy komunikat(zdarzenie) jest adresowany do konkretnego okna. To pole określa adresata danego komunikatu. Gdy zrobimy w naszym programie kilka okien, przetwarzane komunikaty będą wędrować do różnych okien, dlatego potrzebne jest to pole, aby było wiadomo do jakiego okna ma wędrować komunikat. UINT message Pole to określa rodzaj zdarzenia(komunikatu). Każdy komunikat(zdarzenie) ma swój własny numer, który jest zdefiniowany odpowiednia flagą. W systemie istnieją setki

22 rodzajów komunikatów, np. kliknięcie na okno, naciśniecie przycisku, poruszenie myszką, i mnóstwo innych, podczas poznawania Windows API, część z nich poznasz. WPARAM wparam, LPARAM lparam WPARAM i LPARAM to typy używane umownie przy komunikatach, są zdefiniowane z typu unsigned int, ale z uwzględnieniem wielkości architektury procesora, w ten sposób: typedef _w64 unsigned int WPARAM; typedef _w64 unsigned int LPARAM; Dzięki temu wielkości zmiennych zależą od architektury procesora pod jaką pisany jest program, tak jak wielkości wskaźników i można na nich wykonywać działania. Jeśli chodzi o przeznaczenie tych zmiennych w komunikatach, to są to jakieś dodatkowe dane, informacje dla danego komunikatu, zależne od rodzaju komunikatu. Np. wartość informacji lparam komunikatu o kliknięciu na oknie będzie zawierała informacje o współrzędnych kursora w chwili kliknięcia. Pole wparam nazywa się parametrem o wyższym znaczeniu zdarzenia, a lparam nazywa się parametrem o niższym znaczeniu. DWORD time Czas w momencie, którym zdarzenie miało miejsce. Pole mało istotne, rzadko używane. POINT pt; A to pole przechowuje, współrzędne kursora w momencie zaistnienia zdarzenia. To pole także, rzadko jest używane. POINT jest struktura, która opisuje punkt. Jej prostą budowę przedstawię przy innej okazji. Zatem po zapoznaniu się z budową komunikatu(zdarzenia) w systemie Windows, można zapoznać się z pętlą zdarzeniową. Otóż jak już wcześniej wspominałem odpowiedzialna jest ona za przetwarzanie komunikatów w programie. Jak to działa? Otóż każdy program w systemie posiada swoją własną kolejkę komunikatów. Cóż to takiego ta kolejka? Gdy wystąpi jakieś zdarzenie w systemie, np. użytkownik komputera kliknie myszką, system ustala na jakim oknie kliknął użytkownik, jak już to wie, ustala do jakiego programu należy to okno, następnie do kolejki komunikatów tego programu wysyła komunikat o kliknięciu na danym oknie. Więc co to ta kolejka komunikatów? Otóż wirtualne miejsce gdzie przechowywane są komunikaty, które wysłał do programu system lub jakiś inny program. Kolejka komunikatów należy do systemu i to on ją obsługuje. Ale dlaczego to kolejka? Dlatego, że gdy system wyśle więcej komunikatów, ustawią się one w pewnej kolejności, można to porównać do ludzi stojących w kolejce. Naszym zadanie, a właściwie zadaniem naszej pętli zdarzeniowej, jest pobieranie komunikatów z tej kolejki, ściślej dokładnie tym zajmuje się funkcja GetMessage(). Wyciąga ona z kolejki komunikat i umieszcza go w strukturze komunikatu MSG. W ten sposób zdarzenie z kolejki komunikatów, trafiło do struktury MSG, czyli znajduje się już w programie. BOOL GetMessage(LPMSG lpmsg,hwnd hwnd,uint wmsgfiltermin,uint wmsgfiltermax); LPMSG lpmsg Pierwszy parametr to wskaźnik na strukturę MSG, do której zostanie zapisany komunikat świeżo wyciągnięty z kolejki. HWND hwnd Tutaj możemy ustawić, że funkcja będzie pobierać tylko komunikaty adresowane do konkretnego okna, którego należy podać uchwyt w tym parametrze. Podanie NULL, spowoduje, że funkcja będzie pobierać wszystkie komunikaty, bez względu na okno do, którego jest adresowany.

23 UINT wmsgfiltermin, UINT wmsgfiltermax Te wartości określają przedział liczbowy komunikatów, które będzie pobierała funkcja. Funkcjonalność rzadko stosowana. Zwracana wartość BOOL Warto zwrócić uwagę, że wartość którą zwróci funkcja, będzie warunkiem, który zakończy, bądź będzie kontynuował pętlę przetwarzającą zdarzenia. Zwrócenie wartości FALSE, przerwie działanie pętli, w ten sposób rozpoczynając ostatni etap życia programu, czyli zwalnianie zasobów, zwykle przerwanie pętli powoduje, już rozpoczęcie zamykania programu. Wartość TRUE będzie kontynuować przetwarzanie zdarzeń. Otóż istnieje specjalny komunikat, który pobrany przez GetMessage() powoduje, że zwraca FALSE. Komunikat ten służy właśnie do zatrzymywania pętli komunikatów, a tym samym do zamykania programu. W przypadku, gdy w kolejce komunikatów nie ma żadnego zdarzenia, GetMessage() zatrzymuje wykonywanie programu i czeka aż jakiś komunikat się pojawi() oszczędzając zasoby procesora. Istnieje funkcja PeekMessage(), która jedynie sprawdza, czy w kolejce komunikatów jest komunikat, jeżeli jest pobiera go, ale jeżeli nie ma, nie zatrzymuje wykonywania programu, tak jak robi GetMessage(). To czy jest czy nie, sygnalizuje wartością jaką zwraca. Funkcja ta używana jest głównie w grach, gdyż nie zatrzymuje wykonywania kodu gry, a gra może zająć się rysowaniem grafiki, czy generowaniem kolejnej klatki. Po pobraniu komunikatu przez GetMessage(), należy go przetworzyć. Tę robotę odwalają funkcje: TranslateMessage() i DispatchMessage(). Ta pierwsza ma za zadanie odpowiednio przetworzyć komunikat, jeżeli tego wymaga. Istnieje pewna grupa zdarzeń, które są generowane na podstawie innych, właśnie przez TranslateMessage(). Funkcja ta nie jest niezbędna do poprawnego działania pętli zdarzeniowej, ale bez niej mogą nie być generowane wszystkie zdarzenia, o których będzie mowa podczas nauki Windows API. BOOL TranslateMessage(const MSG* lpmsg); const MSG* lpmsg Jedyny parametr to wskaźnik na strukturę komunikatu MSG, na której ma dokonać operacji. Zdarzenie powinno być wyciągnięte z kolejki komunikatów, funkcją GetMessage() lub PeekMessage(). Zwracana wartość BOOL Jeżeli komunikat należał do grupy komunikatów, z których należało wygenerować kolejny komunikat, funkcja zwróci TRUE. Jeżeli komunikat nie wymagał żadnej akcji ze strony TranslateMessage() funkcja zwróci FALSE. Druga funkcją, mającą ważniejsze zadanie w procesie przetwarzania zdarzeń jest funkcja DispatchMessage(). Jej zadaniem jest już wykonanie kodu obsługującego dane zdarzenie. Jej zadanie polega na tym, że według pola hwnd w strukturze komunikatu, szuka okna do którego wędruje komunikat, a gdy znajdzie wywołuje funkcję tego okna(właściwie to klasy okna) odpowiedzialną za wykonanie kodu zdarzenia dla danego okna. Każda klasa okna posiada funkcję, która jest odpowiedzialna za przetwarzanie komunikatów. Po czym pętla wraca do początku, czyli pobiera kolejny komunikat i tym sposobem pompuje komunikaty przez cały czas istnienia naszego programu.

24 LRESULT DispatchMessage(const MSG* lpmsg); const MSG* lpmsg Podobnie jak TranslateMessage() parametrem jest struktura komunikatu, który należy przetworzyć. Zwracana wartość LRESULT Typ LRESULT zwracają funkcje, które wykonują kody zdarzeń(funkcje zdarzeniowe). W przypadku DispatchMessage(), zwraca ona wartość, którą zwróciła funkcja wykonująca kod zdarzenia. 2.9 Procedura zdarzeniowa okna Tworząc własne okno, przydało by się mieć nad nim kontrolę, a dokładniej, móc obsługiwać zdarzenia, które będą związane z oknem, tzn. które będą adresowane do okna, które zostało stworzone w programie, a wcześniej jego klasa zarejestrowana. Obsługiwanie zdarzeń polega na napisaniu własnej funkcji, która będzie przetwarzać komunikaty. Otóż DispatchMessage(), jak wiemy wywołuje funkcję okna, odpowiedzialną za przetworzenie komunikatów wędrujących do tego okna, w tym podrozdziale napiszemy sobie właśnie taką funkcję, którą będzie wywoływała funkcja DispatchMessage(). W tym momencie warto sobie przypomnieć o jednym z pól struktury klasy okna WNDCLASSEX, a mianowicie o polu: WNDPROC lpfnwndproc. Podczas wypełniania tego pola definiuje się funkcję, jaka będzie odpowiedzialna za przetwarzanie komunikatów wędrujących do okien utworzonych z tej klasy. Poprzednio w przykładzie podałem tutaj funkcję o nazwie DefWindowProc(). Jest to systemowa funkcja, która zajmuje się przetwarzaniem komunikatów w sposób domyślny dla systemu. Naszym zadaniem będzie napisanie własnej funkcji, w której sami umieścimy kod jaki ma się wykonać podczas zaistnienia określonego zdarzenia. W naszej funkcji możemy się skupić na obsłudze kilku, kilkunastu, co najwyżej kilkudziesięciu rodzajów komunikatów, ale do naszej funkcji mogą wędrować setki różnych komunikatów, dlatego w przypadku, gdy nasza funkcja nie obsługuje danego komunikatu, musimy go nadal przetworzyć przez DefWindowProc(), niech system się martwi co robić z komunikatami nieobsługiwanymi przez naszą funkcję zdarzeniową.

25 Funkcja zdarzeniowa musi mieć zawszę taką samą postać. Jej typ to LRESULT, musi posiadać 4 ściśle określone parametry, oraz rzecz jasna być w konwencji wywołania funkcji pascalowych. Załóżmy, że nazwiemy funkcję zdarzeniową WndEventsProc, wtedy funkcja będzie wyglądać w ten sposób(nazwy parametrów, oczywiście także mogą być dowolne): LRESULT CALLBACK WndEventsProc(HWND hwnd,uint msg,wparam wparam,lparam lparam) return DefWindowProc(hwnd,msg,wParam,lParam); Parametrami są właściwie wartości ze struktury komunikatu MSG. DispatchMessage() w momencie wywołania funkcji zdarzeniowej, w parametrach przekazuje kolejno pierwsze 4 wartości pól ze struktury komunikatu, który przetwarza. W roli przypomnienia: HWND hwnd Uchwyt okna, do którego adresowany był komunikat, czyli uchwyt okna do którego odnosi się dane zdarzenie. UINT msg Rodzaj zdarzenia. WPARAM wparam, LPARAM lparam Dodatkowe parametry określające zdarzenie. Zwracana wartość LRESULT W przypadku zwracanych wartości, gdy zdarzenie obsługuje ta funkcja, w większości komunikatów powinno się zwrócić wartość 0, w przypadku gdy funkcja nie obsługuje zdarzenia, należy zwrócić to co zwróciła DefWindowProc(). Wróćmy, do obsługi konkretnych zdarzeń. Parametr UINT msg, określa rodzaj zdarzenia. Najwygodniej jest rozbić wykonywanie kodu poszczególnych zdarzeń przy pomocy switch(). LRESULT CALLBACK WndEventProc(HWND hwnd,uint msg,wparam wparam,lparam lparam) switch(msg) case WM_ZDARZENIE_1: //obsługa zdarzenia typu 1 break; case WM_ZDARZENIE_2: //obsługa zdarzenia typu 2 break; default://obsługa pozostałych zdarzeń przez DefWindowProc() return DefWindowProc(hwnd,msg,wParam,lParam); return 0;//po wykoaniu zdarzenia 1 lub 2 zwracane jest 0 W powyższej funkcji zdarzeniowej obsługiwane są 2 rodzaje komunikatów: WM_ZDARZENIE_1 i WM_ZDARZENIE_2. Gdy przetwarzany komunikat będzie typu WM_ZDARZENIE_1 lub WM_ZDARZENIE_2, zostanie wykonany kod, któregoś z tych zdarzeń i zwrócona do DispatchMessage() wartość 0. Natomiast jeżeli zdarzenie nie będzie obsługiwane przez funkcję zdarzeniową, czyli nie będzie uwzględnione w

26 możliwości switch(), zostanie wykonany sposób domyślny instrukcji, czyli zdarzenie zostanie obsłużone przez funkcję systemową DefWindowProc() w sposób domyślny. Każdy nieobsługiwany typ zdarzenia(komunikatu) przez funkcję zdarzeniową musi być obsłużony przez funkcję obsługi domyślnej. Nie może dojść do sytuacji, w której komunikaty nieuwzględnione w funkcji zdarzeniowej nie będą w ogóle obsługiwane. Po napisaniu funkcji zdarzeniowej dla okien klasy, należy ją powiązać z odpowiednią klasą okna WNDCLASSEX, w polu WNDPROC lpfnwndproc, przypisując nazwę własnej funkcji zdarzeniowej. wndcls.lpfnwndproc=wndeventsproc; 2.10 Zdarzenie przerwania pętli Pierwszym rodzajem komunikatu(zdarzenia) jaki przedstawię, będzie zdarzenie, o którym już wcześniej wspominałem: komunikat przerwania pętli zdarzeniowej. Jego flaga kodowa to: WM_QUIT(wszystkie flagi zdarzeń, komunikatów zaczynają się na WM_). Gdy GetMessage() pobierze ten komunikat zwraca wartość FALSE, zaleca się wtedy przerwanie wykonywania pętli, gdyż ten komunikat ma służyć wyjściu z programu, czyli wejściu w ostatnią fazę życia programu, przygotowania do zamknięcia. Jednak ten komunikat, nigdy sam nie zostanie wygenerowany. Interfejs użytkownika nie wysyła tego typu komunikatu, jeśli chcemy zakończyć pętlę zdarzeniową musimy sami umieścić ten komunikat w kolejce komunikatów. Najprostszym rozwiązaniem i najsłuszniejszym jest posłużenie się funkcją PostQuitMessage(), która właśnie to służy do umieszczania WM_QUIT, w kolejce komunikatów w programie, w którym została wywołana. void PostQuitMessage(int nexitcode); int nexitcode Parametr ten powinien określać wartość, którą zwraca WinMain(). Dokładniej wartość tego parametru zostanie przypisana polu: WPARAM wparam, w komunikacie WM_QUIT. Zaleca się także, aby w tym wypadku WinMain() zwracała tę wartość wparam struktury MSG. Najczęstszym miejscem, w którym umieszcza się przerwanie pętli, czyli wywołanie tej funkcji, jest komunikat zamknięcia głównego okna w programie. W momencie gdy użytkownik zamknie okno główne w programie, zaleca się wysłać WM_QUIT, aby przerwać działanie pętli zdarzeniowej. 2.11 Zdarzenia życia okna Istnieją kolejne zdarzenia, które wysyłane są w kluczowych momentach życia okna. Te zdarzenia informują o: Stworzeniu okna (WM_CREATE) Próby zamknięcia okna (WM_CLOSE) Niszczeniu okna, usuwaniu z pamięci (WM_DESTROY)

27 WM_CREATE Zdarzenie WM_CREATE jest wysyłane do okna podczas tworzenia go, jest to prawdopodobnie pierwsze zdarzenie, które dociera do okna. Dokładnie zdarzenie to wykona się w momencie trwania CreateWindowEx(), funkcja ta tworzy okno w pamięci, a następnie właśnie wykonuje kod tego zdarzenia. W obsłudze tego zdarzenia umieszcza się kod, który tworzy kolejne elementy po stworzeniu okna, np. zawartość tworzonego okna. Funkcja domyślna DefWindowProc() nie wykonuje żadnych czynności podczas obsługi tego zdarzenia. Porównując ten komunikat do programowania obiektowego w C++, zdarzenie to ma dokładnie takie samo zadanie jak konstruktor klasy C++. Można powiedzieć, że zdarzenie to jest konstruktorem obiektu okna w Windows API. wparam w tym komunikacie wparam nie posiada żadnych informacji. lparam ten parametr to wskaźnik na strukturę CREATESTRUCT, w której są umieszczone informacje na temat stworzonego okna. Informacje te to wartości podane podczas wywołania CreateWindowEx(). typedef struct tagcreatestructa LPVOID lpcreateparams; HINSTANCE hinstance; HMENU hmenu; HWND hwndparent; int cy; int cx; int y; int x; LONG style; LPCSTR lpszname; LPCSTR lpszclass; DWORD dwexstyle; CREATESTRUCTA, *LPCREATESTRUCTA; LPVOID lpcreateparams Jest to wartość, która została podana w ostatnim parametrze funkcji CreateWindowEx(), podczas tworzenia okna, do którego należy ten komunikat. Dzięki temu polu można przekazać jakąś wartość wartość. HINSTANCE hinstacne Uchwyt do programu, który został podany podczas tworzenia okna w CreateWindowEx(). HMENU hmenu Uchwyt do menu podany jako parametr do CreateWindowEx(). HWND hwndparent Okno rodzica tworzonego okna, wartość podana do CreateWindowEx(). int cy,int cx,int x,int y cy szerokość, cx wysokość utworzonego okna. x i y określa współrzędną położenia okna na oknie rodzica. LONG style Style podstawowe okna. Wartość z CreateWindowEx().

28 LPCSTR lpszname Wskaźnik na tekst z tytułem okna. Wartość z CreateWindowEx(). LPCSTR lpszclass Wskaźnik na tekst z nazwą klasy okna, z której zostało utworzone okno. Wartość z CreateWindowEx(). DWORD dwexstyle Style rozszerzone okna. Wartość z CreateWindowEx(). WM_CLOSE Zdarzenie WM_CLOSE oznacza, że okno powinno zostać zamknięte. Wysyłane jest, gdy użytkownik kliknie na przycisk [X] na pasku tytułowym okna, lub wciśnie kombinację Alt+F4. Funkcja DefWindowProc() domyślnie wywołuje funkcję DestroyWindow() w obsłudze tego zdarzenia, usuwając okno. Obsługując sami to zdarzenie, możemy umieścić w nim kod odpowiedzialny za akcję, który ma się wykonać gdy użytkownik chce zamknąć okno. Większość edytorów(np. Word lub Notatnik) w obsłudze tego komunikatu pyta się użytkownika czy zapisać zamiany w dokumencie przed zamknięciem, a dopiero po odpowiedniej akcji wywołują DestroyWindow(). W przypadku, gdy w obsłudze tego komunikatu nie umieścimy możliwości wywołania DestroyWindow(), zabierzemy użytkownikowi możliwość zamknięcia okna(nie będzie mógł zamknąć okna z poziomu elementów systemowych okna). wparam ten parametr, w tym zdarzeniu nie posiada żadnych informacji. lparam ten parametr również nie przekazuje żadnej informacji. WM_DESTROY Zdarzenie WM_DESTORY jest przeciwieństwem WM_CREATE. Zostaje ono wywołane w momencie niszczenia okna, podczas wywołania funkcji DestroyWindow(). Funkcja DestroyWindow() najpierw wykonuje kod tego zdarzenia, następnie usuwa obiekt okna z systemu. W momencie wykonywania WM_DESTROY nie można już zatrzymać usunięcia okna, w obsłudze tego komunikatu powinny znaleźć się czynności odpowiedzialne, za zwolnienie zasobów używanych przez okno. Porównując ten komunikat do programowania obiektowego w C++, zdarzenie to ma dokładnie takie samo zadanie jak destruktor klasy C++. Można powiedzieć, że zdarzenie to jest destruktorem obiektu okna w Windows API. wparam ten parametr, w tym zdarzeniu nie posiada żadnych informacji. lparam ten parametr również nie przekazuje żadnej informacji. W tym momencie nauki Windows API, mogę już przedstawić kod stworzenia okna głównego w programie. #define WIN32_LEAN_AND_MEAN #include <windows.h> BOOL InitWindow(HINSTANCE);//rejestruje klasę okna i tworzy je //funkcja zdarzeniowa okna, odpowiedzialna za przetwarzanie zdarzeń: LRESULT CALLBACK WndEventProc(HWND,UINT,WPARAM,LPARAM); //funkcja główna WinMain()

29 int WINAPI WinMain(HINSTANCE hinst,hinstance,lpstr lpcmd,int nshow) //etap inicjalizacyjny programu - tworzenie okna głównego if(initwindow(hinst)==false) return 0; //pętla zdzrzeniowa - rozpoczęcie etapu zdarzeniowego MSG msg;//struktura zdarzenia(komunikatu) while(getmessage(&msg,null,0,0))//pobranie zdarzenia z kolejki TranslateMessage(&msg);//przetworzenie komunikatu (zdarzenia) DispatchMessage(&msg);//wykonanie kodu zdarzneia (komunikatu) //w odpowiedniej funkcji zdarzeniowej okna do którego jest wysłany return msg.wparam;//wyjście w programu(zamknięcie procesu) //msg.wparam jest wartością parametru z PostQuitMessage() //funkcja rejestruje i tworzy okno BOOL InitWindow(HINSTANCE hinstance) //parametr - uchwyt programu przekazany z WinMain() //ZNACZENIE UŻYTYCH STYLI WYJAŚNIONE JEST W ROZDZIALE O OKNACH WNDCLASSEX wndcls;//klasa okna char strclassname[]="klasa okna programu";//nazwa klasy okna wndcls.cbsize=sizeof(wndclassex);//rozmiar struktury wndcls.hinstance=hinstance;//uchwyt programu rejestrującego klasę wndcls.lpszclassname=strclassname;//przypisujemy nazwę klasy wndcls.lpfnwndproc=wndeventproc;//własna funckja zdarzeniowa okna //style: wndcls.style=cs_bytealignclient CS_BYTEALIGNWINDOW CS_DBLCLKS CS_DROPSHADOW CS_HREDRAW CS_VREDRAW; wndcls.hicon=null;//ikona główna (NULL-domyślna) wndcls.hiconsm=null;//ikona mniejsza wndcls.hcursor=loadcursor(null,idc_arrow);//kursor(domyslna strzałka) wndcls.hbrbackground=(hbrush)color_background;//kolor tła okien tej klasy wndcls.lpszmenuname=null;//menu (NULL-brak) //dodatkowe dane, których nie używamy wndcls.cbclsextra=0; wndcls.cbwndextra=0; if(registerclassex(&wndcls))//rejestracja klasy okna //stworzenie obiektu okna (uchwyt - HWND) HWND hwnd=createwindowex(ws_ex_clientedge WS_EX_COMPOSITED/*od XP*/, strclassname,"example",ws_overlappedwindow WS_VISIBLE, CW_USEDEFAULT,CW_USEDEFAULT,640,480,NULL,NULL,hInstance,NULL); //parametry okna: 640x480 tytuł: "Example" if(hwnd!=null) return TRUE;//jeśli okno jest stworzone zwracam powodzenie return FALSE;//jesli coś poszło nie tak, zwracam niepowodzenie //funkcja zdarzeniowa okna(funkcja jest ustawiona w polu lpfnwndproc WNDCLASSEX) LRESULT CALLBACK WndEventProc(HWND hwnd,uint msg,wparam wparam,lparam lparam) //parametry: //HWND hwnd - uchwyt okna adresata komunikatu, czyli zawsze uchwyt okna głównego //UINT msg - typ zdarzenia, flagi typów zdarzeń zaczynają się na WM_ //WPARAM wparam, LPARAM lpara - informacje dodatkowe switch(msg)//rozdzielnie poszczeglnych typów zdarzeń case WM_CREATE://WM_CREATE - zdarzenie utworzenia okna (konstruktor okna)

30 //własna obsługa tego zdarzenia break; case WM_DESTROY://WM_DESTROY - zdarzenie zniszczenia okna (destruktor okna) PostQuitMessage(0);//w obsłudze zniszczenia okna głównego, wywołuję //funckję przerywającą pętlę zdarzeniową, aby przerwać wykonywanie progrmu break; default://gdy funkcja ta nie obsługuje przysłanego typu komunikatu, nalezy go return DefWindowProc(hwnd,msg,wParam,lParam);//obsłużyć funkcją domyślną return 0;//po obsłudze komunikatu zdefiniwanego w tej funkcji zwracam 0 2.12 Symulowanie zdarzeń W tym podrozdziale chciałbym przedstawić dwie podobne funkcje Windows API, które służą do tego samego, ale realizują to w trochę inny sposób. Otóż mowa o bardzo przydatnej czynności, jaką jest wykonanie kodu zdarzenia, czy zasymulowanie jakiegoś zdarzenia. Funkcje o których mowa to SendMessage() i PostMessage(). LRESULT SendMessage(HWND hwnd,uint Msg,WPARAM wparam,lparam lparam); Co powinno być widać na pierwszy rzut okna, to budowa tej funkcji, otóż jej parametry i zwracana wartość są dokładnie takie same jak w funkcjach zdarzeniowych. Jest to spowodowane tym, że funkcja w pewnym sensie spełnia właśnie rolę funkcji zdarzeniowej. Jak ona dokładnie działa? Otóż wywołując ją, to tak jakbyśmy wywoływali funkcję zdarzeniową okna, którego uchwyt podaje się jako pierwszy parametr. Funkcja wykona kod zdarzenia i zwróci wartość wykonania tego zdarzenia. HWND hwnd Uchwyt adresata zdarzenia, czyli uchwyt okna, którego wykona się funkcja zdarzeniowa. UINT Msg Typ zdarzenia, np. WM_CREATE, WM_DESTORY, WM_CLOSE lub WM_QUIT. WPARAM wparam, LPARAM lparam Informacje, parametry dodatkowe dla komunikatu, zdarzenia. Zwracana wartość LRESULT Zwraca wartość zwróconą przez funkcję zdarzeniową wykonującą kod zdarzenia. Działanie PostMessage() trochę się różni od SendMessage(). Otóż PostMessage() nie wykonuje zdarzenia od razu. Funkcja ta jedynie wysyła zdarzenie do kolejki komunikatów, dlatego, aby zdarzenie się wykonało trzeba poczekać, aż GetMessage() lub PeekMessage() pobierze to zdarzenie z kolejki komunikatów. PostMessage() sprawdza się tam, gdzie nie trzeba zdarzenia wykonywać od razu, a wystarczy je jedynie umieścić w kolejce komunikatów. PostMessage() nie czeka na przetworzenie komunikatu, jedynie wstawia zdarzenie do kolejki i kończy swoje działanie. BOOL PostMessage(HWND hwnd,uint Msg,WPARAM wparam,lparam lparam);

31 HWND hwnd Okno do, którego adresowany jest komunikat. Podanie wartości HWND_BROADCAST, spowoduje wysłanie komunikatu do wszystkich okien typu zawsze na wirzchu. Podanie wartości NULL, spowoduje nieokreślenie adresata komunikatu, np. WM_QUIT nie wymaga podawania okna, do którego adresuje się komunikat, wystarczy, że pobierze je GetMessage(). UINT Msg Typ zdarzenia. WPARAM wparam, LPARAM lparam Dodatkowe parametru komunikatu. Zwracana wartość BOOL Jeśli komunikat zostanie poprawnie umieszczony w kolejce komunikatów, funkcja zwróci TRUE, jeśli wystąpi błąd FALSE. Co daje wykonywanie zdarzeń przez SendMessage() i PostMessage()? Otóż bardzo dużo, przede wszystkim można symulować dowolne zdarzenie w systemie, np. udawać że użytkownik kliknął w jakieś miejsce lub nacisnął jakiś przycisk. 2.13 Komunikat okienkowy Czasem w programie zachodzi potrzeba szybkiego wyświetlenia pewnej informacji lub zapytania się użytkownika o jakąś akcję. Windows API oferuje bardzo przydatną funkcję, pokazującą okienko. Takie niewielkie okienko wyświetla określony tekst, posiada określony tytuł, może mieć obrazek ikony, oraz pewne określone przyciski. Wykonywanie kodu zatrzymuje się na funkcji MessageBox() dopóki nie zostanie naciśnięty przycisk na wyświetlonym okienku komunikatu. Dopiero po zniknięciu okienka kod programu jest dalej wykonywany. Okno z tą właściwością nazywa się oknem dialogowym. int MessageBox(HWND hwnd,lpcstr lptext,lpcstr lpcaption,uint utype); HWND hwnd Uchwyt do okna rodzica komunikatu okienkowego. Można podać NULL, wtedy rodzicem będzie pulpit. Gdy podamy uchwyt do okna(parametr inny niż NULL), podczas wyświetlania okienka komunikatu, nie będzie można powrócić do okna rodzica, gdyż na czas wyświetlania okienka stanie się ono nieaktywne. LPCSTR lptext Wskaźnik na bufor z tekstem wiadomości na okienku. LPCSTR lpcaption Wskaźnik na bufor z tekstem tytułu okienka, wyświetlanym na pasku tytułu. UINT utype Kombinacja flag określających działanie okienka i jego wyglądu. Flagi dla tego parametru zaczynają się na MB_.

32 Zwracana wartość int Funkcja zwraca identyfikator przycisku, który nacisnął użytkownik. Przykład programu, który składa się z wywołania okienka komunikatu: #define WIN32_LEAN_AND_MEAN #include <windows.h> int WINAPI WinMain(HINSTANCE hinst,hinstance,lpstr lpcmd,int nshow) MessageBox(NULL,"Treść komunikatu tekstowego Windows API","Wiadomość!",0); return 0; MessageBox() w ostatnim parametrze ustala różne właściwości, poprzez ustawienie jednej z dostępnych flag dla tego parametru. Okienko może posiadać ikonę wyświetlaną z lewej strony, każda ikona posiada własną flagę. W zależności od wersji i ustawień systemu Windows, graficzny wygląd ikon jest różny. Na okienku możemy także ustawić rodzaje przycisków jakie użytkownik będzie miał do wyboru. Mamy do dyspozycji 7 możliwych kombinacji. Niezdefiniowanie żadnej z flag przycisków, oznacza sam przycisk OK. Flaga kombinacji Przyciski MB_OK OK MB_OKCANCEL OK i Anuluj MB_RETRYCANCEL Ponów próbę i Anuluj MB_YESNO Tak i Nie MB_YESNOCANCEL Tak, Nie i Anuluj MB_ABORTRETRYIGNORE Przerwij, Ponów próbę i Ignoruj MB_CANCELTRYCONTINUE Anuluj, Ponów próbę, Kontynuuj (flaga dostępna od Windows 2000) Funkcja zwraca jaki przycisk nacisnął użytkownik, oto flagi zwracanych przycisków. Flaga przycisku Przycisk IDOK IDCANCEL IDRETRY IDYES IDNO IDABORT OK Anuluj Ponów próbę Tak Nie Przerwij