ďťż

Blog literacki, portal erotyczny - seks i humor nie z tej ziemi


LEKCJA 40: STRUKTURA PROGRAMU PROCEDURALNO - ZDARZENIOWEGO
PRZEZNACZONEGO DLA WINDOWS.
________________________________________________________________
W trakcie tej lekcji poznasz ogólną budowę interfejsu API
Windows i dowiesz się, co z tego wynika dla nas - autorów
programów przeznaczonych dla Windows.
________________________________________________________________

W przeciwieństwie do długich liniowych programów przeznaczonych
dla DOS, w naszych programach dla Windows będziemy pisać coś
na kształt krótkich odcinków programu i przekazywać sterowanie
Windows. Jest to bardzo ważna cecha - kod programu jest zwykle
silnie związany z Windows w taki sposób, że użytkownik może w
dużym stopniu decydować o sposobie (kolejności) wykonywania
programu. Praktycznie robi to poprzez wybór opcji-klawiszy w
dowolnej kolejności. Przy takiej filozofii w dowolnym momencie
powinniśmy mieć możliwość przełączenia się do innego programu
(innego okna) i nasz program powinien (bez zauważalnej zwłoki)
przekazać sterowanie, nie zagarniając i nie marnując czasu CPU.
Z tego powodu kod programu powinien być bardzo
"zmodularyzowany". Każda sekcja kodu powinna być odseparowana i
każda, po wykonaniu powinna przekazywać sterowanie do Windows.

NOTACJA WĘGIERSKA I NOWE TYPY DANYCH.

Tworzenie zdarzeniowych programów dla Windows wymaga kilku
wstępnych uwag na temat nowych typów danych. Okienkowe typy są
definiowane w plikach nagłówkowych (WINDOWS.H, WINDOWSX.H, OWL.H

itp) i mają postać najczęściej struktury, bądź klasy. Typowe
sposoby deklaracji w programach okienkowych są następujące:

HWND hWnd - WiNDow Handle - identyfikator okna
HWND hWnd - typ (predefiniowany), hWnd - zmienna
HINSTANCE hInstance - Instance Handle - identyfikator danego
wystąpienia (uruchomienia) programu
PAINTSTRUCT - struktura graficzna typu PAINTSTRUCT
ps - nasza robocza struktura (zmienna)
WNDCLASS - struktura (a nie klasa wbrew mylącej nazwie)
POINT - struktura (współrzędne punktu - piksela na ekranie)
RECT - struktura (współrzędne prostokąta)
BOOL - typ signed int wykorzystywany jako flaga (TRUE/FALSE)
WORD - unsigned int
DWORD - unsigned long int
LONG - long int
HANDLE, HWND, HINSTANCE - unsigned int (jako nr - identyfikator)

UINT - j. w. - unsigned int.

W programach okienkowych stosuje się wiele predefiniowanych
stałych, których znaczenie sugeruje przedrostek i nazwa, np:

WM_CREATE - Windows Message: Create! - Komunikat Windows:
Utworzyć! (np. okno)
WS_VISIBLE - Window Style: Visible - Styl Okna: Widoczne
ID_... - IDentifier - IDentyfikator
MB_... - Message Box - elementy okienka komunikatów

W środowisku Windows stosuje się specjalną notację nazwaną od
narodowości swojego wynalazcy Karoja Szimoni - notacją
węgierską. Sens notacji węgierskiej polega na dodaniu do nazwy
zmiennej określonych liter jako przedrostka (prefix).
Litery-przedrostki stosowane w notacji węgierskiej zebrano w
Tabeli poniżej. Pomiędzy nazewnictwem Microsofta a Borlanda
istnieją wprawdzie drobne rozbieżności, ale ogólne zasady można
odnieść zarówno do BORLAND C++ 3+...4+, jak i Microsoft C++
6...7, czy Visual C++.

Notacja węgierska
________________________________________________________________
Prefix Skrót ang. Znaczenie
________________________________________________________________

a array tablica
b bool zmienna logiczna (0 lub 1)
by unsigned char znak (bajt)
c char znak
cb count of bytes liczba bajtów
cr color reference value określenie koloru
cx, cy short (count x, y len.) x-ilość, y-długość (short)
dw unsigned long liczba długa bez znaku
double word podwójne słowo
fn function funkcja
pfn pointer to function wsk. do funkcji
h handle "uchwyt" - identyfikator
i integer całkowity
id identifier identyfikator
n short or int krótki lub całkowity
np near pointer wskaźnik bliski
p pointer wskaźnik
l long długi
lp long pointer wskaźnik typu long int
lpfn l. p. to function daleki wskaźn. do funkcji
s string łańcuch znaków
sz string terminated '\0' łańcuch ASCIIZ
tm text metric miara tekstowa
w unsigned int (word) słowo
x,y short x,y coordinate współrzędne x,y (typ: short)
________________________________________________________________

