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.