Menükezelés


A programok másik fontos eleme a megjelenítések mellett a vezérlés. Az, hogy a felhasználó hogyan tudja irányítani a program menetét, választani adott lehetőségek közül. Az egyik legkulturáltabb megoldás a menüvezérlés, amikor a képernyőn felsorolt lehetőségek közül úgy választhatunk, hogy például egy más háttérszínű sávval mozoghatunk az egyes lehetőségek között, és az enter vagy más billentyű lenyomásával lehet kiválasztani a megfelelő funkciót. Egy másik módszer, amikor a választás egy betű lenyomásával történik, ilyen például az i/n választási lehetőség. Természetesen az kevés, hogy kiírjuk a képernyőre a menüpontokat, és közöttük mozgunk, bár ez is feladat, de a választás után el kell tudnunk dönteni, hogy melyik lehetőséget választottuk és azt kell végrehajtani. Erre láthatunk egy példát a következő programban, ami egy kicsit már hosszabb az eddigieknél és a bonyolultsága is nagyobb egy kicsit.

Pelda10 Segment ;Szegmensdefinicio.
assume cs:Pelda10,ds:Pelda10 ;Cs, ds beallitasa.
Start: mov ax,Pelda10 ;Ds beallitasa a kod elejere.
mov ds,ax
mov ax,0b800h ;Videomemoria szegmenscimet
mov es,ax ;es regiszterbe tolti.
mov ax,3 ;Kepernyotorles.
int 10h
call Kiiro ;A menupontok kiiratasa.
.1_Pelda10: call Csik ;A mutato kirakasa.
.2_Pelda10: mov ax,100h ;Billentyuzet figyeles.
int 16h
jz .2_Pelda10
xor ax,ax
int 16h
cmp ah,1ch ;Enter figyelese
jz Enter ;Ugras ha az.
cmp ah,48h ;A felfele nyil figyelese.
jnz .3_Pelda10 ;Ha nem az akkor a kovetkezo
;vizsgalatra ugrik.
cmp byte ptr [MUTATO],1 ;Ha a mutato erteke meg nem 1
jz .2_Pelda10 ;akkor csokkenti a MUTATO
dec byte ptr [MUTATO] ;poziciojat egyel.
jmp .1_Pelda10 ;ujabb billentyu figyelese.
.3_Pelda10: cmp ah,50h ;A lefele nyil figyelese,
jnz .2_Pelda10 ;Ha nem az, vissza a
;billentyuzet figyelesre.
cmp byte ptr [MUTATO],5 ;Ha a MUTATO meg nem az
jz .2_Pelda10 ;otodik helyen all, noveli
inc byte ptr [MUTATO] ;az erteket.
jmp .1_Pelda10 ;Vissza az figyeles elejere.
Enter: mov cl,byte ptr [MUTATO] ;Cl ciklusvalltovoba a
;MUTATO erteket tolti.
mov si,offset CIMEK ;A CIMEK cimkenel tarolt
.1_Enter: mov bx,[si] ;offsetcimek kozul azt
add si,2 ;tolti a bx regiszterbe,
loop .1_Enter ;amelyiken a mutato allt.
jmp bx ;Ugrik a bx alltal mutatott
;programreszre.
.2_Enter: mov si,offset KERDES ;A KERDES alatti szoveg
call Kiiro2 ;kiiratasa.
.3_Enter: mov ax,100h ;Az igen/nem valasztasi
int 16h ;lehetosegek vizsgalata.
jz .3_Enter
xor ax,ax
int 16h
cmp ah,31h ;Ha nem, ugras a kilepesre.
jz .4_Enter
cmp ah,17h ;Ha nem az igen, akkor
;ujabb billentyu beolvasasa,
;mert a lenyomott gomb
jnz .3_Enter ;ervenytelen.
jmp Start ;Ha az i lett lenyomva,
;ugrik a program elejere.
.4_Enter: mov ax,4c00h ;Kilepes a DOD-hoz.
int 21h
Menu1: mov si,offset SZOVEG1 ;Az egyes valasztasok
call Kiiro2 ;eseten vegrehalytando
jmp .2_Enter ;programok.
Menu2: mov si,offset SZOVEG2
call Kiiro2
jmp .2_Enter
Menu3: mov si,offset SZOVEG3
call Kiiro2
jmp .2_Enter
Menu4: mov si,offset SZOVEG4
call Kiiro2
jmp .2_Enter
Menu5: mov si,offset SZOVEG5
call Kiiro2
jmp .2_Enter
Csik Proc
mov al,byte ptr [MUTATO2] ;A mutato regi erteket
mov bl,160 ;al, egy sor hosszat a bl
;regiszterbe tolti.
xor ah,ah
mul bl ;Es kiszamolja a mutato
add ax,1499 ;kezdocimet.
mov di,ax
mov cx,21
mov al,7 ;Fekete alapon feher szin.
.1_Csik: mov es:[di],al ;A szininformaciok beirasa
add di,2 ;a kepernyomemoriaba,
loop .1_Csik ;ezalltal torli az elozo
;csikot.
mov al,byte ptr [MUTATO] ;Ugyan az, mint az elobb,
mov byte ptr [MUTATO2],al ;de mostmar az uj pozicioval
mov bl,160 ;es a kiamalt hatter szinnel
xor ah,ah ;rajzolja a csikot.
mul bl
add ax,1499
mov di,ax
mov cx,21
mov al,47
.2_Csik: mov es:[di],al
add di,2
loop .2_Csik
ret
Csik Endp
Kiiro Proc
mov si,offset MENUK ;A mar ismert szoveg
mov ah,7 ;kiirato rutin azzal a
mov di,1660 ;kiegeszitessel, hogy a
;0 kodra az eltarolt di
.1_Kiiro: push di ;erteket kiolvassa, hozzaad
;160-at (igy sort emel),
.2_Kiiro: mov al,[si] ;majd ismet elmenti.
inc si ;A 255 kod jelenti a szoveg
cmp al,0 ;veget es a rutinbol valo
jz .3_Kiiro ;visszaterest.
cmp al,255
jz .4_Kiiro
mov es:[di],ax
add di,2
jmp .2_Kiiro
.3_Kiiro: pop di
add di,160
jmp .1_Kiiro
.4_Kiiro: pop di
ret
Kiiro Endp
Kiiro2 Proc
mov al,[si] ;Ez a kiiro csak az elso
xor ah,ah ;feleben kulonbozik az
shl ax,1 ;elozotol, ugyanis itt a
mov di,ax ;kiirando szovegy elso ket
mov al,[si+1] ;byte-ja a koordinataja,
mov bl,160 ;Amibol a program kiszamolja
mul bl ;az aktualis memoriacimet.
add di,ax ;Az eljaras a tovabbiakban
add si,2 ;azonos az elozovel, de itt
mov ah,12 ;nincs soremeles.
.1_Kiiro2: mov al,[si]
inc si
cmp al,0
jz .2_Kiiro2
mov es:[di],ax
add di,2
jmp .1_Kiiro2
.2_Kiiro2: ret
Kiiro2 Endp
MUTATO: db 1
MUTATO2: db 1
MENUK: db "Az elso menupont",0
db "A masodik menupont",0
db "A harmadik menupont",0
db "A negyedik menupont",0
db "Az otodik menupont",255
SZOVEG1: db 22,5,"Az elso menupont lett kivalasztva.",0
SZOVEG2: db 21,5,"A masodik menupont lett kivalasztva.",0
SZOVEG3: db 21,5,"A harmadik menupont lett kivalasztva.",0
SZOVEG4: db 21,5,"A negyedik menupont lett kivalasztva.",0
SZOVEG5: db 22,5,"A otodik menupont lett kivalasztva.",0
KERDES: db 28,20,"Akar ujra valasztani (i/n)",0
CIMEK: dw offset Menu1
dw offset Menu2
dw offset Menu3
dw offset Menu4
dw offset Menu5
Pelda10 Ends
End Start


