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);
        CaseE.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;
                    ifQ<>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...