Az előző rész enyhén szólva elméletire
sikeredett, úgyhogy most egy kicsit gyakorlatiasabbra vesszük a figurát
- elkezdjük megírni azt, amiről a múlt alkalommal rizsáztam.
Kezdjük az alapobjektumnál!
Az egész rendszer (a régi XVision-nel ellentétben)
egyetlen alapobjektumra épül, vagyis maga a rendszer is egy képernyőelem
(éppen az, amelyik legfelül van).
Az alapobjektum felépítése:
PView = ^TView;
TView = Object
Prev, Next: PView;
Owner: PView;
Child, LChild: PView;
Options: Word;
X, Y, W, H: Integer;
EventMask: Word;
TabNum: Integer;
Constructor Init(ax, ay, aw, ah: Integer);
Procedure Insert(P: PView); Virtual;
Procedure HandleEvent(var Event: TEvent);
Virtual;
Procedure Draw; Virtual;
Procedure DrawIt; Virtual;
Procedure DrawView; Virtual;
Procedure GetVideoArea(var xx, yy: Integer);
End;
Azt hiszem, a fenti kis deklaráció némi magyarázatra szorul:
Mezők:
-
a Prev és Next metódusok az
előző és a következő objektumra mutatnak (ha nincs előző vagy következő,
akkor a megfelelő mező értéke NIL).
-
Owner: mutató az objektum “szülő” objektumára
-
Child, LChild: Az objektum alatt lévő
első és utolsó objektum (ha nincs az objektum alatt még egy objektumszint,
akkor mindkettő NIL).
-
Options: Az objektum általános beállításait fogja tartalmazni (látszik
/ nem látszik, engedélyezett / tiltott stb.)
-
X, Y, W, H: Az objektum kiterjedése (X, Y, Szélesség, Magasság)
-
EventMask: átvettük a régi XVision-ből
az eseményrendszert (TEvent) - ez azt tartalmazza, hogy az objektumok
HandleEvent (eseményértelmező) metódusa milyen események beérkezésekor
hívódjon meg.
-
TabNum: A múlt havi cikkben volt róla szó, hogy az objektumok sorrendje
folyton változik és emiatt a “Tab” gomb használata csak egy olyan változóval
oldható meg, amely a tabulálási sorrendet tartalmazza. Ha a TabNum -1,
akkor az objektum nem fókuszálható.
Metódusok:
Constructor Init(ax, ay,
aw, ah: Integer);
A TView konstruktora. Itt állítjuk be
az objektumok mezőinek kezdőértékét.
Constructor TView.Init(ax, ay, aw, ah: Integer);
Begin
X:=AX; Y:=AY; W:=AW; H:=AH;
Options:=0;
TabNum:=0;
EventMask := evMouseDown + evMouseUp
Prev := Nil; Next := Nil; Child := Nil; LChild := Nil; Owner
:= Nil;
End;
Procedure Insert(P: PView);
Virtual;
Ez a metódus végzi az új objektumok rendszerbe
illesztését. Amelyik objektum Insert metódusát hívjuk, a fában az alá fog
illeszkedni az új objektum. (Az új, beillesztendő objektum címét adjuk
meg paraméterként);
Procedure TView.Insert(P: PView);
Var
MaxNum: Integer;
Q: PView;
Begin
P^.Owner := @Self;
if Child <> Nil then
Begin
Child^.Prev := P;
P^.Next := Child;
Child := P;
End
Else
Begin
Child := P;
LChild := P;
End;
if P^.TabNum<>-1 then
Begin
Q := Child;
MaxNum:=0;
While Q <>
Nil do
Begin
if
Q^.TabNum > MaxNum then MaxNum := TabNum;
Q:=Q^.Next;
End;
P^.TabNum:=MaxNum+1;
End;
P^.DrawView;
End;
A procedúra először beállítja a beillesztendő
objektum Owner mezőjét (a beillesztő
objektumra mutat) majd beszúrja a beillesztő objektum alatti szint első
objektumának (pirossal jelölt rész). Az objektum már benne van a listában
- beállítja a TabNum mező értékét
(kék) és kirajzolja a képernyőre az objektumot.
Procedure HandleEvent(var Event:
TEvent); Virtual;
A HandleEvent-re nem kell sok helyet pazarolni, ugyanis az alapobjektumban
üres (Ez a procedúra tartalmazza, hogy mit kell csinálnia az objektumnak,
ha eseményt kap).
Procedure Draw; Virtual;
Szintén gyorsan elintézhető a Draw metódus
is, mivel szintén üres. (ebben kell megírni az objektum kirajzolását.
FIGYELEM!! Sose hívjuk a Draw metódust! Mindig a DrawIt metódus
hívásával rajzoljunk ki!)
Procedure DrawIt; Virtual;
A metódus kirajzolja az objektumot (csak az objektumot, ami alatta
van azt nem!)
Procedure TView.DrawIt;
Var
P: PView;
xx, yy: Integer;
Begin
P := @Self;
While ((P <> Nil) and (P <> Application))
do
Begin
P := P^.Owner;
End;
if P = Application then
Begin
Mouse_Hide;
GetVideoArea(xx, yy);
SetViewPort(xx, yy, xx+w, yy+h, True);
Draw;
Mouse_Show;
End;
End;
Ezt a metódust kell hívni, ha ki akarjuk rajzoltatni az objektumot.
A rutin először ellenőrzi, hogy a képernyőelem szerepel-e az objektumokat
tartalmazó fa szerkezetben (erre azért van szükség, mert így egyetlen objektum
egyetlen mezőjének átírásával egész “faágakat” tilthatunk ki a rajzolásból)
- ha igen, akkor lekapcsolja az egeret, megnézi
az objektum pontos helyét (erre azért van szükség, mert az objektum X és
Y mezője mindig a szülő objektum bal felső sarkához képest tartalmazza
a képernyőelem koordinátáit) majd definiál egy ablakot a képernyőn
ezzel biztosítva, hogy az objektum mindig úgy rajzolhat ki, mintha a bal
felső sarokban lenne. A Draw metódus csak most hívódik meg. Miután az objektum
befejezte a kirajzolást, visszakapcsolja az egeret.
Procedure DrawView; Virtual;
Az objektum és az alatta lévő objektumok
kirajzolására szolgál
Procedure TView.DrawView;
Var
P: PView;
xx, yy: Integer;
Begin
DrawIt;
P := @Self;
P := LChild;
While P <> Nil do
Begin
P^.DrawView;
P := P^.Prev;
End;
End;
Ez a metódus kirajzolja az objektumot
és mindent, ami alatta van. (Az Application.DrawView frissíti a képernyőt)
A rutin először meghívja a DrawIt metódust majd az objektum alatt egyel
lévő szinten minden objektum DrawView metódusát meghívja, ezzel rekurzív
módon bejárva a fát.
Procedure GetVideoArea(var xx,
yy: Integer);
Ez a metódus az objektum fizikai koordinátáival tér vissza.
Procedure TView.GetVideoArea(var xx, yy: Integer);
Var
P: PView;
Begin
P:=@Self;
xx:=0;
yy:=0;
While P<>Nil do
Begin
xx:=xx+P^.X;
yy:=yy+P^.Y;
P:=P^.Owner;
End;
End;
A működése a következő: A rutin a hívó
objektumtól egy ciklussal lépked felfelé a listában és mindig hozzáadja
az adott szinten lévő szülőobjektum koordinátáit a már meglévőkhöz ?
mire felér a rendszerobjektumig, xx és yy a fizikai objektumkoordinátákat
fogja tartalmazni.
Egyelőre ennyi a TView, ennyi az, amivel már
valamit csináló rendszert lehet írni. Természetesen még rengeteg dolog
hiányzik például: sehol sem használtuk
ki az Options mezőt, nincs megírva a képernyőelem törlése a memóriából.
A rendszer működéséhez szükséges változók és
globális procedúrák
Kezdjük talán a változókkal:
Var
Application: PView;
Exiting: Boolean;
MouseInstalled: Boolean;
MouseShown: Boolean;
OldX,OldY: Integer;
OldButtons: Byte;
Az Application változó a fában a legfelső
objektumot tartalmazza, ez a rendszerobjektum.
Az Exiting majd később lesz érdekes,
ha az értéke True, akkor a leáll a rendszer.
A többi változó az esemény- és egérkezeléshez kell.
Globális procedúrák és függvények:
Function HiMouseX: Word;
Az egér X koordinátájával tér vissza
Function HiMouseY: Word;
Az egér Y koordinátájával tér vissza
Function buttons: Byte;
Az egérgombok pillanatnyi állapotát adja vissza
procedure mouse_show;
Egér bekapcsolása - csak ha a mouseshown false
procedure mouse_hide;
Egér kikapcsolása - csak ha a mouseshown true
Function IsInArea(X,Y,XX,YY,WW,HH:
Integer): Boolean;
Megadja, hogy X, Y koordináta benne van-e az XX, YY, WW, HH által
meghatározott téglalapban
Procedure SetFocus(P: PView);
Adott objektumot fókuszálttá tesz - csak ha a tabnum nem -1.
Úgy gondolom, ez a procedúra bővebb kifejtést
érdemel, úgyhogy most ez következik:
Procedure SetFocus(P: PView);
Begin
While ((P<>Application) and (P<>Nil))
do
Begin
if ((P^.TabNum<>-1) and (P^.Prev<>Nil))
then
Begin
if
P^.Next<>Nil then P^.Next^.Prev:=P^.Prev Else
P^.Owner^.LChild:=P^.Owner^.LChild^.Prev;
if
P^.Prev<>Nil then P^.Prev^.Next:=P^.Next Else
P^.Owner^.Child:=P^.Owner^.Child^.Next;
P^.Next:=P^.Owner^.Child;
P^.Prev:=Nil;
P^.Owner^.Child^.Prev:=P;
P^.Owner^.Child:=P;
P^.DrawView;
End;
P:=P^.Owner;
End;
End;
A rutin elindul a megadott objektumtól és felfelé lépeget a listában,
miközben az objektumot és szülőit kiveszi a listából (kék) és előre rakja
(piros). Ha nem volt a lista elején az objektum akkor kirajzolja.
Procedure GetEvent(Var E: TEvent);
Eseményfigyelő rutin, ha nincs esemény
akkor EvNothing eseményt ad vissza.
A rendszerobjektum (TApplication)
PApplication = ^TApplication;
TApplication = Object(TView)
Constructor Init;
Procedure SetGraphMode; Virtual;
Procedure Draw; Virtual;
Procedure Run; Virtual;
Procedure Execute(Def: PView); Virtual;
End;
Constructor Init;
Constructor TApplication.Init;
Begin
MouseInstalled := False; MouseShown:=False;
Exiting:=False;
Application:=@Self;
WriteLn('XVISION2> Rendszerelemek
azonosítása...');
Asm
MOV AX, 0
INT 33h
CMP AX, 0 ; JE @@Exit;
MOV BYTE(MouseInstalled),
1
@@Exit:
End;
WriteLn('XVISION2> Átkapcsolás grafikus
módba...');
SetGraphMode;
Inherited Init(0,0,GetMaxX, GetMaxY);
Mouse_Show;
DrawIt;
End;
A főprogramban, amikor a rendszert
inicializáljuk, majd ezt az Init konstruktort fogjuk hívni tehát erre a
procedúrára hárul a teljes rendszer beállítása.
A konstruktor először beállítja az eseménykezelés
kezdeti értékeit és az Exiting változót. Beállítja a globális application
változó értékét, megnézi, hogy van-e egér és beállítja a mouseinstalled
változó értékét. Ezután a rendszer átkapcsolódik grafikus módba (Setgraphmode),
majd meghívódik az alapobjektum Init metódusa, ami beállítja a rendszerobjektum
metódusainak kezdőértékét. Ezután egér bekapcs.
és kirajzolás - felállt a rendszer.
Procedure Draw; Virtual;
Ez tartalmazza az alkalmazás hátterének kirajzolását.
Procedure Run; Virtual;
Ez hívja az Execute metódust, ami a tulajdonképpeni “rendszermag”.
Procedure Execute(Def: PView);
Virtual;
Procedure TApplication.Execute;
Var
E: TEvent;
P,Q: PView;
xx, yy: Integer;
Begin
Repeat
GetEvent(E);
Case E.What
of
EvMouseMove,
EvMouseDown,
EvMouseUp :Begin
P:=Def; Q:=Nil;
While (P<>Nil)
do
Begin
P^.GetVideoArea(xx,yy);
if IsInArea(E.WhereX, E.WhereY, xx, yy, P^.W, P^.H) then
Begin
Q:=P;
P:=P^.Child;
End
Else
Begin
P:=P^.Next;
End;
End;
if Q<>Nil
then
Begin
if (Q^.EventMask and E.What)<>0 then
Begin
SetFocus(Q);
Q^.HandleEvent(E);
End;
End;
End;
EvKeyDown: Begin
End;
End;
Until Exiting;
End;
Maga a rendszermag. A működése egyszerű:
A program egy ciklusban kering (a repeat-until) aminek csak akkor szakad
vége, ha valami az Exiting változót True-ra állítja.
A ciklusban a rendszer megnézi, hogy érkezett-e esemény (GetEvent(E))
majd az esemény típusától függően dönt
a Case-es szerkezetben:
Ha egér-esemény érkezett, akkor meg kell
keresni az objektumfában az a legmélyebben lévő objektumot, ami felett
történt az esemény (ha egy ablakban van egy gomb és valaki a gombra kattint,
akkor nyilvánvaló, hogy a gombnak és nem az ablaknak kell adni az
eseményt).
Billentyűzet-eseménynél az aktuálisan
fókuszált objektumok közül a legmélyebb szinten levőnek
kell adni az eseményt.
Mielőtt átadnánk az objektum HandleEvent-jének
a vezérlést, meg kell győződnünk arról, hogy
az objektum jogosult-e az eseményre (lehet, hogy az esemény nem szerepel
az Eventmask-jában vagy éppenséggel tiltott vagy nem látható állapotban
van a képernyőelem).
Az egér-esemény átadásáról részletesebben:
P:=Def; Q:=Nil;
While (P<>Nil) do
Begin
P^.GetVideoArea(xx,yy);
if IsInArea(E.WhereX, E.WhereY,
xx, yy, P^.W, P^.H) then
Begin
Q:=P;
P:=P^.Child;
End
Else
Begin
P:=P^.Next;
End;
End;
if Q<>Nil then
Begin
if (Q^.EventMask and E.What)<>0
then
Begin
SetFocus(Q);
Q^.HandleEvent(E);
End;
End;
Látható, hogy a ciklus elindul a legfelső
szintről (def) és megnézi, hogy az egér-esemény helye benne
van-e az épp aktuális objektum területében. Ha nem, akkor továbblép a rendszer
és nézi az azonos szinten lévő, következő objektumot. Ha benne van az egér-esemény
helye az objektumban akkor Q-ban eltárolódik
a legmélyebben lévő ismert, a feltételeknek megfelelő objektum címe. P
(amin az összehasonlítást végezzük) egy szinttel lejjebb lép, és folytatódik
a ciklus. Ha a P Nil lesz (például mert olyan mély szintre lépett, ahol
már nincs objektum), akkor a ciklus véget ér és Q-ban megvan azaz
objektum, amelynek oda kell adni az eseményt. Ezután ellenőrzések jönnek
- ha Q nil (ilyen is előfordulhat) vagy ha Q nem jogosult valamilyen okból
az eseményre, akkor senki sem kapja meg az eseményt.
ITT A VÉGE, FUSS EL VÉLE de ha tetszett...
...A lemezen megtalálod az XV2APP.PAS fájlt, amiből
a fent tárgyalt forráskódok származnak és az XV2DEMO1.PAS fájlt, ami egy
bugyuta példa a XVision 2 használatára. Kilépés: Ctrl+Pause!
A rendszer egyelőre Graph unittal működik,
de ha nagyobb színmélységet akarsz (vonatkozik ez a XVision 1-re
is) keress egy 256 színt tartalmazó grafikus meghajtót (BGI) és a TApplication
Setgraphmode metódusát (XV2-ben) átírva működtetheted
256 színben a rendszert. (Sajna jogi okok miatt nem kerülhet fel a CD-re
az SVGA256.BGI)
Gerebenics Andor

