Dr. Vermes Mátyás1
2002. október
A ,,Villámrajt" cím arra utal, hogy belevágtunk a téma közepébe. Sajnos a Jáva programok közel sem indulnak ilyen gyorsan. Ráadásul a fenti linkek bármelyike két Jáva programot is elindít: először a Java Webstart jön fel, ami azután letölti és elindítja a Jáva Terminált. Úgyhogy türelem. A későbbiekben a terminálos alkalmazás böngésző nélkül is használható a javaws program ablakából.
A Jáva Terminál egy alkalmazásfüggetlen megjelenítő program, ami más programok részére biztosít GUI-t. A terminál alapvető feladata dialogboxok megjelenítése. A Jáva Terminál dialogboxai Swing elemekből építkeznek:
A Jáva terminál és a CCC alkalmazás XML üzenetekkel kommunikál. A kommunikáció kezdetekor az alkalmazás elküldi a terminálnak a dialogbox XML leírását. Ez tartalmazza a dialogboxban megjelenítendő komponensek paraméterezését, a bennük levő adatok kezdőértékét. A terminál megjeleníti és működteti a dialogboxot.
A felhasználó tevékenysége közben a terminált magára hagyjuk. A felhasználó azt ír a szövegmezőkbe, amit akar (és amit a szerkesztő sablonok megengednek), kedvére választhat a listboxokban, klikkelhet a check boxokban, választhat a browse-ban, stb.
Üzenetváltás csak akkor történik, amikor a felhasználó valamilyen akciót kezdeményez: választ egy menüpontot, vagy megnyom egy pushbuttont. Ilyenkor a terminál elküldi a szervernek az akciót azonosító adatokat és a dialogbox teljes aktuális adattartalmát. Az akció hatására a szerver végrehajtja az üzleti logikát, és válaszként további adatokat és utasításokat küld a terminálnak, például:
Bár a Jáva Terminált eddig csak CCC szerverek megjelenítő moduljaként használtuk, a terminál más nyelvekből is igénybe vehető. A legtöbb ma használt programnyelvben, pl. C++-ban, Pythonban, Jávában (!) könnyen megvalósítható a szerver oldali API. Az API implementálhatóságának demonstrálása céljából készült a Python interfész. A felsorolásban a Jáva említése nem elírás, van értelme Jáva programok Jáva Terminálban történő megjelenítésének, ez ugyanis lehetővé teszi, hogy a szerver és a terminál egymástól földrajzilag távoli gépeken fusson.
Az API egyszerűsége abból adódik, hogy lényegében nincs belső működése. Az alkalmazás egyszerűen megüzeni a terminálnak, hogy mit akar, és az utasításokat a terminál végrehajtja. Az üzenetek XML-ben (azaz szövegesen) jönnek-mennek, mindig el lehet olvasni őket, semmi sem marad titokban az alkalmazásfejlesztő előtt, az esetleges hibákat ezért hamar meg lehet találni. Persze nem mindenki számára tesszük lehetővé az üzenetek olvasását. A Jáva Terminál és a CCC szerverek képesek SSL kommunikációra, így a használat nyilvános hálózaton is biztonságos.
Alapvetően kétféle GUI-t tudunk csinálni. A fix pozícionálású ablak a régi Clipper readmodallal működtetett dialogbox utóda, a lehetőségek azonban tágabbak. Szövegmezőkön (get) kívül az új jtdialog ablak tartalmazhat browset, list boxot, buttonokat, progress bart stb. A 1. ábrán látható GUI-t a jólbevált mask programmal rajzoltam (pdialog.msk), ebből az msk2dlg programmal generáltam kódot (pdialog.dlg), amit végül a main.prg demonstrációs programban használtam fel.2
Ezzel a típussal készül a menüző browse utóda. A menüben sok új lehetőség van, rakhatunk bele ikonokat, check boxokat, rádió buttonokat. A browse fölé/alá tehetünk toolbart, amiben lehet push, check, rádió button, list box, get, progressbar, label (bármi). A Swingben a komponensek (pl. egy label) szövege HTML formátumú is lehet, amivel különböző tördelést, fontokat, színezést lehet megvalósítani.
Az alsó státusz sor szintén egy toolbar, ebben van egy getmező, amibe számlaszám szerint kereső stringet lehet beírni. A toolbárok és a JTable függőleges sorrendjét az ablakhoz való hozzáadásuk sorrendje határozza meg. A toolbárok egérrel áthelyezhetők.
Megjegyzem, hogy a terminál támogatja (függőleges helyett) a vízszintes pakkolást, és más komponenseket is lehetne egymás alá (mellé) helyezni. A toolbárok által tartalmazott komponensek például vízszintesen vannak egymás mellé rakva. Az ablak CCC-beli leprogramozása nem igényel GUI tervező eszközt, nincs szükség méretek megadására. A fenti GUI-t a main1.prg program hozta létre.
A jtlisten program feladata szerver programok indítása.
jtlisten.exe [if:]port <command>A program figyel az opcionálisan megadott interfészen és a megadott porton. Ha a portra kapcsolódnak, akkor elindítja <command>-ot úgy, hogy kiegészíti azt a -jtsocket <sck> opcióval, ahol <sck> az accept-ben létrejött socket. A Jáva terminálos programok szokás szerint a -jtsocket <sck> opcióban kapják meg azt az örökölt socketet, amin a terminállal kommunikálni lehet. A <command>-nak spawnvp-vel (UNIX-on fork plusz execvp-vel) indítható filéspecifikációval kell kezdődnie.
A CCC szerver programok semmit sem tudnak az SSL-ről. A plain socketen kommunikáló szerver és az SSL-en kommunikáló terminál között egy ,,fordító" réteg van, hasonlóan az ssh port forwardinghoz. Tegyük fel, hogy <command> olyan programindító parancs, amit jtlisten el tud indítani az előző pontban tárgyalt módon. Akkor a
jtlisten.exe [if:]port sslforward.exe <command>parancs ugyanúgy elindítja <command>-ot, de még egy SSL fordító réteget is közbeiktat.
Az xstart a CCC 2.x rendszer tools könyvtárában található segédprogram, amit a UNIX-on ismert inetd-hez hasonló módon lehet használni. Az xstart-ot egy XML szintaktikájú szövegfilével konfiguráljuk.
<xstart> <item> <name>Program SSL nélkül</name> <host>localhost</host> <port>46000</port> <workdir></workdir> <command>program.exe -jtsocket $(SOCKET)</command> </item> <item> <name>Program SSL-lel</name> <host>localhost</host> <port>46001</port> <workdir></workdir> <command>sslforward.exe program.exe -jtsocket $(SOCKET)</command> </item> </xstart>
A konfigurációs filében host:port címekhez rendelünk szolgáltatásokat. A program figyel a megadott portokon, és ha valamelyik portra kapcsolódnak, akkor átvált a <workdir> tagban megadott directoryba, és ott elindítja a <command> tagban megadott szolgáltatást. A <command>-nak most is spawnvp-vel vagy execvp-vel indítható filéspecifikációval kell kezdődnie. A parancshoz xstart nem fűzi hozzá a -jtsocket opciót, viszont helyettesíti a $(SOCKET) makrót az accept-ben kapott sockettel. Mint a példában látszik az sslforward program most is használható SSL fordító réteg közbeiktatására.
java -jar jterminal.jar <host> <port> [ssl]
A terminál kapcsolódni fog a <host>-ban megadott gép <port>-jára. Ha az opcionális ssl paraméter is meg van adva, akkor a terminál SSL-en fog kommunikálni. Az ssl paramétert a szerverrel összhangban kell használni, azaz akkor és csak akkor kapcsoljuk be a terminálban az SSL-t, ha a szerver is SSL-en kommunikál.
Megírjuk a kívánt alkalmazást (CCC-ben) a jtlib könyvtárban levő megjelenítő interfészre. A CCC alkalmazásnak nincs szüksége web szerverre, nem töltődik le a webes kliens gépére, hanem egyszerű programként fut a szolgáltatónál akár a webszerveren, akár a szolgáltató egy erre kijelölt másik gépén. Az alkalmazások a szokásos TCP (SSL) protokollt használják: minden alkalmazáshoz elindítunk egy listenert, ami egy megadott porton figyeli a kliensek konnektálását, és minden új kliensnek elindítja az alkalmazás egy példányát. Ahány alkalmazást akarunk közreadni, annyi listenert indítunk, amik az alkalmazásokhoz rendelt (különböző) portokon hallgatóznak.
A webes kliensnél az alkalmazásokat a Jáva Terminál program fogja megjeleníteni. Ez egy alkalmazásfüggetlen Jáva program (nem applet), ami teljes jogkörrel (nem sandboxban) fut a kliens gépén. A szolgáltató által közreadott alkalmazások futtatásához tehát a kliensnek szüksége van
A jterminal a Jáva régebbi (1.3.x) változatával is működik, ekkor azonban külön kell gondoskodni az XML csomag, az SSL kiegészítés és a Jáva Web Start csomag letöltéséről és installálásáról. Az újabb Jávák előnye, hogy ezeket az alkatrészeket már alapértelmezésben tartalmazzák.
Olyasmivel itt nem foglalkozom, mint a Jáva környezet automatikus installálása. A jterminal.jar program letöltését megkönnyíti a Jáva Web Start technológia, amiről írok pár sort a következő pontban.
A webszerveren elhelyezzük a jt.jnlp filét az alábbihoz hasonló tartalommal:
<?xml version="1.0" encoding="utf-8"?> <jnlp spec="1.0+" codebase="http://1g.comfirm.ve/jterminal/" href="jt.jnlp"> <information> <title>Jáva Terminál Demó</title> <vendor>ComFirm Bt</vendor> <homepage href="html/jterminal.html"/> <description>CCC Download</description> <offline-allowed/> </information> <security> <all-permissions/> </security> <resources> <j2se version="1.4.0+"/> <jar href="jterminal.jar"/> </resources> <application-desc main-class="jterminal"> <argument>1g.comfirm.ve</argument> <argument>46000</argument> <argument>ssl</argument> </application-desc> </jnlp>
Szintén feltesszük a webszerverre a paraméterfilé által hivatkozott objektumokat: jar és html filéket, azok további alkatrészeit, stb. Ha most a kliens a windowsos gépén kiadja az alábbi parancsot:
path_to_javaws\javaws "http://1g.comfirm.ve/jterminal/jt.jnlp"
akkor a javaws program letölti, a paraméterfilében talált Jáva alkalmazást, jelen esetben jterminal.jar-t, és elindítja azt a megadott paraméterrel, esetünkben az ip, port, ssl értékekkel, amire viszont a szerveren automatikusan elindul a 46000-es porthoz rendelt CCC alkalmazás.
A javaws program a letöltött Jáva alkalmazásokat tárolja, képes azok futtatására offline módban is (a Jáva Terminál esetében persze ennek nincs értelme), illetve ha megvan a hálózati kapcsolat, akkor automatikusan frissíti az alkalmazásokat. A kliens továbbiakban a javaws ablakában keletkező ikonokkal indíthatja az egyszer már letöltött és tárolt Jáva alkalmazásokat.
Ha a szolgáltató Jáva Terminálon át elérhető CCC alkalmazásokat ad közre, akkor minden alkalmazáshoz önálló jnlp filét kell csinálni. Ezek mind a jterminal.jar-t tartalmazzák letöltendő Jáva programként, különbözni fognak viszont az ip:port paraméterben, illetve az alkalmazás leírásában.
A fent leírt módszernél automatikusabb installálásra is van mód:
Az utóbbi eljárás lehetővé teszi, hogy a Jáva alkalmazás letöltését a böngészőből indítsuk egy jnlp-re mutató linkre kattintva.
application/x-java-jnlp-file jnlp
Description | : Java Web Start |
MIME type | : application/x-java-jnlp-file |
Suffixes | : jnlp |
Application | : .../javaws %s |
Tapasztalatom szerint Linuxon a Jáva Web Start installálásakor ez magától megtörténik. Ha viszont később máshova rakjuk a javaws programot, akkor itt utánállításra van szükség.
A Settings/Configure Konqueror/File Associations menüben beviszünk egy új típust:
Group | : application |
Type name | : x-java-jnlp-file |
Az új típus adatait a következőképpen adjuk meg:
File Patterns | : *.jnlp |
Description | : Java Web Start |
Application | : megkeressük a javaws-t |
Egyszer talán itt egy komplett referencia áll majd, most azonban meg kell elégednem az üzenetek egyszerű felsorolásával, ez legalább felhívja a figyelmet bizonyos funkciók létezésére. Emellett megpróbálok rávilágítani a legfontosabb összefüggésekre, megkönnyítve ezzel a kódban való tájékozódást.
A szerver (CCC) és a terminál (Jáva) között SSL kapcsolat van, és a felek XML formátumú üzenetekkel kommunikálnak egymással.
Megjegyzés. Az 1.4.1-es Jáva helyből tartalmazza a szükséges XML elemző csomagot és az SSL kiegészítést, az 1.3.x-es Jávához ezeket külön kell telepíteni. Régebbi Jávával nem kísérleteztem.
A szerver először egy <jtdialog> üzenetet küld a terminálnak, ami egy dialogbox teljes leírását tartalmazza. A dialogboxot a terminál megjeleníti és működteti.
A terminál csak akkor üzen a szervernek, ha a felhasználó valamilyen akciót vált ki. Akció akkor keletkezik, ha kiválasztanak egy menüt, megnyomnak egy push buttont, entert ütnek egy get mezőben, stb. (Szabály: a menük és push buttonok mindig jelentik az akciót, a többi kontroll csak akkor, ha azt a programban előírtuk.) Az akciót a terminál egy <action> üzenettel jelenti. Az üzenet tartalmazza az akciót kiváltó kontroll nevét (azonosítóját), és a dialogboxban levő összes változtatható értékű kontroll aktuális értékét. Az <action> üzenet elküldése után a dialogbox setEnabled(false) állapotba helyezi magát, és vár a szerver válaszára (a reakcióra).
A szerver eközben egy messageloop-ban várakozik a terminál üzeneteire. Amikor megérkezik az akció, azt a _dlgbase_getmessage függvény feldolgozza, és az akcióban jelentett új tartalmakat betölti a dialogboxot reprezentáló szerver oldali objektumba.
Az így frissített (szerver oldali) dialog objektummal a szerver azt csinál, amit akar, pl. számításokat végez a kontrollok tartalmával, vagy éppen új értéket ad egyes kontrolloknak, ez az üzleti logika.
Miután az üzleti logika elvégezte a dolgát, de még mielőtt a messageloop a következő körre fordulna, _dlgbase_getmessage függvény egy <reaction> üzenetet küld a terminálnak. A reaction üzenetben egyúttal elküldjük a (terminál oldali) dialogboxnak a megváltozott kontrollok új értékét.
A terminál a reaction üzenetben kapott új tartalmakat betölti a kontrollokba, majd setEnabled(true) állapotba helyezi magát.
Összefoglalva:
A fentieken kívül vannak más üzenetek is, de az egész rendszer vázát a fenti üzenetmechanizmus képezi, aminek megértése elengedhetetlen rendszer használatához.
public void action(xmlcomponent source) { if( !actionenabled ) { return; } String x="<action dialogid=\""+dialogid+"\">"; x+="<source>"+source.getName()+"</source>"; for( int i=0; i<varlist.size(); i++ ) { xmlcomponent c=(xmlcomponent)varlist.get(i); try { x+=c.xmlget(); } catch( pfexception e ) { if( !source.isEscape() ) { //szólni kell a kontrolloknak: //amit küldeni akartak, nem ment el, //erre való az xmlreset() for( int k=0; k<i; k++ ) { ((xmlcomponent)varlist.get(k)).xmlreset(); } jtalert a=new jtalert(jterm); a.message="Hibás adatbevitel: "+c.getName(); a.parent=this.wnd; a.send=false; //a.beep=false; a.type=JOptionPane.ERROR_MESSAGE; a.run(); e.getField().requestFocus(); return; } } } x+="</action>"; actionenabled=false; jterm.send(x); }
Az action üzenetet a jtdialog objektum készíti (és küldi) miután értesült róla, hogy valamelyik kontrollban jelenteni való akció történt. Mint a kódban látjuk, csak enabled állapotban küldünk akciót. Mivel az akció eredményére várva a dialogbox disabled állapotban van, ez egyúttal azt jelenti, hogy az akciók nem skatulyázódnak egymásba. A <source> tag tartalmazza az akciót kiváltó kontroll azonosítóját. A jtdialog objektum körbekérdezi a (változtatható értékű) kontrollokat, hogy adják meg az aktuális állapotukat (xmlget). Egyes kontrollok erre kivétel dobásával reagálhatnak, ha a kitöltésük nem megfelelő. Ezt a kivételt nem vesszük figyelembe, ha az akció forrásának escape attribútuma true. Ennek az az értelme, hogy egy ,,Kilép" gombbal ki lehessen lépni akkor is, ha vannak érvénytelen állapotú kontrollok.
A szerver oldali jtdialog objektum xmlout metódusa készíti, és a show metódus küldi a <jtdialog> üzenetet. Ebben a szerver a dialogbox teljes leírását adja át a terminálnak.
static function _jtdialog_xmlout(this) local x,n,c,g:={},grp,i x:="<jtdialog" x+=ATTRI("top",this:top) x+=ATTRI("left",this:left) x+=ATTRI("bottom",this:bottom) x+=ATTRI("right",this:right) x+=ATTR("name",this:name) x+=ATTR("dialogid",this:dialogid) x+=ATTR("pid",this:pid) x+=">"+EOL x+="<caption>"+cdataif(this:text)+"</caption>"+EOL if( this:layout!=NIL ) x+="<layout>"+this:layout+"</layout>"+EOL end for n:=1 to len(this:itemlist) c:=this:itemlist[n] if( c:classname=="jtradio" .and. c:group!=NIL ) grp:=eval(c:group) for i:=1 to len(g) if( grp==g[i] ) exit end next if( i>len(g) ) aadd(g,grp) x+="<jtradiogroup>"+EOL for i:=1 to len(grp) x+=grp[i]:xmlout+EOL next x+="</jtradiogroup>"+EOL end else x+=c:xmlout+EOL end next x+="</jtdialog>" return x
A metódus bejárja azt a fát, amibe a menük, kontrollok szerveződnek. Minden komponensnek meghívódik az xmlout metódusa, ezzel minden komponens hozzáadja a saját járulékát a dialogboxot leíró XML dokumentumhoz.
A szerver a reaction üzenettel jelzi a terminálnak, hogy annak újból enabled állapotba kell helyeznie magát, egyúttal elküldi a megváltozott kontrollok új állapotát.
static function _jtdialog_response(this) local n,v,x:="" for n:=1 to len(this:varlist) v:=this:varlist[n] if( v:changed ) x+="<"+v:name+">"+v:xmlget+"</"+v:name+">" v:savestate end next if( empty(x) ) x:='<reaction dialogid="'+this:dialogid+'"/>' else x:='<reaction dialogid="'+this:dialogid+'"><set>'+x+'</set></reaction>' end this:send(x) this:mustreact:=.f. return NIL
Mint látjuk, a szerver oldali dialogbox objektum körbekérdezi a megváltozott állapotú kontrollokat (xmlget), amire azok megadják az új értéküket.
static function _jtelem_changeenabled(this,v) local x if( v!=NIL ) this:enabled:=v end x:='<jtmessage' x+=ATTR("pid",alltrim(str(getpid()))) x+=ATTR("dialogid",this:dialogid) x+='>' x+="<control>"+this:name+"</control>" x+="<enabled>" x+=if(this:enabled,"true","false") x+="</enabled>" x+="</jtmessage>" this:send(x) return NIL
A példa érzékelteti, hogy mire számíthatunk a jtmessage üzenetekben. Ez itt egy komponenes enabled attribútumát állítja át a dialogbox működése közben.
Sajnos megint csak fő összefüggések leírására kell szorítkoznom, ez legalább megkönnyíti a kód olvasását.
Az akció és reakció üzenetekben a kontrollok ,,értéke" utazik a termináltól a szerverhez és vissza. Alább felsoroljuk, hogy az egyes kontrolloknál mit értünk érték alatt:
Az értéket (a régi Clipper get objektumának mintájára) a varget metódussal lehet lekérdezni, és a varput metódussal lehet beállítani. A varget nem tévesztendő össze a kontroll szövegével (text), vagy az érték XML formátumú alakjával (xmlget), bár egyes esetekben azok megegyezhetnek. A felsorolásban nem szereplő kontrollok esetén a varget metódus azonos a text metódussal, ahogy az a jtelem osztályból öröklődik.
A kontrolloknak az értékükön kívül természetesen van egy sor egyéb jellemzője, ilyen pl. egy checkbox felirata, ikonja, tooltipje. Ezeknek az állítgatása lehetséges ugyan, de csak ritkán fordul elő a programban, tipikusan csak egyszer, a kontroll létrehozásakor.
A szerver oldalon az osztályok többsége a jtelem absztrakt osztályból származik, legelőször tehát ezt kell tanulmányozni.
Belső használatra
Alkalmazási programoknak
A felsorolás képet ad a Jáva Terminál lehetőségeiről. Az osztályok valójában a terminálon létrejövő Swing objektumok szerver oldali reprezentációi, és többségük az előbb tárgyalt jtelem osztály leszármazottja.
Lesznek később más kontrollok is, pl. famegjelenítésre. Jelenleg nincs még megoldva nagy szövegállományok terminál oldali megjelenítése, ahol a ,,nagy" akkorát jelent, amit már nem lehet egyben átküldeni a hálózaton.
A CCC szerverprogramokban a jtlib könyvtár osztályait használjuk a megjelenítéshez. A könyvtár objektumai a szerver oldalon reprezentálják a terminál által működtetett dialogboxot, egyúttal támogatják a két fél közti adatcserét. A teljes könyvtár szisztematikus leírására nincs időm, ezért csak néhány kiragadott kérdést tárgyalok.
Vizsgáljuk meg részletesen a main1.prg-ben található message loop-ot:
function msgloop(dlg) local msg dlg:show while( NIL!=(msg:=dlg:getmessage) ) //az alábbi funkciókat lehetne //a kontrollok blockjába is rakni if( msg=="x" ) //x button quit elseif( msg=="ok" ) //ok button dlg:close elseif( msg=="menuitem1" ) ? "alert:", alert("Van, aki forrón szereti!",; {"Válasz-1","Válasz-22","Válasz-333"}) elseif( msg=="menuitem3" ) msgloop( makedlg(dlg:caption+"@") ) elseif( msg=="search" ) //név szerint kikeresi a kontrollt, //kiolvassa a tartalmát, azzal végrehajtja a seek-et, //és a kapott pozíció alatti lapot megjeleníti //tabSeek(table,dlg:getcontrol(msg):varget) //dlg:getcontrol("szamla"):pagecurrent tabSeek(table,dlg:var:search:varget) dlg:var:szamla:pagecurrent end end return NIL
Először a show metódus meghívásával megjelenítjük a dialogboxot, mint tudjuk, ez küld egy <jtdialog> üzenetet a terminálnak. Ezt követően várunk a terminál <action> üzenetére a dlg:getmessage hívásban.
Ha a getmessage metódus NIL-t ad, az azt jelenti, hogy a dialogbox ablakát becsukták, ez esetben a ciklus befejeződik. Ha getmessage valódi akciót jelent, akkor annak a kontrollnak az azonosítójával tér vissza, ami az akciót indította.
Ha az ,,x" (Kilép) gombot nyomták meg, akkor kilépünk a programból. Lehetne itt vacakolni egy előzőleg elküldött <jtexit> üzenetettel, de ez nem igazán fontos. A terminál magától is észre fogja venni, hogy a socket kapcsolat lezáródott, és meg fogja tenni a szükséges lépéseket. Ezért az egyszerű quit is megfelelő.
Az ,,ok" gomb megnyomására nem az egész program lép ki, csak bezárjuk a terminál legfelső ablakát. Persze, ha éppen csak egy ablakunk volt, akkor mégiscsak befejeződik a program.
Ha kiválasztják a ,,menuitem1"-et, elindítunk egy alertet, ha a ,,menuitem3"-at, akkor pedig létrehozunk egy új dialogbox ablakot a korábbihoz hasonló tartalommal.
Az utolsó ág a legtanulságosabb. A ,,search" annak a getmezőnek a neve, ami az alsó toolbárban van, és célja, hogy számlaszám szerint kereső mintát lehessen benne megadni. Általánosságban, ha egy getben entert ütnek, akkor Jáva értelemben vett akció keletkezik. Mivel a mi ,,search" getmezőnk valid attribútuma true-ra van állítva, az akciót a terminál jelenti, ezért kapjuk meg a messaget. A getmessage metódus visszatérése után a getbe gépelt kereső minta már beíródott a getmezőt a szerver oldalon reprezenátáló jtget objektumba, ahonnan a varget metódussal lehet azt megkapni. Van azonban egy probléma: hogyan szerezzük meg magát a jtget objektumot? Ez a probléma a moduláris programozás eredményeképpen lép fel, ui. az a kód, amiben a jtget objektumot létrehoztuk, nagy valószínűséggel nem a messageloop-ot tartalmazó forrásmodulban van, vagy csak egy másik függvényben, mindenesetre az a tipikus, hogy nem látszik azon a helyen, ahol hivatkoznunk kellene rá. Három lehetséges megoldás van:
Miután így sikerült megkapnunk a kereső mintát, végrehajtunk vele egy seek-et a browse-olt adatbázisban. Ezután a browse-t, melynek neve ,,szamla", utasítjuk az aktuális lap frissítésére.
Remélem, idáig jutva az olvasóban már felvetődött a kérdés: mi van a többi akciót kiváltó kontrollal, amit egyáltalán nem látunk a messageloop-ban? A messageloop-on kívül más lehetőség is van az akciók kezelésére. Ha egy kontrollnak actionblock-ot adunk, akkor azt a getmessage automatikusan végrehajtja. Azt is megtehetjük, hogy kizárólagosan ez utóbbi módszert alkalmazzuk, ez esetben a messageloop belseje üres lesz (magára a ciklusra persze akkor is szükség van).
A Jáva terminálnak minden adatot szövegesen küldünk, ezért a terminál csak akkor tudja megállapítani egy adat típusát, ha azt explicite megmondjuk neki. A getek esetében a picture-ben kell kódolni a getben editálandó adat típusát. Ha a picture function stringjében szerepel az N, D, L betűk valamelyike, akkor a get szám, dátum, logikai típusú adatot fog editálni. Ha az előbbi betűk egyike sem szerepel a function stringben, akkor az adat karakteres.
A get mindig akkora, amekkorára méretezzük. Ha a méreténél hosszabb adatot editálunk benne, akkor az külön intézkedés nélkül vízszintesen scrollozódik. A getben editálható adat mindig olyan hosszú, mint amilyen a picture template stringjének hossza. Ha a template string rövidebb a getnél, akkor a get végére nem lehet rámenni a kurzorral. Ha a template string hosszabb, akkor a get scrolloz.
Ha egy karakteres getnek nincs template stringje, akkor abba akármilyen hosszú adatot be lehet gépelni, és a get scrolloz. A többi típus mindig kap egy default template stringet:
Még egy bővítmény a régi Clipperhez képest: Ha a picture function stringjébe beírjuk az X karaktert, akkor az akció küldése előtt a terminál ellenőrzi a get kitöltését, és saját hatáskörben hibát jelez, ha az nem megfelelő (hogy ne keringjenek triviális hibaüzenetek a hálózaton). Egyes gombokat kilépésre (escape) akarunk használni, kellemetlen volna, ha ezeknél a terminál nem hajtaná végre az akciót, hanem az X flaggel ellátott getek rossz kitöltésére figyelmeztetne. Ezért, ha egy kontroll escape attribútuma .t., akkor az a getek ellenőrzése nélkül jelenti az akciót.
Végül, amire programozás során feltétlenül emlékezni kell: A geteket mindig szereljük fel picture-rel, annak a function stringjében mindig jelöljük meg az adat típusát. Olyan zagyva dolgok, mint a régi Clipperben, ahol a get automatikusan alkalmazkodik az adat típusához, itt nincsenek.
A jtbrowse osztály egyik őse a (Clipper) TBrowse. Ez azt jelenti, hogy egy jtbrowse objektumhoz ugyanúgy lehet oszlopokat adni a brwColumn() függvénnyel, mint ahogy azt korábban csináltuk. Ugyanezért a jtbrowse objektumot a brwShow() és brwLoop() függvényekkel lokálisan is meg lehet jeleníteni.
A másik módja a jtbrowse objektum létrehozásának, hogy a konstruktorának átadunk egy már felszerelt TBrowse objektumot, amit a jtbrwose objektum magába épít.
Megjegyzem, hogy időközben elkészült a jtbrowsenak egy olyan változata, ami nem épít a régi TBrowse osztályra, ez a jttable. A Jáva terminálos programnak ui. nincs szüksége a TBrowse-ban levő rengeteg kódra, hiszen nem kell ténylegesen megjelenítenie a browset, hanem csak a megjelenítéshez szükséges adatok tárolása, és a terminálhoz való eljuttatása a feladat.Az új jttable osztálynak van két közvetlen leszármazottja a jtbrowsearray és jtbrowsetable, amik fel vannak készítve array-k, illetve táblaobjektumok browseolására (a felkészítés azt jelenti, hogy megfelelő kódblokkokkal vannak ellátva). Ezeket egyszerűbben és tisztábban lehet használni, mint az itt tárgyalt, először elkészült jtbrowset.
A továbbiakban elmondottak (a TBrowse örökség kivételével) mindegyik browse fajtára érvényesek. Azt is érdemes megjegyezni, hogy a terminál nem tesz különbséget a különféle szerver oldali browse fajták között, mert azok üzenetszinten egyformák, így mindegyiket ugyanaz a jttable Jáva komponenes jeleníti meg.
A jtbrowse osztályban van két új kódblock, ami a browseolás lapozós technikája miatt vált szükségessé:
A dolgok közti összefüggések szemléltetése kedvéért nézzük a pagereload kódját:
static function _jtbrowse_pagereload(this) local page:="",n,ok ok:=this:restpos(1) this:row2pos:=array(this:maxrow) for n:=1 to this:maxrow if( n>1 ) ok:=(1==eval(this:skipblock,1)) end if( !ok ) asize(this:row2pos,n-1) exit end this:savepos(n) page+=_jtbrowse_row_xmlout(this) next this:xmlpage(page) return NIL
Először az adatforrást a browse első sorának megfelelő helyre pozícionáljuk. Létrehozzuk a row2pos tömböt, amibe a savepos metódus majd bejegyezi a {browse tömbindex, adatpozícó} párokat. Ezután megpróbálunk végiglépkedni maxrow darab adatsoron, miközben a sorok pozícióit feljegyezzük, és page-ben gyűjtjük a lap XML leírását. A savepos metódus természetesen a saveposblock kiértékelésével állapítja meg az adatforrás pozícióját. Végül az xmlpage metódus készít egy <jtmessage> üzenetet, amiben elküldi az új lapot a terminálnak. A <jtmessage> küldése így történik:
static function _jtbrowse_xmlpage(this,page) local x:='<jtmessage' x+=ATTR("pid",alltrim(str(getpid()))) x+=ATTR("dialogid",this:dialogid) x+='>' x+="<control>"+this:name+"</control>"+EOL x+="<setpage>"+EOL x+=page x+="</setpage>"+EOL x+="</jtmessage>" this:send(x) return NIL
A browse használatára a main1.prg demóprogramban lehet példát találni. A lapozásra szolgáló toolbár kódja a mkbrowsebar.prg programban van.
1ComFirm BT.
2 Ismerem Cs.L. véleményét arról, hogy a maskot fejlettebb eszközzel kellene helyettesíteni, azonban szükségem volt valamire, amivel gyorsan el lehet indulni, az msk2dlg-ben pedig nincs több, mint félnapos munka. A Glade-et kipróbáltam, de egyáltalán nem tetszett. Szerintem a mask+msk2dlg-vel is messzire lehet jutni, és később még mindig módunk lesz a GUI tervezőt kicserélni, feltéve, hogy az XML interfészen nem változtatunk.