Blog literacki, portal erotyczny - seks i humor nie z tej ziemi
LEKCJA 46: O PROGRAMACH OBIEKTOWO - ZDARZENIOWYCH.
________________________________________________________________
Po aplikacjach sekwencyjnych, proceduralno-zdarzeniowych, jedno-
i dwupoziomowych, pora rozważyć dokładniej stosowanie technik
obiektowych.
________________________________________________________________
Programy pracujące w środowisku Windows tworzone są w oparciu o
tzw. model trójwarstwowy. Pierwsza warstwa to warstwa
wizualizacji, druga - interfejs, a trzecia - to właściwa
maszyneria programu. W tej lekcji zajmiemy się "anatomią"
aplikacji wielowarstwowych a następnie sposobami wykorzystania
bogatego instrumentarium oferowanego przez Borlanda wraz z
kompilatorami BC++ 3+...4+.
Biblioteka OWL w wersjach BORLAND C++ 3, 3.1, 4 i 4.5 zawiera
definicje klas potrzebnych do tworzenia aplikacji dla Windows.
Fundamentalne znaczenie dla większości typowych aplikacji mają
następujące klasy:
TModule (moduł - program lub biblioteka DLL)
TApplication (program - aplikacja)
TWindow (Okno)
Rozpocznę od krótkiego opisu dwu podstawowych klas.
KLASA TApplication.
Tworząc obiekt klasy TNaszProgram będziemy wykorzystywać
dziedziczenie od tej właśnie klasy bazowej:
class TNaszProgram : public TApplication
Podstawowym celem zastosowania tej właśnie klasy bazowej jest
odziedziczenie gotowej funkcji - metody virtual InitMainWindow()
(zainicjuj główne okno programu). Utworzenie obiektu klasy
TNaszProgram następuje zwykle w czterech etapach:
* Windows uruchamiają program wywołując główną funkcję WinMain()
lub OwlMain() wchodzącą w skład każdej aplikacji.
* Funkcja WinMain() tworzy przy pomocy operatora new nowy obiekt
- aplikację.
* Obiekt - aplikacja zaczyna funkcjonować. Konstruktor obiektu
(własny, bądź odziedziczony po klasie TApplication) wywołuje
funkcję - wirtualną metodę InitMainWindow().
* Funkcja przy pomocy operatora new tworzy obiekt - okno
aplikacji.
Wskaźnik do utworzonego obiektu zwraca funkcja GetApplication().
Dla zobrazowania mechanizmów poniżej przedstawiamy uproszczony
"wyciąg" z dwu opisywanych klas. Nie jest to dokładna kopia kodu
źródłowego Borlanda, lecz skrót tego kodu pozwalający na
zrozumienie metod implementacji okienkowych mechanizmów wewnątrz
klas biblioteki OWL i tym samym wewnątrz obiektów obiektowo -
zdarzeniowych aplikacji.
A oto najważniejsze elementy implementacji klasy TApplication:
- Konstruktor obiektu "Aplikacja":
TApplication::TApplication(const char far* name,
HINSTANCE Instance,
HINSTANCE prevInstance,
const char far* CmdLine,
int CmdShow,
TModule*& gModule)
{
hPrevInstance = prevInstance;
nCmdShow = CmdShow;
MainWindow = 0;
HAccTable = 0; //Accelerator Keys Table Handle
BreakMessageLoop = FALSE;
AddApplicationObject(this); //this to wskaźnik do własnego
gModule = this; //obiektu, czyli do bież. aplikacji
}
Funkcja - metoda "Zainicjuj Instancję":
void TApplication::InitInstance()
{
InitMainWindow();
if (MainWindow)
{
MainWindow->SetFlag(wfMainWindow);
MainWindow->Create();
MainWindow->Show(nCmdShow);
}
Metoda "Zainicjuj główne okno aplikacji":
void TApplication::InitMainWindow()
{
SetMainWindow(new TFrameWindow(0, GetName()));
}
Metoda Run() - "Uruchom program":
int TApplication::Run()
{
int status;
{
if (!hPrevInstance) InitApplication();
InitInstance();
status = MessageLoop();
}
A oto pętla pobierania komunikatów w uproszczeniu. "Pump" to po
prostu "pompowanie" komunikatów (message) oczekujących (waiting)
w kolejce. PeekMessage() to sprawdzenie, czy w kolejce oczekuje
komunikat. PM_REMOWE to "brak komunikatu".
BOOL TApplication::PumpWaitingMessages()
{
MSG msg;
BOOL foundOne = FALSE;
while (::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
foundOne = TRUE;
if (msg.message == WM_QUIT)
{
BreakMessageLoop = TRUE;
MessageLoopResult = msg.wParam;
::PostQuitMessage(msg.wParam);
break;
}
if (!ProcessAppMsg(msg))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
return foundOne;
}
int TApplication::MessageLoop()
{
long idleCount = 0;
MessageLoopResult = 0;
while (!BreakMessageLoop) {
TRY {
if (!IdleAction(idleCount++))
::WaitMessage();
if (PumpWaitingMessages())
idleCount = 0;
}
if (MessageLoopResult != 0) {
::PostQuitMessage(MessageLoopResult);
break;
}
})
}
BreakMessageLoop = FALSE;
return MessageLoopResult;
}
else if (::IsWindowEnabled(wnd)) {
*(info->Wnds++) = wnd;
::EnableWindow(wnd, FALSE);
}
}
return TRUE;
}
KLASA TWindow.
Klasa TWindow (Okno) zawiera implementację wielu przydatnych
przy tworzeniu aplikacji "cegiełek". Poniżej przedstawiono
fragment pliku źródłowego (patrz \SOURCE\OWL\WINDOW.CPP). Łatwo
można rozpoznać pewne znane już elementy.
...
extern LRESULT FAR PASCAL _export InitWndProc(HWND, UINT,
WPARAM, LPARAM);
...
struct TCurrentEvent //Struktura BieżąceZdarzenie
{
TWindow* win; //Wskażnik do okna
UINT message; //Komunikat
WPARAM wParam;
LPARAM lParam;
};
...
DEFINE_RESPONSE_TABLE(TWindow)
//Makro: Zdefiniuj tablicę odpowiedzi na zdarzenia
//EV_WM_SIZE - Zdarzenie (EVent)-nadszedł komunikat WM_SIZE
...
EV_WM_SETCURSOR,
EV_WM_SIZE,
EV_WM_MOVE,
EV_WM_PAINT,
EV_WM_LBUTTONDOWN,
EV_WM_KILLFOCUS,
EV_WM_CREATE,
EV_WM_CLOSE,
EV_WM_DESTROY,
EV_COMMAND(CM_EXIT, CmExit),
...
END_RESPONSE_TABLE;
Funkcje - metody obsługujące komunikaty zaimplementowane zostały
wewnątrz klasy TWindow tak:
TWindow::EvCreate(CREATESTRUCT far&)
{
SetupWindow();
return (int)DefaultProcessing();
}
void TWindow::EvSize(UINT sizeType, TSize&)
{
if (Scroller && sizeType != SIZE_MINIMIZED)
{
Scroller->SetPageSize();
Scroller->SetSBarRange();
}
}
Metoda GetWindowClass() bardzo przypomina klasyczne
zainicjowanie zanej już struktury WNDCLASS:
void TWindow::GetWindowClass(WNDCLASS& wndClass)
{
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = *GetModule();
wndClass.hIcon = 0;
wndClass.hCursor = ::LoadCursor(0, IDC_ARROW);
wndClass.hbrBackground = HBRUSH(COLOR_WINDOW + 1);
wndClass.lpszMenuName = 0;
wndClass.lpszClassName = GetClassName();
wndClass.style = CS_DBLCLKS;
wndClass.lpfnWndProc = InitWndProc;
}
Skoro te wszystkie "klocki" zostały już zaimplementowane
wewnątrz definicji klas, nasze programy powinny tylko umiejętnie
z nich korzystać a teksty źródłowe programów powinny ulec
skróceniu i uproszczeniu.
STADIA TWORZENIA OBIEKTOWEJ APLIKACJI.
Ponieważ znakomita większość dzisiejszych użytkowników pracuje z
Windows 3.1, 3.11, i NT - zaczniemy tworzenie aplikacji od
umieszczenia na początku informacji dla OWL, że nasz docelowy
program ma być przeznaczony właśnie dla tego środowiska:
#define WIN31
Jak już wiemy dzięki krótkiemu przeglądowi struktury bazowych
klas przeprowadzonemu powyżej - funkcje API Windows są w istocie
klasycznymi funkcjami posługującymi się mechanizmami języka C.
C++ jest "pedantem typologicznym" i przeprowadza dodatkowe
testowanie typów parametrów przekazywanych do funkcji (patrz
"Technika programowania w C++"). Aby ułatwić współpracę,
zwiększyć poziom bezpieczeństwa i "uregulować" potencjalne
konflikty - dodamy do programu:
#define STRICT
Chcąc korzystać z biblioteki OWL wypada dołączyć właściwy plik
nagłówkowy:
#include
Plik OWL.H zawiera już wewnątrz dołączony WINDOWS.H, który
występował we wcześniejszych aplikacjach proceduralno -
zdarzeniowych i jeszcze parę innych plików.
Ponieważ chcemy skorzystać z gotowych zasobów - odziedziczymy
pewne cechy po klasie bazowej TApplication. Zgodnie z zasadami
programowania obiektowego chcąc utworzyć obiekt musimy najpierw
zdefiniować klasę:
class TOkno ...
i wskazać po której klasie bazowej chcemy dziedziczyć:
class TOkno : public TApplication
{
...
Konstruktor obiektu klasy TOkno powinien tylko przekazać
parametry konstruktorowi klasy bazowej - i już.
class TOkno : public TApplication
{
public:
TOkno(LPSTR name, HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nShow) : TApplication(name,
hInstance, hPrevInstance, lpCmdLine, nShow)
{
return;
}
virtual void InitMainWindow();
};
Umieściliśmy w definicji klasy jeszcze jedną funkcję inicjującą
główne okno aplikacji. Możemy ją zdefiniować np. tak:
void TOkno::InitMainWindow(void)
{
MainWindow = new (TWindow(0, "Napis - Tytul Okna"));
}
Działanie funkcji polega na utworzeniu nowego obiektu (operator
new) klasy bazowej TWindow. Główne okno stanie się zatem
obiektem klasy TWindow (Niektóre specyficzne aplikacje posługują
się okienkiem dialogowym jako głównym oknem programu. W takiej
sytuacji dziedziczenie powinno następować po klasie TDialog).
Konstruktorowi tego obiektu przekazujemy jako parametr napis,
który zostanie umieszczony w nagłówku głównego okna aplikacji.
Pierwszy argument (tu ZERO) to wskażnik do macieżystego okna,
ponieważ w bardziej złożonych aplikacjach występują okna
macieżyste (parent) i okna potomne (child). Okno macieżyste to
zwykle obiekt klasy "główne okno" a okno potomne to najczęściej
okienko dialogowe, bądź okienko komunikatów. W tym przypadku
wpisujemy zero, ponieważ program nie posiada w tym stadium
wcześniejszego okna macieżystego.
Pozostało nam jeszcze dodać funkcję WinMain() i pierwszy program
obiektowy w wersji "Maszyna do robienia nic" jest gotów.
Listing . Obiektowa "Maszyna do robienia nic"
________________________________________________________________
#define STRICT
#define WIN31
#include
class TOkno : public TApplication
{
public:
TOkno(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
: TApplication(AName, hInstance, hPrevInstance, lpCmdLine,
nCmdShow) {};
void InitMainWindow(){MainWindow = new TWindow(NULL, Name);};
};
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
TOkno OBIEKT("Windows - Program PW1", hInstance,
hPrevInstance, lpCmdLine, nCmdShow);
OBIEKT.Run();
return 0;
}
________________________________________________________________
Wykonanie takiej aplikacji przebiega następująco. Windows
wywołują główną funkcję WinMain(), która przekazuje swoje
parametry do konstruktora klasy TOkno::TOkno(). Konstruktor
przekazuje parametry do konstruktora klasy bazowej
TApplication(). Po skonstruowaniu obiektu w pamięci funkcja
wywołuje odziedziczoną metodę Run(). Funkcja Run() wywołuje
metody InitApplication() (zainicjuj aplikację) i InitInstance()
(zainicjuj dane wystąpienie programu). Metoda InitInstance()
wywołuje funkcję InitMainWindow(), która buduje główne okno
aplikacji na ekranie. Po pojawieniu się okna rozpoczyna
działanie pętla pobierania komunikatów (message loop). Pętla
komunikatów działa aż do otrzymania komunikatu WM_QUIT.
Rozbudujmy aplikację o okienko komunikatów. Zastosujemy do tego
funkcję MessageBox(). Funkcja zostanie użyta nie jako metoda
(składnik obiektu), lecz jako "wolny strzelec" (stand alone
function).
Listing B. Maszyna rozszerzona o okienka komunikatów.
________________________________________________________________
#define WIN31
#define STRICT
#include
class TOkno : public TApplication
{
public:
TOkno(LPSTR Nazwa, HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
: TApplication(Nazwa, hInstance, hPrevInstance, lpCmdLine,
nCmdShow) {};
void InitMainWindow(){MainWindow = new TWindow(NULL, "Okno
PW2" );};
};
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
TOkno OBIEKT("Okno PW2", hInstance, hPrevInstance,
lpCmdLine, nCmdShow);
LPSTR p1 = "Jesli wybierzesz [Anuluj]\n- aplikacja nie
ruszy!";
LPSTR p2 = "START";
if (MessageBox(NULL, p1, p2, MB_OKCANCEL) == IDCANCEL)
MessageBox(NULL, "I juz..." , "KONIEC" , MB_OK);
else
OBIEKT.Run();
return 0;
}
________________________________________________________________
Uwagi techniczne.
Ścieżki do katalogów:
..\INCLUDE;..\CLASSLIB\INCLUDE;..\OWL\INCLUDE;
..\LIB;..\CLASSLIB\LIB;..\OWL\LIB;
Konsolidacja:
Options | Linker | Settings: Windows EXE (typ aplikacji)
Options | Linker | Libraries:
- Container class Libraries: Static (bibl. klas CLASSLIB)
- OWL: Static (bibl. OWL statycze .LIB)
- Standard Run-time Lib: Static (bibl. uruchomieniowe .LIB)
(.) None - oznacza żadne (nie zostaną dołączone);
(.) Static - oznacza statyczne .LIB
(.) Dinamic - oznacza dynamiczne .DLL
________________________________________________________________
JAK ROZBUDOWYWAĆ OBIEKTOWE APLIKACJE?
Mimo całego uroku obiektowych aplikacji pojawia się tu wszakże
drobny problem. Skoro komunikacja klawiatura/myszka -> program
-> ekran nie odbywa się wprost, lecz przy pomocy wymiany danych
pomiędzy obiektami różnych warstw - w jaki sposób (w którym
miejscu programu) umieścić "zwyczajne" funkcje i procedury i jak
zorganizować wymianę informacji. "Zwyczajne" funkcje będą
przecież wchodzić w skład roboczych części naszych programów
(Engine). Rozważmy to na przykładzie aplikacji reagującej na
naciśnięcie klawisza myszki. Najbardziej istotny -
"newralgiczny" punkt programu został zaznaczony w tekście "<--
TU". Od Windows przejmiemy obsługę komunikatów WM_LBUTTONDOWN,
WM_RBUTTONDOWN. Aby wiedzieć, w którym miejscu ekranu jest
kursor myszki, wykorzystamy informacje przenoszone przez
parametr lParam.
Rozpoczniemy tworzenie programu od zdefiniowania klasy.
#define WIN31
#define STRICT
#include
#include
#include
class TNAplikacja : public TApplication
{
public:
TNAplikacja(LPSTR AName, HANDLE hInstance, HANDLE
hPrevInstance, LPSTR lpCmdLine, int nCmdShow) :
TApplication(AName, hInstance, hPrevInstance, lpCmdLine,
nCmdShow) {};
virtual void InitMainWindow();
};
Wykorzystamy okienko komunikatu do świadomego zakończenia pracy
aplikacji. Klasa TApplication jest wyposażona w metodę
CanClose() (czy można zamknąć?) służącą do zamykania głównego
okna aplikacji. Metoda została zaimplementowana tak:
BOOL TApplication::CanClose()
{
if (MainWindow)
return (MainWindow->CanClose());
else
return (TRUE);
}
Będzie nam więc potrzebna własna wersja metody CanClose() i
wskaźnik do obiektu MainWindow. Wskaźnik (typu far utworzony
przez składowe makro _FAR) wygenerujemy przy pomocy makra
_CLASSDEF(nazwa_klasy):
_CLASSDEF(TGOkno)
Implementujemy teraz klasę główne okno aplikacji. Jako klasę
bazową stosujemy TWindow.
class TGOkno : public TWindow
{
public:
TGOkno(PTWindowsObject AParent, LPSTR ATitle)
: TWindow(AParent, ATitle) {};
Konstruktor tradycyjnie wykorzystujemy do przekazania parametrów
do konstruktora klasy bazowej. PTWindowsObject AParent to
wskażnik (PoinTer) do obiektu "okno" a ATitle to string - tytuł.
Obsługa komunikatów kierowanych do tego okna może być
realizowana przy pomocy metod zaimplementowanych jako elemeny
składowe klasy Główne Okno - TGOkno.
Program graficzny powinien reagować raczej na myszkę niż na
klawiaturę. Windows rozpoznają zdarzenia związane z myszką i
generują komunikaty o tych zdarzeniach.
Zdarzenia myszki (mouse events).
________________________________________________________________
Komunikat Zdarzenie
________________________________________________________________
WM_MOUSEMOWE - przesunięto myszkę (wewnątrz obszaru
roboczego - inside the client area -
ICA)
WM_LBUTTONDOWN - naciśnięto LEWY klawisz myszki (ICA)
WM_LBUTTONDBLCLK - naciśnięto dwukrotnie LEWY klaw. (ICA)
WM_LBUTTONUP - puszczono LEWY klawisz (ICA)
WM_RBUTTONDOWN - naciśnięto PRAWY klawisz myszki (ICA)
WM_RBUTTONDBLCLK - naciśnięto dwukrotnie PRAWY klaw. (ICA)
WM_RBUTTONUP - puszczono PRAWY klawisz (ICA)
WM_MBUTTONDOWN - naciśnięto ŚRODK. klawisz myszki (ICA)
WM_MBUTTONDBLCLK - naciśnięto dwukrotnie ŚROD. klaw. (ICA)
WM_MBUTTONUP - puszczono ŚRODKOWY klawisz (ICA)
WM_NCMOUSEMOVE - ruch myszki poza client area (NCA)
WM_NLBUTTONDOWN - naciśnięto LEWY klawisz myszki poza
obszarem roboczym - non-client area
(NCA)
WM_NCLBUTTONDBLCLK - naciśnięto dwukrotnie LEWY klaw. (NCA)
WM_NCLBUTTONUP - puszczono LEWY klawisz (NCA)
WM_NCRBUTTONDOWN - naciśnięto PRAWY klawisz myszki (NCA)
WM_NCRBUTTONDBLCLK - naciśnięto dwukrotnie PRAWY klaw. (NCA)
WM_NCRBUTTONUP - puszczono PRAWY klawisz (NCA)
WM_NCMBUTTONDOWN - naciśnięto ŚR. klawisz myszki (NCA)
WM_NCMBUTTONDBLCLK - naciśnięto dwukrotnie ŚRODK. klaw. (NCA)
WM_LBUTTONUP - puszczono ŚRODKOWY klawisz (NCA)
________________________________________________________________
Następna tabelka zawiera (znacznie skromniejszy) zestaw
komunikatów generowanych pod wpływem zdarzeń związanych z
klawiaturą. Choćby z wizualnego porównaia wielkości tych tabel
wyrażnie widać, że Windows znacznie bardziej "lubią" współpracę
z myszką.
Komunikaty Windows w odpowiedzi na zdarzenia związane z
klawiaturą.
_______________________________________________________________
Komunikat Zdarzenie
_______________________________________________________________
WM_KEYDOWN Naciśnięto (jakiś) klawisz.
WM_KEYUP Puszczono klawisz.
WM_SYSKEYDOWN Naciśnięto klawisz "systemowy".
WM_SYSKEYUP Puszczono klawisz "systemowy".
WM_CHAR Kod ASCII klawisza.
________________________________________________________________
Klawisz systemowy to np. [Alt]+[Esc], [Alt]+[F4] itp.
________________________________________________________________
Komunikaty Windows możemy wykorzystać w programie.
...
BOOL CanClose();
void WMLButtonDown(RTMessage Msg)= [WM_FIRST + WM_LBUTTONDOWN];
void WMRButtonDown(RTMessage Msg)= [WM_FIRST + WM_RBUTTONDOWN];
};
Nasze Główne_Okno potrafi obsługiwać następujące zdarzenia:
* Funkcja CanClose() zwróciła wynik TRUE/FALSE,
* Naciśnięto lewy/prawy klawisz myszki.
Komunikat Msg zadeklarowany jako zmienna typu RTMessage jest w
klasie macieżystej TWindow wykorzystywany tak:
_CLASSDEF(TWindow)
class _EXPORT TWindow : public TWindowsObject
{
...
protected:
virtual LPSTR GetClassName()
{ return "OWLWindow"; }
virtual void GetWindowClass(WNDCLASS _FAR & AWndClass);
virtual void SetupWindow();
virtual void WMCreate(RTMessage Msg) = [WM_FIRST +
WM_CREATE];
virtual void WMMDIActivate(RTMessage Msg) =
[WM_FIRST + WM_MDIACTIVATE];
...
virtual void WMSize(RTMessage Msg) = [WM_FIRST + WM_SIZE];
virtual void WMMove(RTMessage Msg) = [WM_FIRST + WM_MOVE];
virtual void WMLButtonDown(RTMessage Msg) = [WM_FIRST +
WM_LBUTTONDOWN];
Zwróć uwagę na notację. Zamiast WM_CREATE pojawiło się [WM_FIRST
+ WM_CREATE]. Komunikat WM_FIRST jest predefiniowany w OWLDEF.H
i musi wystąpić w obiektowych aplikacjach w dowolnej klasie
okienkowej, bądź sterującej (window class/controll class), która
winna odpowiadać na określony komunikat. Oto fragment pliku
OWLDEF.H zawierający definicje stałych tej grupy:
#define WM_FIRST 0x0000
/* 0x0000- 0x7FFF window messages */
#define WM_INTERNAL 0x7F00
/* 0x7F00- 0x7FFF reserved for internal use */
#define ID_FIRST 0x8000
/* 0x8000- 0x8FFF child id messages */
#define NF_FIRST 0x9000
/* 0x9000- 0x9FFF notification messages */
#define CM_FIRST 0xA000
/* 0xA000- 0xFFFF command messages */
#define WM_RESERVED WM_INTERNAL - WM_FIRST
#define ID_RESERVED ID_INTERNAL - ID_FIRST
#define ID_FIRSTMDICHILD ID_RESERVED + 1
#define ID_MDICLIENT ID_RESERVED + 2
#define CM_RESERVED CM_INTERNAL - CM_FIRST
W tym momencie zwróćmy jeszcze uwagę, że funkcje z grupy
MessageHandlers są typu void i zwykle są metodami wirtualnymi -
przeznaczonymi "z definicji" do nadpisywania przez programistów
w klasach potomnych. Wszystkie te metody mają zawsze jedyny
argument - referencję do struktury TMessage zdefiniowanej
następująco:
struct TMessage
{
HWND Receiver; //Identyfikator okna - odbiorcy
WORD Message; //sam komunikat
union
{
WORD WParam; //Parametr WParam stowarzyszony z
//komunikatem; ALBO (dlatego unia!)
struct tagWP
{
BYTE Lo;
BYTE Hi;
} WP;
union
{
DWORD lParam;
struct tagLP
{
WORD Lo;
WORD Hi;
} LP;
};
long Result;
};
Po tych wyjaśnieniach możemy zaimplementować poszczególne
funkcje.
void TAplikacja::InitMainWindow()
{
MainWindow = new (0, Name);
}
Jeśli wybrano klawisz [Yes] funkcja zwróci IDYES. Jeśli funkcja
zwróciła IDYES - operator porównania zwróci TRUE (prawda) i ta
też wartość zostanie zwrócona przez metodę CanClose:
BOOL TMyWindow::CanClose()
{
return (MessageBox(HWindow, "Wychodzimy?",
"Koniec", MB_YESNO | MB_ICONQUESTION) == IDYES);
}
Stosunkowo najciekawsza kombinacja odbywa się wewnątrz handlera
komunikatu WM_LBUTTONDOWN. Ze struktury komunikatów pobierana
jest zawartość młodszego słowa parametru lParam - Msg.LP.Lo i
starszego słowa Msg.LP.Hi. Są to względne współrzędne graficzne
kursora myszki (względem narożnika okna) w momencie naciśnięcia
lewego klawisza myszki. Funkcja sprintf() zapisuje je w postaci
dwu liczb dziesiętnych %d, %d do bufora znakowego char
string[20]. Funkcja GetDC() (Get Device Context) określa
kontekst urządzenia (warstwa sterownika urządzenia) i dalej
obiekt może już stosując funkcję kontekstową "czuć się"
niezależny od sprzętu. Dane te w postaci znakowej są pobierane
przez funkcję kontekstową OutText() jako string a równocześnie
pobierane są w formie liczbowej: Msg.LP.Hi. Msg.LP.Lo, aby
wyznaczyć współrzędne tekstu na ekranie. Funkcja strlen()
oblicza długość łańcucha znakowego - i to już ostatni potrzebny
nam parametr.
void TMyWindow::WMLButtonDown(RTMessage Msg)
{
HDC DC;
char string[20];
sprintf(string, "(%d, %d)", Msg.LP.Lo, Msg.LP.Hi); <-- TU
DC = GetDC(HWindow);
TextOut(DC, Msg.LP.Lo, Msg.LP.Hi, string, strlen(string));
/* Można zwolnić kontekst */
ReleaseDC(HWindow, DC);
}
Ewentualna metoda unieważniająca prostokąt (invalid rectangle) i
kasująca w ten sposób zawartość okna w odpowiedzi na
WM_RBUTTONDOWN może zostać zaimplementowana np. tak:
void TMyWindow::WMRButtonDown(RTMessage)
{
InvalidateRect(HWindow, 0, 1);
}
Główny program to już tylko wywołanie metody Run() wobec
obiektu.
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
TNAplikacja OBIEKT("Wspolrzedne w oknie", hInstance,
hPrevInstance, lpCmdLine, nCmdShow);
OBIEKT.Run();
return (OBIEKT.Status);
}
Wyświetlanie współrzędnych jakkolwiek wartościowe z
dydaktycznego punktu widzenia jest mało interesujące. Pokusimy
się o obiektową aplikację umożliwiającą odręczne rysowanie w
oknie (freehand drawing).
[!!!]UWAGA
________________________________________________________________
Pakiety Borland C++ 3..4.5 zawierają wiele gotowych "klocków" do
wykorzystania. Oto przykład wykorzystania w pliku zasobów .RC
standardowego okienka wejściowego (Input Dialog Box) i
standardowego okienka typu Plik (File Dialog Box):
#include
#include
rcinclude INPUTDIA.DLG
rcinclude FILEDIAL.DLG
ROZKAZY MENU LOADONCALL MOVEABLE PURE DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&New" CM_FILENEW
MENUITEM "&Open" CM_FILEOPEN
MENUITEM "&Save" CM_FILESAVE
END
END
Takie menu można zastosować w programie obiektowym umieszcając
je w konstruktorze i dokonując nadpisania metody AssignMenu()
(przypisz menu):
TGOkno::TGOkno(PTWindowsObject AParent, LPSTR ATitle) :
TWindow(AParent, ATitle)
{
AssignMenu("ROZKAZY");
...
}
[S]
rcinclude - dołącz zasoby
LOADONCALL - załaduj po wywołaniu
owlrc - zasoby biblioteki klas OWL
Gotowe "klocki" można wykorzystać nawet wtedy, gdy nie pasują w
100%. Inne niż typowe odpowiedzi na wybór rozkazu implementujemy
w programie głównym poprzez nadpisanie wirtualnej metody
virtual void CMFileOpen(RTMessage msg) =
[CM_FIRST + CM_FILEOPEN]
TGOkno GOkno;
void TGOkno::CMFileOpen(RTMessage)
{
... obsługa zdarzenia ...
}
________________________________________________________________
[Z]
________________________________________________________________
1. Przeanalizuj gotowe zasoby dołączone do Twojej wersji Borland
C++.
2. Uruchom kilka projektów "firmowych" dołączonych w katalogu
\EXAMPLES. Zwróć szczególną uwagę na projekty STEPS (kolejne
kroki w tworzeniu aplikacji obiektowej).
________________________________________________________________