O PROGRAMOWANIU PROCEDURALNO - ZDARZENIOWYM DLA WINDOWS.

W proceduralno-sekwencyjnych programach DOS'owskich sterowanie
jest przekazywane mniej lub bardziej kolejno kolejnym
instrukcjom w taki sposób, jak życzył sobie tego programista. W
Windows program-aplikacja prezentuje użytkownikowi wszystkie
dostępne opcje w formie widocznych na ekranie obiektów (visual
objects) do wyboru przez użytkownika. Program funkcjonuje zatem
według zupełnie innej koncepcji nazywanej "programowaniem
zdarzeniowym" (ang. event-driven programming). Można powiedzieć,

że za przebieg wykonania programu nie jest odpowiedzialny tylko
programista lecz część tej odpowiedzialności przejmuje
użytkownik i to on decyduje w jaki sposób przebiega wykonanie
programu. Użytkownik może wybrać w dowolnym momencie dowolną
spośród wszystkich oferowanych mu opcji a program powinien
zawsze zareagować poprawnie i równie szybko. Jest oczywiste, że
pisząc program nie możemy przewidzieć w jakiej kolejności
użytkownik będzie wybierał opcje/rozkazy z menu. Przeciwnie
powiniśmy napisać program w taki sposób by dla każdego rozkazu
istniał oddzielny kod. Jest to ogólna koncepcja, na której
opiera się programowanie zdarzeniowe.

W przeciwieństwie do programów proceduralno - sekwencyjnych,
które należy czytać od początku do końca, programy dla Windows
muszą zostać pocięte na na mniejsze fragmenty - sekcje - na
zasadzie jedna sekcja - obsługa jednego zdarzenia. Jeśli
zechcesz wyświetlić napis "Hello, World", sekcja zdarzeniowego
programu obsługująca takie zdarzenie może wyglądać np. tak:

