Haladó billentyűzet- és képernyőkezelés

 

 

25.) Vizsgáljuk meg ReadKey segítségével a billentyűzet pufferből kiolvasható kódokat.

 

Azt szeretnénk, hogy bármely billentyűre a program tovább fusson, ezért a szokásos leállításokat nem használhatjuk. Egyszerű, de ritkán használatos és kerülendő megoldást használtunk az állandó olvasás megvalósítására, a GoTo-t. A lista bármely utasítása címkével látható el (összetett esetén csak az első), és a program bármely helyéről a vezérlést feltétel nélkül ide lehet küldeni. A címke deklarációja: Label és egy jelsorozat, programunkban: 1. A címke lehelyezése: az utasítás előtt kettősponttal. Így a programunkban a WriteLn állandóan végrehajtódik. A program megszakítását Ctrl+Break-el kell kezdeményezni.

 

Program BillTest;

Uses NewDelay, Crt;

Label 1;

Begin

  TextMode(CO80);

  ClrScr;

  1: WriteLn(Ord(ReadKey));

  GoTo 1;

End.

 

Futtatva a programot, a következőket tapasztaljuk: az Esc, Tab, BackSpace, a normál betűk, számok és írásjelek egy kódot adnak vissza, Shift-el a kód változik. A következő billentyűk önmagukban nem adnak kódot: CapsLock, NumLock, ScrollLock, Pause, PrintScreen, Ctrl, Alt, Shift, F11 és F12. A további funkcióbillentyűk, a kurzorvezérlő, a Home, End, PageUp, PageDown, Insert és Delete billentyűk kettős kódot, amelyből az első nulla. Ha betűt Ctrl-el nyomunk más kódot kapunk, ha Alt-al, akkor kettős kódot.

 

26.) Írjunk programot, amely egy csillagot jelenít meg a képernyőn, és amelyet a kurzorvezérlő billentyűkkel mozgathatunk, Esc-re pedig befejeződik a program.

 

Program Vezerlo;

Uses NewDelay, Crt, CrtPlus;

Var Ch: Char;

    X, Y: Byte;

Begin

  TextMode(CO80);

  ClrScr;

  X:= 40;

  Y:= 12;  {a képernyő közepe}

  WriteXY(X,Y,'*');

  Tunj;

  Repeat

    Ch:= ReadKey;

    If Ch=#0 Then

    Begin

      Ch:= ReadKey;

      WriteXY(X,Y,' ');

      Case Ch Of

        #72: If Y>1 Then Dec(Y);

        #75: If X>1 Then Dec(X);

        #77: If X<80 Then Inc(X);

        #80: If Y<25 Then Inc(Y);

      End;

      WriteXY(X,Y,'*');

      Tunj;

    End;

  Until Ch=#27;

End.

 

A programban a ch változóban a billentyűzetről beolvasott karakter található, X a csillag x, Y a csillag y koordinátája. A vezérlést egy Repeat ciklus végzi. A leállítás #27-re történik, ami az Esc billentyű kódja. A ciklus első függvényhívása a ch:= Readkey; ami a futást megfogja, mindaddig nem lép tovább a program, amit a billentyűzethez hozzá nem nyulunk. Ha kettőskódú billentyűt nyomtunk meg, akkor annak első kódja #0, a második minden kettőskódú billentyűnél más. Így beazonosítható, hogy melyik volt megnyomva, ha újra olvassuk a billentyűzet puffert. A feltételes elágazás után ez a szerepe az újabb ReadKey-nek. Az ismétlő eljárásban az első WriteXY kikapcsolja, a második bekapcsolja a csillagot. Közben egy Case szerkezet dönt arról, hogy mozog-e és merre a csillag, azaz hogyan változzék az X és Y. Vezérlő kódok:

 

#72:  Fel,

#75:  Balra,

#77:  Jobbra és

#80:  Lefelé.

 

A változtatást csak akkor hajtja végre, ha a koordináták nem lépik túl a képernyőnek megfelelő értékeket. A WriteXY-ok utáni Tunj; eljárásnak az a szerepe, hogy a kurzor ne villogjon a csillag mellett.

 

 

Ha futtatjuk a programot és a képernyő jobb alsó sarkába vezéreljük a csillagot azt tapasztaljuk, hogy a képernyő jobb szélén további csillagok jelennek meg, mind ahányszor az utolsó képernyőhelyre szeretnénk lépni. Ez természetes, hiszen Write-al szeretnénk írni erre a helyre, ez pedig automatikus soremeléssel jár, ezért jelennek meg a további csillagok. Az ilyen természetű problémák megoldására ad lehetőséget a következő program.

 

