WinAPI-roz 2


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;

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.

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

wartość WINVER

Windows 95 / 98 / ME

0x400

Windows 2000

0x500

Windows XP / Server 2003

0x501

Windows XP SP2 / Server 2003 SP1

0x502

Windows Vista / Server 2008

0x600

A oto wartości _WIN32_IE dla wersji Internet Explorer:

wersja Internet Explorer

wartość _WIN32_IE

Internet Explorer 5.0

0x500

Internet Explorer 5.01

0x501

Internet Explorer 5.5

0x550

Internet Explorer 6.0

0x600

Internet Explorer 6.0 SP1

0x601

Internet Explorer 6.0 SP2

0x603

Internet Explorer 7.0

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.

0x01 graphic

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

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.

0x01 graphic

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.

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.

0x01 graphic

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

0x01 graphic

Wygląd okna Windows:

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.

0x01 graphic

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

0x01 graphic

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.

0x01 graphic

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.

0x01 graphic

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.

0x01 graphic

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

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;

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);

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().

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

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.

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.

0x01 graphic

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

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

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().

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()

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)

//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);

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_”.

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;

}

0x01 graphic

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

0x01 graphic

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

OK

IDCANCEL

Anuluj

IDRETRY

Ponów próbę

IDYES

Tak

IDNO

Nie

IDABORT

Przerwij

IDIGNORE

Ignoruj

IDTRYAGAIN

Ponów próbę z MB_CANCELTRYCONTINUE

IDCONTINUE

Kontynuuj

Są także flagi określające, który z przycisków ma być domyślnie aktywny, tzn. który będzie posiadał pole, którym nawiguje się przyciskiem TAB(fokus). Domyślnie jest to zawsze pierwszy przycisk(od lewej strony).

Flaga aktywnego przycisku

Numer przycisku od lewej

MB_DEFBUTTON1

Pierwszy jest aktywny

MB_DEFBUTTON2

Drugi jest aktywny

MB_DEFBUTTON3

Trzeci jest aktywny

Kolejną grupą flag dla MessageBox() to flagi określające siłę modalności okna.

Flaga modalności

Efekt

MB_APPLMODAL

Domyślny efekt(gdy nie zdefiniujemy żadnej z flag modalności). Okienko zablokuje okno rodzica(nie będzie można kliknąć na nie).

MB_SYSTEMMODAL

Oprócz efektu z MB_APPLMODAL, okienko komunikatu będzie typu „zawsze na wierzchu”, więc nie będzie się go dało przesłonić zwykłymi oknami.

MB_TASKMODAL

Gdy pierwszy parametr będzie wynosił NULL, okienko zablokuje wszystkie okna w programie. Jeśli pierwszy parametr będzie określał okno, flaga będzie miała taką samą właściwość jak MB_APPLMODAL.

Ostatnią grupą flag są flagi o różnym znaczeniu.

Flaga MessageBox()

Znaczenie

MB_DEFAULT_DESKTOP_ONLY

Flaga ma znaczenie od Windows 2000. Jeśli program został uruchomiony na koncie innego użytkownika, niż obecnie jest wyświetlany, MessageBox() poczeka, aż z powrotem nie zostanie wyświetlony pulpit użytkownika, na którym został uruchomiony program, żeby wyświetlić okienko wiadomości.

MB_RIGHT

Tekst jest wyświetlany od prawej do lewej strony.

MB_RTLREADING

Przycisk zamknięcia jest z lewej strony, a tytuł z prawej na pasku tytułu.

MB_TOPMOST

Okienko będzie posiadać stan „zawsze na wierzchu”.

MB_SETFOREGROUND

Okno komunikatu jest oknem pierwszoplanowym.

MB_SERVICE_NOTIFICATION

Wyświetla okienko nawet jeśli żaden użytkownik się nie zalogował(z poziomu poza użytkownika). Pierwszy parametr musi być ustawiony na NULL.

W ramach przykładu, można się spytać czy użytkownik chce na pewno zamknąć program. W obsłudze komunikatu WM_CLOSE należy umieścić kod wyświetlający komunikat okienkowy, z odpowiednim pytaniem, oraz według przycisku, który wybierze użytkownik podjąć odpowiednią akcję.

#define WIN32_LEAN_AND_MEAN

#include <windows.h>