Funkcja_Obsługi_Komunikatów_o_Zdarzeniach(komunikat)
{
switch (komunikat_od_Windows)
{
case WM_CREATE:
...
TextOut(0, 0, "Napis: np. Hello world.", dlugosc_tekstu);
break;

...
case WM_CLOSE: // CLOSE - zamknąć okno
.... break;
..................... itd.
}

a w przypadku obiektowego stylu programowania - metoda
obsługująca to zdarzenie (należąca np. do obiektu
Obiekt_Główne_Okno - TMainWindow) może wyglądać np. tak:

void TMainWindow::RysujOkno()
{
TString Obiekt_napis = "Hello, World";
int dlugosc_tekstu = sizeof(Obiekt_napis);
TextOut(DC, 10, 10, Obiekt-napis, dlugosc_tekstu);
}

Taki fragment kodu programu jest specjalnie przeznaczony do
obsługi jednego zdarzenia (ewent-ualności). W okienku wykonuje
się operacja PAINT (maluj). "Malowanie" okna może się odbywać
albo po raz pierwszy, albo na skutek przesunięcia. Programy
zdarzeniowe tworzone w C++ dla Windows będą zbiorem podobnych
"kawałków" następujących w tekście programu sekcja za sekcją.
Oto jak działa program zdarzeniowy: kod programu, podzielony na
sekcje obsługujące poszczególne zdarzenia koncentruje się wokół
interfejsu.

FUNKCJE WinMain() i WindowProc().

W programach pisanych w standardowym C dla Windows używane są
dwie najważniejsze funkcje: WinMain() i WindowProc().

________________________________________________________________

UWAGA:
Funkcji WindowProc() można nadać dowolną nazwę, ale WinMain()
musi się zawsze nazywać WinMain(). Jest to nazwa zastrzeżona
podobnie jak main() dla aplikacji DOSowskich.
________________________________________________________________


Funkcja WinMain() powoduje utworzenie okna programu umożliwiając

zdefiniowanie i zarejestrowanie struktury "okno" (struct
WNDCLASS) a następnie powoduje wyświetlenie okna na ekranie. Od
tego momentu zarządzanie przejmuje funkcja WindowProc(). W
typowej proceduralno - zdarzeniowej aplikacji dla Windows to
właśnie funkcja WindowProc() obsługuje pobieranie informacji od
użytkownika (np. naciśnięcie klawisza lub wybór z menu). Funkcja

WindowProc() robi to dzięki otrzymywaniu tzw. komunikatów (ang.
Windows message).

W Windows zawsze po wystąpieniu jakiegoś zdarzenia (event)
następuje przesłanie komunikatu (message) o tym zdarzeniu do
bieżącego aktywnego w danym momencie programu w celu
poinformowania go, co się stało. Jeśli został naciśnięty
klawisz, komunikat o tym zdarzeniu zostanie przesłany do funkcji

WindowProc(). Tak funkcjonuje interfejs pomiędzy aplikacją a
Windows. W programach tworzonych w C prototyp funkcji
WindowProc() wygląda następująco:

LONG FAR PASCAL WindowProc(HWND hWnd, WORD Message,
WORD wParam, LONG lParam);

Słowa FAR i PASCAL oznaczają, że:

FAR - kod funkcji znajduje się w innym segmencie niż kod
programu;
PASCAL - kolejność odkładania argumentów na stos - odwrotna (jak

w Pascalu).

________________________________________________________________

UWAGA:
Prototyp funkcji może zostać podany również tak:

LONG FAR PASCAL WndProc(HWND, unsigned, WORD, LONG);
________________________________________________________________


Pierwszy parametr hWnd jest to tzw. identyfikator okna (ang.
window handle). Ten parametr zawiera informację, dla którego
okna przeznaczony jest komunikat. Zastosowanie takiego
identyfikatora jest celowe, ponieważ funkcje typu WindowProc()
mogą obsługiwać przesyłanie komunikatów do wielu okien. Jeśli
okien jest wiele, okno jest identyfikowane przy pomocy tego
właśnie identyfikatora (numeru).

Następny parametr to sam komunikat o długości jednego słowa
(word). Ten parametr przechowuje wartość z zakresu
zdefiniowanego w pliku nagłówkowym WINDOWS.H. W zależności od
tego co się zdarzyło, Windows mogą nam przekazać ok. 150 różnych

komunikatów a w tym np.:

WM_CREATE Utworzono okno
WM_KEYDOWN Naciśnięto klawisz
WM_SIZE Zostały zmienione wymiary okna
WM_MOVE Okno zostało przesunięte
WM_PAINT Okno należy narysować (powtórnie) - (re)draw
WM_QUIT Koniec pracy aplikacji
itp.

Przedrostek WM_ to skrót od Windows Message - komunikat Windows.

Wymiana komunikatów w środowisku Windows może przebiegać w różny

sposób - zależnie od źródła wywołującego generację komunikatu i
od charakteru zdarzenia. Ze względu na źródło można komuniakty
umownie podzielić na następujące grupy:

1. Działanie użytkownika (np. naciśnięcie klawisza) powoduje
wygenerowanie komunikatu.
2. Program - aplikacja wywołuje funkcję Windows i powoduje
przesłanie komunikatu do aplikacji.
3. Środowisko Windows przesyła komunikat do programu.
4. Dwie aplikacje związane mechanizmem dynamicznej wymiany
danych (Dinamic Data Exchange - DDE) wymieniają komunikaty.

Komunikaty Windows można także podzielić umownie na następujące
kategorie:

1. Komunikaty dotyczące zarządzania oknami (Windows Managenent
Msg.):
WM_ACTIVATE (zaktywizuj lub zdezaktywizuj okno), WM_PAINT,
WM_MOVE, WM_SIZE, WM_CLOSE, WM_QUIT.

Bardzo istotnym szczegółem technicznym jest problem
przekazywania aktywności pomiędzy oknami. Szczególnie często
występuje potrzeba przekazania aktywności do elementu
sterującego. Jeśli hEditWnd będzie identyfikatorem (window
handle) okienka edycyjnego:

case WM_SETFOCUS:
SetFocus(hEditWnd);
break;

funkcja SetFocus() spowoduje, że wszystkie komunikaty dotyczące
zdarzeń klawiatury będą kierowane do okna sterującego, jeżeli
okno macieżyste jest aktywne. Ponieważ zmiana rozmiaru okna
głównego nie pociąga za sobą automatycznej zmiany rozmiaru okna
sterującego, potrzebna jest dodatkowo obsługa wiadomości
WM_SIZE wobec okna elementu sterującego.

2. Komunikaty inicjacyjne dotyczące konstrukcji np. menu
aplikacji:
WM_INITMENU - zainicjuj menu (wysyłany przed zainicjowaniem),
WM_INITDIALOG - zainicjuj okienko dialogowe.

3. Komunikaty generowane przez Windows w odpowiedzi na wybór
rozkazu z menu, zegar, bądź naciśnięcie klawisza:
WM_COMMAND - wybrano rozkaz z menu,
WM_KEYDOWN - naciśnięto klawisz,
WM_MOUSEMOVE - przesunięto myszkę,
WM_TIMER - czas minął.

4. Komunikaty systemowe. Aplikacja nie musi odpowiadać na
rozkazy obsługiwane przez domyślną procedurę Windows -
DefWindowProc(). Szczególnie dotyczy to rozkazów nie odnoszących

się do roboczego obszaru okna - Non Client Area Messages.

5. Komunikaty schowka (Clipborad Messages).

Sens działania funkcji WindowProc() w C/C++ polega na
przeprowadzeniu analizy, co się stało i podjęciu stosownej
akcji. Można to realizować przy pomocy drabinki if-else-if, ale
najwygodniejsze jest stosowanie instrukcji switch.

LONG FAR PASCAL WindowProc(HWND hWnd, WORD Message,
WORD wParam, LONG lParam)
{
switch (Message)
{
case WM_CREATE:
.....
break; /* Koniec obsługi komunikatu WM_CREATE */
case WM_MOVE:
.... /* Kod obsługi komunikatu WM_MOVE */
break; /* Koniec obsługi WM_MOVE. */
case WM_SIZE:
.... /* Kod obsługi sytuacji WM_SIZE */
break; /* Koniec obsługi WM_SIZE */

.......... /* Inne, pozostałe możliwe sytuacje */

case WM_CLOSE: /* Zamknięcie okna */
....
break;
default: /* wariant domyślny: standardowa obsługa
.... przez standardową funkcję Windows */
}
}
________________________________________________________________
UWAGA:
Ponieważ komunikatów "interesujących" daną aplikację może być
ponad 100 a sposobów reakcji użytkownika jeszcze więcej, w
"poważnych" aplikacjach tworzone są często struktury decyzyjne o

większym stopniu złożoności. Jeśli istnieje potrzeba
optymalizacji działania programów stosuje się struktury dwu
typów:
* hierarchia wartości (Value Tree) i
* drzewo analizy zdarzeń (Event Tree).
Utworzone w taki sposób tzw. "Drzewo decyzyjne" nazywane także
"Drzewem analizy zdarzeń" może być wielopoziomowe. Widoczny
powyżej pierwszy poziom drzewa (pierwszy przesiew) realizowany
jest zwykle przy pomocy instrukcji switch a następne przy pomocy

drabinek typu if-else-if-break. Schemat if-else-if-break często
bywa zastępowany okienkami dialogowymi.
________________________________________________________________


Parametry wParam i lParam przechowują parametry istotne dla
danego komunikatu. wParam ma długość pojedynczego słowa (word) a

lParam ma długość podwójnego słowa (long). Jeśli, dla przykładu,

okno zostało przesunięte, te parametry zawierają nowe
współrzędne okna.

Jeżeli program ma być programem zdarzeniowym, powinniśmy przed
podjęciem jakiejkolwiek akcji zaczekać aż Windows przyślą nam
komunikat o tym, jakie zdarzenie nastąpiło. Wewnątrz Windows
tworzona jest dla komunikatów kolejka (ang message queue).
Dzięki istnieniu kolejkowania otrzymujemy komunikaty pobierane z

kolejki pojedynczo. Jeśli użytkownik przesunie okno a następnie
przyciśnie klawisz, to Windows wywołają funkcję WindowProc()
najpierw z parametrem WM_MOVE a następnie z parametrem
WM_KEYDOWN.

Jednym z najważniejszych zadań funkcji WinMain() jest utworzenie

kolejki dla komunikatów i poinformowanie Windows, że komunikaty
do naszego programu należy kierować pod adresem funkcji
WindowProc(). W tym celu stosuje się daleki wskaźnik do
procedury okienkowej lpfn (Long Pointer to Function). Poza tym
funkcja WinMain() tworzy okno (okna) i wyświetla je na ekranie w

pozycji początkowej. Kiedy program zostaje po raz pierwszy
załadowany i uruchomiony - Windows najpierw wywołują funkcję
WinMain().

Windows manipulują komunikatami posługując się strukturą MSG (od

messages - komunikaty). Struktura MSG jest zdefiniowana w pliku
WINDOWS.H w następujący sposób:

typedef struct tagMSG
{
HWND hwnd;
WORD message;
WORD wParam;
LONG lParam;
DWORD time;
POINT pt;
} MSG;

Na pierwszym polu tej struktury znajduje się "identyfikator"
(kod) okna, dla którego przeznaczony jest komunikat (każdy
komunikat może być przesłany tylko do jednego okna). Na drugim
polu struktury przechowywany jest sam komunikat. Komunikat jest
zakodowany przy pomocy predefiniowanych stałych w rodzaju
WM_SIZE, WM_PAINT czy WM_MOUSEMOVE. Kolejne dwa pola służą do
przechowania danych-parametrów towarzyszących każdemu
komunikatowi: wParam i lParam. Na następnym polu przechowywany
jest w zakodowanej postaci czas - moment, w którym wystąpiło
zdarzenie. Na polu pt przechowywane są współrzędne kursora
myszki na ekranie w momencie w którym został wygenerowany
komunikat o wystąpieniu zdarzenia. Należy zwrócić tu uwagę, że
typ POINT oznacza strukturę. Struktura POINT (punkt) w Windows
wygląda tak:

typedef struct tagPOINT
{
int x;
int y;
} POINT;

Aby mieć pewność, że otrzymaliśmy wszystkie komunikaty, które
zostały do nas skierowane, w programie wykonywana jest pętla
pobierania komunikatów (message loop) wewnątrz funkcji
WinMain(). Na początek wywoływana jest zwykle okienkowa (czyli
należącą do Windows API) funkcja GetMessage(). Ta funkcja
wypełnia strukturę komunikatów i zwraca wartość. Zwracana przez
funkcję wartość jest różna od zera, jeżeli otrzymany właśnie
komunikat był czymkolwiek za wyjątkiem WM_QUIT. Komunikat
WM_QUIT jest komunikatem kończącym pracę każdej aplikacji dla
Windows. Jeśli otrzymamy komunikat WM_QUIT powinniśmy przerwać
pętlę pobierania komunikatów i zakończyć pracę funkcji
WinMain(). Taka sytuacja oznacza, że więcej komunikatów nie
będzie. Po uwzględnieniu tych warunków pętla może wyglądać tak:

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, \
LPSTR lpszCmdLine, int nCmdShow)
....
while(GetMessage(&msg,NULL,0,0)) //Poki nie otrzymamy WM_QUIT
{
....
}