27.) Írjunk programot, amely a video puffert feltölti azonos értékekkel, majd megjelenít rajta egy ablakot, majd újra visszaállítja az eredeti képernyőt.

 

Ennek a feladatnak a megoldásához több új dolog is szükséges. Az első mindjárt az, hogy a Pascal lehetőséget ad saját változó típusok deklarálására. Ezt a Type kulcsszó vezeti be. Ezt követi az új típusnév, esetünkben Scr (screen=képernyő). Aztán egy egyenlőség után az új típus leírása már ismert típusokkal. Példánkban az Scr típus egy, a video puffer szerkezéhez alkalmazkodó háromdimenziós tömb, melynek elemei byte-ok. Az első index jelenti a képernyő Y(!) koordinátáját, a második az X-et. (Tehát az eddigiekhez képest fordított sorrendben!). A harmadik a képernyőhely tartalmát írja le úgy, hogy az első érték a karakter képernyő kódja, a második a színe. Így a vezérlő karaktereknek is lesz képernyő kódja, azaz megjeleníthetők. A második érték a szín byte, melynek 8 bitje a következőket jelenti: az első bit a villogást, ha értéke 1 és 0, ha nem villog a karakter; a 2-3-4 bitek a háttérszint tartalmazzák (0-7 kódot); az 5-6-7-8 bitek a karakter színét (0-15 kódot). A képernyő tehát összesen 4000 Byte-al leírható, a memóriában egymás után található a karakter kódja, majd a színkódja.

 

Program VideoPuf;

Uses NewDelay, Crt, CrtPlus;

Type Scr=Array[1..25,1..80,1..2] Of Byte;

Var KKep: Scr;

    BKep: Scr Absolute $B800:0;

    I, J: Byte;

Begin

  TextMode(CO80);

  ClrScr;

  Tunj;

  For I:= 1 To 25 Do For J:= 1 To 80 Do

  BKep[I,J,1]:= 65;

  KKep:= BKep;

  Varj;

  Ablak(4,15, 20,5,60,15, True, 'Ez egy ablak');

  Tunj;

  Varj;

  BKep:= KKep;

  Varj;

End.

 

A Var deklarációban a KKep és BKep is egy-egy képernyőt definiálnak. Csakhogy, amíg a KKep helyét a fordító az adatszegmens üres helyére teszi, addig a BKep helyét mi mondjuk meg a memóriában. A deklarációjában lévő Absolute lefoglalt szó arra utal, hogy a memória rögzített helyén kell tárolni a változót. A helyet a kulcsszó után kell leírni. A leíráshoz hexadecimális számrendszert célszerű használni. A programban egyébként bárhol használhatunk 16-os számrendszerbeli számokat, csak egy dolgot kell tenni, a szám elé $ (dollár) jelet kell tenni. Az operatív tár (RAM) objekt helyét két szakaszban kell megadni, (mivel értéke $FFFF-nél nagyobb is lehet, és legfeljebb 64kB-ot lehet folytonos memóriaterületként kezelni) először a szegmens értéket, majd pedig az ofszet-et. Mindkét érték Word típusú. Szokás a szegmens-t nagyra választani, az ofszet így 0-15 értéket vehet fel. Programunkban a szegmens $b800, az ofszet pedig $0. Ez a színes képernyő objektív helye a memóriában. A video-vezérlő áramkörök innen olvassák ki a megjelenítendő karaktereket. A BKep változónk ezek után olyan, hogy értékadással a képernyőn való megjelenítést valósíthatunk meg. Ezek szerint a BKep[1,1,1]:= 65 értékadás a képernyő első helyére egy A betűt helyez. A programban a képernyő törlése (azaz 32-es karakterekkel való feltöltése a BKep tömbnek) után a kursorra nincs szükség, hiszen nincs írás a programban (de az ablak után újra el kell tüntetni). A kettős For ciklus A betűkkel teleírja a képernyőt, a ciklus lejátszódását a gép gyorsasága miatt nem látjuk, de ha lassítást írnánk a végrehajtásba, akkor láthatnánk. A KKep:= BKep értékadással azért élhetünk, mert a két változó típusa azonos. Ha mindkettő külön-külön tömbként lenne deklarálva, akkor csak For ciklussal lehetne átvinni az adatokat egyikből a másikba, ami sokkal lassúbb lenne. A KKep a BKep tartalmát a fordító által meghatározott helyen, a képernyő memóriától függetlenül tárolja. Várakozás után egy ablak kerül a képernyőre, újabb billentyű megnyomásra az ablak eltűnik és a BKep:= KKep értékadás visszahozza az A betűket. Ezzel a program teljesen megoldja a kitűzött feladatot miközben egy régi vágyunk is teljesült: tudtunk írni a képernyő utolsó helyére.

 

 