Nos a program méretétől nem szabad megijedni, mert részegységeiben vizsgálva nagyon sok minden ismerősnek tűnhet. Egyébként ez valójában egy parányi program, a gyakorlatban ilyen rövid feladattal ritkán találkozunk.

Egy program működésének elemzésekor az egyik megközelítés az, amikor a programot részegységeiben vizsgáljuk és csak az egyik programrészből a másikba átvitt paramétereket kell számontartani a pontos működés megértéséhez. Ez a módszer itt is célravezető.

Vegyük akkor sorra ennek a programnak a részegységeit: a legelső, a képernyő beállítása a megfelelő üzemmódba, de ez már teljesen természetes. Ezután kiírjuk a képernyőre az egyes menüpontokat. Nézzük, hogyan is történik ez: Ugyebár egy call utasítással meghívjuk a Kiiro rutint, ami először is si indexregiszterbe tölti a kirakandó szöveg kezdőcímét, ah-ba a színt és di-be a képernyőcímet, ahonnan a kiírást el kell majd kezdeni. Itt ezt a program nem számolja, mivel ez egy fix érték ami nem változik, ezért a címet közvetlenül a regiszterbe töltjük. De mivel a legritkább esetben áll egy sorból egy menü, ezért a di értékét elmentjük a verembe, hogy később könnyebben tudjuk kiszámolni a következő sor kezdőcímét. Ha mindezzel megvagyunk, következhet a karakterek beolvasása a memóriából és képernyőre írása, hacsak nem vezérlőkód. A rutin a 0-t és a 255-öt használja vezérlésre. A nulla hatására előveszi a veremből a di elmentett értékét ami az éppen írt sor elejére mutat. Ha ehhez hozzáadunk 160-at, éppen egy sorral lejjebb jutunk, így az egyes menüpontok pontosan egymás alatt lesznek és ugyanabban az oszlopban fognak kezdődni. Természetesen a regiszter tartalmát ismét el kell tárolni egy újabb sor címének kiszámításához. Ha nem nullával találkozik, hanem 255-el, akkor a rutin befejezésére ugrik ami abból áll, hogy a di tartalmát kiolvassa a veremből, nehogy benne maradjon a már említett lefagyási lehetőségek miatt és egy ret utasítással visszatér oda, ahonnan elindítottuk.