Po naciśnięciu przez użytkownika klawisza generowany jest
komunikat WM_KEYDOWN. Jednakże z faktu otrzymania komunikatu
WM_KEYDOWN nie wynika, który klawisz został przyciśnięty, czy
była to duża, czy mała litera. Funkcję TranslateMessage()
(PrzetłumaczKomunikat) stosuje się do przetłumaczenia komunikatu

WM_KEYDOWN na komunikat WM_CHAR. Komunikat WM_CHAR przekazuje
przy pomocy parametru wParam kod ASCII naciśniętego klawisza.
Funkcję TranslateMessage() stosujemy w pętli pobierania
komunikatów tak:

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, \
LPSTR lpszCmdLine, int nCmdShow)
....
while(GetMessage(&msg, 0, 0, 0))
{
TranslateMessage(&msg);
....
}

W tym stadium program jest gotów do przesłania komunikatu do
funkcji - procedury okienkowej WindowProc(). Posłużymy się w tym

celu funkcją DispatchMessage() (ang. dispatch - odpraw, przekaż,

DispatchMessage = OtprawKomunikat). Funkcja WinMain()
poinformowała wcześniej Windows, że odprawiane komunikaty
powinny trafić właśnie do WindowProc().

while(GetMessage(&msg, NULL, NULL, NULL))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

Tak funkcjonuje pętla pobierająca komunikaty od Windows i
przekazująca je funkcji WindowProc(). Pętla działa do momentu
pobrania komunikatu WM_QUIT (Koniec!). Otrzymanie komunikatu
WM_QUIT powoduje przerwanie pętli i zakończenie pracy programu.
Komunikaty systemowe (system messages), które są kierowane do
Windows także trafiają do tej pętli i są przekazywane do
WindowProc(), ale ich obsługą powinna się zająć specjalna
funkcja DefWindowProc() - Default Window Procedure, umieszczona
na końcu (wariant default).
Jest to standardowa dla aplikacji okienkowych postać pętli
pobierania komunikatów.

Jak widać, wymiana informacji pomiędzy użytkownikiem,
środowiskiem a aplikacją przebiega tu trochę inaczej niż w DOS.
Program pracujący w środowisku tekstowym DOS nie musi np.
rysować własnego okna.

[Z]
________________________________________________________________
1. Uruchom Windows i popatrz świadomym, fachowym okiem, jak
przebiega przekazywanie aktywności (focus) między okienkami
aplikacji.
________________________________________________________________
  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • qualintaka.pev.pl
  •