Lássunk még két kis rövid programot, amely a gép lelki világába enged egy kis betekintést. Az első folyamatosan másolja a nullás lapot a képernyőre, megfigyelhetjük, hogy mennyire változik a tartalma.

 

Program NullasPg;

Uses NewDelay, Crt, CrtPlus;

Type Scr=Array[1..25,1..80,1..2] Of Byte;

Var NLap: scr Absolute $0:0;

    BKep: scr Absolute $b800:0;

Begin

  TextMode(CO80);

  ClrScr;

  Repeat

    BKep:= NLap;

  Until KeyPressed;

End.

 

 

Mivel a Scr típust és a színes képernyőre vonatkozó BKep-et több programban is használhatjuk, deklaráljuk ezeket is a CrtPlus-ban, az Implementation kulcsszó előtt.

 

Unit CrtPlus;

InterFace

Uses NewDelay, Crt;

. . .

Type Scr= Array[1..25,1..80,1..2] Of byte;

Var BKep: Scr Absolute $B800:0;

    KKep: Scr;

Implementation

. . .

End.

 

A következő kis programot futtatva a gép teljesen leáll, csak Reset-tel vagy ki-be kapcsolással indítható újra, ugyanis a nullás lapot 0-val teleírjuk. A FillChar eljárás a megadott a tömböt, a teljes SizeOf(a) méretben 0–val tölti fel (Csak Win98-on működik, XP már nem enged ilyen értékadást).

 

Program Rezet;

Uses NewDelay, Crt, CrtPlus;

Var A: Array[0..1023] Of Byte Absolute $0:0;

Begin

  TextMode(CO80);

  ClrScr;

  WriteXY(34,12,'Most száll el a gép');

  Varj;

  FillChar(A,SizeOf(A),0);

End.

 

Futtatás előtt feltétlen mentsük ki, mert aztán már nem lehet!

 

Eddigi programjainkban gyakran kellett a billentyűzetről adatot bevinni. Ha nem voltunk eléggé elővigyázatosak, bizony előfordult, hogy a program leállt, vagy egyszerűen megváltozott az input helye a képernyőn, mert fölöslegesen nyomtunk Enter-t. Ezek bizony egy komolyabb programnál végzetes következményekkel járna, pl. nagy mennyiségű adatvesztéshez, ami csak hosszú fáradságos munkával lenne pótolható. A programjainkat a téves billentyűzéstől a lehető legjobban meg kell védeni. Mostanra tanultunk meg annyit a Pascal nyelvből, hogy egy komoly Input rutint írhatunk. Ennek helye természetesen a CrtPlus-ban van, hiszen bármikor használni szeretnénk.

 

28.) Bővítsük a CrtPlus Unit-ot egy olyan Bevitel függvénnyel, amellyel a billentyűzetről való beolvasás a lehető legbiztonságosabb lesz.

 

A végleges megoldásban szükség lesz arra, hogy a képernyő egy – az inputnak megfelelő – területén a képernyő színeket utólag megadhassuk. A feladat megkönnyítése, illetve más programokban való alkalmazás lehetősége végett, célszerű egy kis eljárást készíteni a CrtPlus-ba, mely tehát a képernyő valamely sorában, annak egy összefüggő részén, be tudja állítani a háttér és a betű színeit. Legyen ennek az eljárásnak a neve szinez. Helyezzük a szinek eljárás után. Paraméterei: a háttérszín, karakterszín, képernyőhely X és Y koordinátája, és a sor hossza.

 

Unit CrtPlus;

Interface

Uses NewDelay, Crt;

  . . .

  Procedure Szinek(HSz,KSz: Byte);

  Procedure Szinez(HSz,KSz,X,Y,Sh: Byte);

Implementation

Procedure Szinez(HSz,KSz,X,Y,Sh: Byte);

Var I: Byte;

Begin

  For I:= X To X+Sh-1 Do BKep[Y,I,2]:= 16*HSz+KSz;

End;

  . . .

End.

 