A program további teendője már többször fog ismétlődni, így ide egy címke is került, amire később ugrani lehet. A legelső lépés itt, hogy a választást jelző csíkot kitesszük valamelyik menüpontra (ez alapesetben a legelső, de ezen lehet változtatni). Ezt a Csik nevű szubrutin végzi el, ami két majdnem egyforma részből áll. Az elsőben eltünteti a képernyőn lévő csíkot, majd kirakja az újat. Ez a következőképpen történik: a MUTATO2 értékét ha megszorozzuk a sor hosszával (160) és hozzáadjuk az első menüsor címét (160-at), akkor pontosan annak a menünek a helyét kapjuk, amelyiken éppen áll a mutatónk. Azért kell 160-al kevesebbet hozzáadni, mert a mutató kezdőértéke 1 és ha ezt megszorozzuk a sorhosszal, akkor a 160-at kapunk. A kezdőérték egyről való indításának oka, hogy a későbbiek során amikor ezt az értéket egy ciklusban használjuk, akkor ha cx=0 értékkel indul, akkor nem egyszer fog lefutni a ciklus, hanem 65536-szor (ez a loop működéséből adódik). Ha kiszámoltuk a csík kezdőcímét, elkezdhetjük a csík kirakását. Ezt a csík hosszának és színének beállításával kezdjük. Itt a szín az eredeti, fekete hátterű a törlés céljából. Ezután a már kiszámolt memóriacímre, ami most nem egy karakterhely, hanem egy színhely mert most a szöveget nem kívánjuk megváltoztatni, csak a színét, kitesszük a mutatót jelképező csíkot. Tehát egy ciklussal minden második helyre beírjuk a megadott színbyte-ot. Ha ezzel megvagyunk, megismételjük a műveletet, de most már az új pozícióval és színnel. Majd a mutató helyét a MUTATO2 változóba írjuk, hogy a következő elmozdításnál le tudjuk törölni a jelenlegit. Ezután természetesen egy ret segítségével visszatér a rutinból. A most következő BIOS rutin ellenőrzi, hogy van-e lenyomott billentyű. Ha van, akkor kiolvassa azt és megvizsgálja, hogy a számunkra van-e jelentése. Ha nincs, akkor vissza a figyeléshez. Ha a felfelé nyílat nyomtuk meg, akkor ellenőrizni kell, hogy a mutató nem áll-e a legfelső soron, mert innen följebb már nem léphetünk. Ha nem ott állt, akkor a mutató értékét csökkentjük eggyel. Hasonlóan a lefelé nyílnál is megvizsgáljuk a csík helyét és ha lehet, növeljük a mutató értékét. Amennyiben a menüpont kiválasztására használt enter-t nyomtuk le, akkor kilép a körből és az Enter címkénél folytatja a program futását. Itt a cl regiszterbe tölti a MUTATO értékét, si-be pedig azt a címet, ahonnan kezdve le lettek tárolva az esetlegesen indítható programok címei. Ez úgy történt, hogy a CIMEK címke után wordös formában az egyes programok offsetcímei kerültek letárolásra. Innen egy mov utasítással a bx regiszterbe töltjük először az első címet, növeljük si értékét kettővel, majd a loop utasítás a cx-nek megfelelően megismétli ezt a műveletet. Ha cx-ben 1 volt, akkor a ciklus nem kerül ismétlésre és a bx regiszterben marad az első cím. Ha nem egy, akkor a cx-től függően a megfelelő értéket kerül a bx-be. Ezzel megkerestük a kiválasztott menüpont által végrehajtandó program címét. A feladat már csak annyi, hogy végrehajtsuk. Ezt egy jmp bx utasítással tehetjük meg. Ezek a rutinok most csak annyit csinálnak, hogy si-be beállítják a kiíratandó szöveg kezdőcímét (természetesen a menüponttól függően) és egy másik kiíró rutin segítségével kiírják azt, majd visszaugranak a .2_Enter címkéhez. Az itt használt szövegkirakó eljárás csak annyiban különbözik az előzőtől, hogy a szöveg elején található 2 byte, ami a kirakandó szöveg koordinátáját tartalmazza. Ebből a program a már megszokott módon kiszámolja a képernyőcímet. A szöveg végét a 0 kód jelzi. Visszatérés után egy kérdés kerül kirakásra, mégpedig, hogy akarunk-e újból választani. Itt figyeli, ha az n gombot nyomtuk le, ugrik a kilépésre, ha az i gombot akkor elölről kezdi a programot, egyébként újabb billentyű várása következik. A programban nem a karakterek ascii kódja lett figyelve, hanem a scan kód mivel előfordulhat, hogy be van nyomva a Caps Lock és ekkor az n billentyűt hiába figyeljük mivel leütésekor N kerülne beolvasásra. Ellenben a scan kód a billentyűzet gombjait jelenti, nem pedig a rajtuk lévő betűt.

Egy szintet vissza, vagy vissza a főmenübe.