BOOL InitWindow();

LRESULT CALLBACK WndEventProc(HWND,UINT,WPARAM,LPARAM);

LRESULT CloseWnd(HWND hwnd);//funkcja pyta się czy zamknąc program

int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int)

{

if(InitWindow()==FALSE) return 0;

MSG msg;

while(GetMessage(&msg,NULL,0,0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

BOOL InitWindow()

{

//uchwyt programu pobrany za pomocą funkcji GetModuleHandle()

HINSTANCE hInstance=GetModuleHandle(0);

WNDCLASSEX wndcls;

char strClassName[]="Klasa okna programu";

wndcls.cbSize=sizeof(WNDCLASSEX);

wndcls.hInstance=hInstance;

wndcls.lpszClassName=strClassName;

wndcls.lpfnWndProc=WndEventProc;

wndcls.style=CS_BYTEALIGNCLIENT | CS_BYTEALIGNWINDOW | CS_DBLCLKS |

CS_DROPSHADOW | CS_HREDRAW | CS_VREDRAW;

wndcls.hIcon=NULL;

wndcls.hIconSm=NULL;

wndcls.hCursor=LoadCursor(NULL,IDC_ARROW);

wndcls.hbrBackground=(HBRUSH)COLOR_BACKGROUND;

wndcls.lpszMenuName=NULL;

wndcls.cbClsExtra=0;

wndcls.cbWndExtra=0;

if(RegisterClassEx(&wndcls))

{

HWND hwnd=CreateWindowEx(WS_EX_CLIENTEDGE | WS_EX_COMPOSITED,

strClassName,"Example",WS_OVERLAPPEDWINDOW | WS_VISIBLE,

CW_USEDEFAULT,CW_USEDEFAULT,640,480,NULL,NULL,hInstance,NULL);

if(hwnd!=NULL) return TRUE;

//w przypadku błędu utworzenia okna, wyświetlenie treści błędu

else MessageBox(NULL,"Błąd tworzenia okna.",NULL,MB_OK | MB_ICONERROR);

}

//w przypadku błędu rejestarcji klasy okna, wyświetlenie treści błędu

else MessageBox(NULL,"Błąd rejestarcji klasy.",NULL,MB_OK | MB_ICONERROR);

return FALSE;

}

LRESULT CALLBACK WndEventProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)

{

switch(msg)

{

case WM_CLOSE://obsługa komunikatu WM_CLOSE

return CloseWnd(hwnd);//obsługa tego zdarzenia-wykonuje się funckja CloseWnd

case WM_DESTROY:

PostQuitMessage(0);

break;

default:

return DefWindowProc(hwnd,msg,wParam,lParam);

}

return 0;

}

LRESULT CloseWnd(HWND hwnd)

{

int UserResult=MessageBox(hwnd,"Zamknąć?",":(",MB_ICONQUESTION | MB_YESNO);

if(UserResult==IDYES) DestroyWindow(hwnd);// użytkownik wybrał tak - zamykam

return 0;

}

33



Wyszukiwarka

Podobne podstrony:
W09 Ja wstep ROZ
164 ROZ M G w sprawie prowadzeniea prac z materiałami wybu
124 ROZ stwierdzania posiadania kwalifikacji [M G P P S
013 ROZ M T G M w sprawie warunków technicznych, jakim pow
4 ROZ w sprawie warunkow techn Nieznany (2)
16 ROZ w sprawie warunkow tec Nieznany
18 ROZ warunki tech teleko Nieznany (2)
034 ROZ M I w sprawie wzoru protokołu obowiązkowej kontroli
5 ROZ w sprawie warunkow tech Nieznany (2)
123 roz uprawnienia D20140176id Nieznany
bio gle srod roz
133 ROZ bhp i p poz w zakla Nieznany
hej mam bardzo fajna zagadke dla ciebie jak bedziesz miał chwile to sobie zobacz, ■RÓŻNOŚCI, MOŻNA S
rr RĂłznice Indywidualne Wszytskie pytania, Studia, Psychologia, SWPS, 2 rok, Semestr 04 (lato), Psy
teorie roz reg, ściągi 2 rok ekonomia 1 sem
Roz 4 Pedagogika egzystencjalna[1]
roz i serduszka
roz III
biola roz

więcej podobnych podstron