Eek után nézzük a Bevitel függvényt. Illesszük a függvényt utolsóként a CrtPlus-ba. A függvény öt paraméterrel hívható: az inputhely háttér és karakterszíne, az inputhely első karakterének két koordinátája, valamint az input hossza (HSz,KSz,X,Y,Sh). A hely megtervezéséről nekünk kell gondoskodni, pl. kifér-e a sorban, az Bevitel ugyanis hossza maximum egy sornyi lehet. A visszaadott érték String típusú. Ha számot szeretnénk beolvasni, akkor a szöveges változóból Val eljárással nekünk számmá kell konvertálni. A változók szerepe:

 

Ax: aktuális x koordináta,

Ch: a billentyűzetről olvasott karakter;

Ovw: ha True akkor felülírásos, ha False beszúrásos az írásmód;

I: ciklusváltozó;

BSzov: munka string.

 

Az egyes részek magyarázatát a lista tartalmazza. Aki nem kíván elmélyülni a megvalósításban, megteheti, hogy csak azt tanulja meg, hogyan kell használni, úgy is hasznos lehet a számára.

 

Unit CrtPlus;

Interface

Uses NewDelay, Crt;

  ...

  Function Bevitel(HSz,KSz,X,Y,Sh: Byte): String;

Implementation

...

Function Bevitel(HSz,KSz,X,Y,Sh:Byte): String;

Var Ch: Char;

    I, Ax: Byte;

    Ovw: Boolean;

    BSzov: String;

Begin

  Szinez(HSz,KSz,X,Y,Sh);

  Ax:= X;

  GoToXY(Ax,Y);

  Ovw:= False;

  Repeat

    Ch:= ReadKey;

    Case Ch Of

      #0:Begin {ha kettős kódú billentyűt nyomtak}

           Ch:= ReadKey;

           Case Ch Of

             #71: Ax:= X; {Home}

             #75: If Ax>X Then Dec(Ax);    {Balra}

             #77: If Ax<X+Sh Then Inc(Ax); {Jobbra}

             #79: Begin   {End}

                    I:= X+Sh;

                    Repeat

                      Dec(I);

                    Until (BKep[Y,I,1]<>32) Or (I<X);

                    Ax:= I+1;

                  End;

             #82: Ovw:= Not Ovw; {Ins: felülírás-beszúrás váltása}

             #83: If Ax<X+Sh Then {Delete}

                  Begin

                    For I:= Ax To X+Sh-1 Do

                    BKep[Y,I,1]:= BKep[Y,I+1,1];

                    BKep[Y,X+Sh-1,1]:= 32;

                  End;

           End;

         End;

      #8: If Ax<>X Then {BackSpace}

          Begin

            For I:= Ax-1 To X+Sh-1 Do

            BKep[Y,I,1]:= BKep[Y,I+1,1];

            BKep[Y,X+Sh-1,1]:= 32;

            Dec(Ax);

          End;

      #9: ;  {Tab}

      #13: ; {Enter}

      #27: ; {Esc}

      Else   {megjeleníthető karakter esetén}

      If Ax<X+Sh Then

      Begin

        If Not Ovw Then {beszúrás esetén helyet csinál}

        For I:= X+Sh-1 DownTo Ax+1 Do

        BKep[Y,I,1]:= BKep[Y,I-1,1];

        BKep[Y,Ax,1]:= Ord(Ch);

        Inc(Ax);

      End;

    End;

    GotoXY(Ax,Y);

  Until Ch In [#9,#13,#27]; {a bevitel Enter, Tab vagy Esc-re áll le}

  If Ch=#27 Then bevitel:= '' Else {ha Esc-re állt le, az bevitel üres String}

  Begin

    BSzov:= '';

    I:= X+Sh;

    Repeat {megkeresi az utolsó nem üres helyet}

      Dec(I);

    Until (BKep[Y,I,1]<>32) Or (I=X);

    Ax:= I;

    {a karakterek összerakása szöveggé a munka stringben}

    For I:= X To Ax Do BSzov:= BSzov+Chr(BKep[Y,I,1]);

    Bevitel:= BSzov;

  End;

End;

 

End.

 

A következő kis program a most készített bevitelt teszteli:

 

Program InputPro;

Uses NewDelay, Crt, CrtPlus;

Var Sz: String;

Begin

  TextMode(CO80);

  Szinek(1,3);

  ClrScr;

  Sz:= Bevitel(7,0, 20,10,15);

  WriteXY(20,20,Sz);

  Tunj;

  Varj;

End.