Jáva Terminál

Dr. Vermes Mátyás1

2002. október

1.  Villámrajt
2.  Áttekintés
    2.1.  Mi a Jáva Terminál?
    2.2.  Hogyan működik?
    2.3.  Hogy néz ki?
        2.3.1.  Fix pozícionálás
        2.3.2.  Rugalmas pozícionálás
    2.4.  Hogyan indítjuk a programokat?
        2.4.1.  A szerverek indítása SSL nélkül
        2.4.2.  A szerverek indítása SSL-lel
        2.4.3.  Több szerver indítása xstart-tal
        2.4.4.  A terminál indítása
3.  Web alkalmazás készítése Jáva Terminállal
    3.1.  Mi kell hozzá?
    3.2.  Jáva Web Start
4.  Üzenetek
    4.1.  Az üzenetrendszer alapjai
    4.2.  A terminál üzenetei
        4.2.1.  <action> üzenet
        4.2.2.  <destroy> üzenet
        4.2.3.  Egyéb üzenetek
    4.3.  A szerveralkalmazás üzenetei
        4.3.1.  <jtdialog> üzenet
        4.3.2.  <reaction> üzenet
        4.3.3.  <jtexit> üzenet
        4.3.4.  <jtclose> üzenet
        4.3.5.  <jtalert> üzenet
        4.3.6.  <jtdirlist> üzenet
        4.3.7.  <jtupload> üzenet
        4.3.8.  <jtdownload> üzenet
        4.3.9.  <jtfilechooser> üzenet
        4.3.10.  <jtfileutil> üzenetcsoport
        4.3.11.  <jtmessage> üzenetcsoport
5.  Szerver oldali objektumok
    5.1.  A kontrollok értékéről
    5.2.  A jtelem osztály
    5.3.  Grafikus komponens osztályok jtlib-ben
6.  Szerver oldali programozás
    6.1.  Messageloop
    6.2.  Szerkesztő sablonok (picture)
    6.3.  Browse (jtbrowse osztály)

1.  Villámrajt

Ha a Jáva Webstart már installálva van, érdemes kipróbálni az alábbi linkeket:

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.

2.  Áttekintés

2.1.  Mi a Jáva Terminá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 Swing használata meglehetősen szép külsőt eredményez, ami ráadásul a Jáva L&F bevetésével tovább alakítható.

2.2.  Hogyan működik?

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:

Ez az egyszerű funkcionalitás az ügyviteli alkalmazások széles körének megfelelő felhasználói felületet nyújt.

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.

2.3.  Hogy néz ki?

2.3.1.  Fix pozícionálás

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

fixpos

1. ábra: Fix pozícionálás

Az ablak mérete változtatható, de az a kontrollok pozíciójára nincs hatással. A statikus labelek fix (kék) fontokkal vannak kiírva. Minden komponens ezen fix font mérete által meghatározott rácsra illeszkedik. A get mezők Clipper-szerű szerkesztő sablonokkal (picture) vannak felszerelve. A getek belsejében ugyanaz a fix font van, mint a statikus labelekben (amik azért mégsem annyira statikusak, mert dinamikusan lehet változtatni a szövegüket, ikonjukat). A buttonokhoz, rádió és check boxokhoz egyedi ikon adható meg. Minden kontrollhoz tooltipet lehet rendelni.

fixposbrw

2. ábra: Fix pozícionálás browse-zal

A 2. ábra mutatja, hogy a fix pozícionálású dialogboxba browset is rakhatunk. A browse lapozós, ez azt jelenti, hogy az alkalmazás a tartalmat laponként adja a browsenak. A lapozásra szolgálnak a nyíl ikonos buttonok. A szokásos navigációs eszközök (Pgdn, Pgup, scrollbár) csak a már megkapott lapon belül mozognak.

2.3.2.  Rugalmas pozícionálás

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.

rugpos

3. ábra: Rugalmas pozícionálás (box layout)

Középen egy JTable komponens van, ez kb. megfelel a Clipper TBrowsenak. A JTable cellái Clipper kompatibilis szerkesztő sablonnal vannak formázva, és editálhatók. A logikai értékek check boxként editálódnak. Egyszerre több sort is ki lehet választani. Az oszlopok sorrendje egérhúzogatással felcserélhető.

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.

2.4.  Hogyan indítjuk a programokat?

2.4.1.  A szerverek indítása SSL nélkül

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.

2.4.2.  A szerverek indítása SSL-lel

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.

2.4.3.  Több szerver indítása xstart-tal

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.

2.4.4.  A terminál indítása

  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.

3.  Web alkalmazás készítése Jáva Terminállal

3.1.  Mi kell hozzá?

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

de semmi másra, pl. nincs szükség böngészőre.

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.

3.2.  Jáva Web Start

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.

MIME type beállítás Apache-on

A /etc/httpd/mime.types filébe beírjuk az alábbi sort:
application/x-java-jnlp-file    jnlp

MIME type beállítás Netscape 4.x-ben

Az Edit/Preferences/Navigator/Applications menüben felvesszük a következő adatokat:

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.

MIME type beállítás Konqueror 3.x-ban

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

4.  Üzenetek

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.

4.1.  Az üzenetrendszer alapjai

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:

  1. A szerver a <jtdialog> üzenetet küld, ha meg akar jeleníteni egy dialogboxot.

  2. A terminál <action> üzenetet küld, amikor valamilyen akció történt. Az üzenet tartalmazza a kontrollok aktuális tartalmát. Az üzenet után a terminál vár a <reaction> üzenetre.

  3. Az akció feldolgozása után a szerver <reaction> üzenetet küld a terminálnak, amiben küldi a megváltozott kontrollok új tartalmát.

  4. A <reaction> üzenetre a terminál betölti a dialogboxba az új értékeket, és folytatja az akciónál felfüggesztett működést.

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.

4.2.  A terminál üzenetei

4.2.1.  <action> üzenet

Action üzenet akkor keletkezik, ha a felhasználó A valid attribútum elnevezése a régi Clipperre utal, ahol a valid bővítményben lehetett megadni a get mezők postblockját. Most ilyen postblock nincs, csak annyit mondhatunk a terminálnak, hogyha az adott kontrollban akció történik, akkor arról azonnal értesülni akarunk.

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.

4.2.2.  <destroy> üzenet

Destroy üzenet keletkezik, ha a felhasználó becsukja a terminál valamelyik ablakát.

4.2.3.  Egyéb üzenetek

Az itt felsorolt üzenetek az alkalmazás által kezdeményezett műveletre adott válaszként fordulnak elő: <jtversion>, <alert>, <memoedit>, <dirlist>, <filechooser>, <uploadbegin>, <uploadend>, <uploaderror>, <download>, <makedir>, <makedirs>, <delete>, <exists>, <isfile>, <isdirectory>, <rename>.

4.3.  A szerveralkalmazás üzenetei

4.3.1.  <jtdialog> üzenet

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.

4.3.2.  <reaction> üzenet

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.

4.3.3.  <jtexit> üzenet

Utasítja a terminált a kilépésre.

4.3.4.  <jtclose> üzenet

Utasítja a terminált a legfelső ablak bezárására.

4.3.5.  <jtalert> üzenet

A Clipperből ismert alertnek megfelelő üzenet. A Jáva lehetővé teszi, hogy a messagebox szövegét HTML jelöléseket használva különféle stílusokkal lássuk el.

4.3.6.  <jtdirlist> üzenet

A Clipperből ismert directory függvény kicsit okosabb (reguláris kifejezéseket támogató) megvalósítása. Ezzel a szerver alkalmazás körül tud nézni a terminál gépen.

4.3.7.  <jtupload> üzenet

Filé másolás a terminálról a szerverre.

4.3.8.  <jtdownload> üzenet

Filé másolás a szerverről a terminálra.

4.3.9.  <jtfilechooser> üzenet

Filék és directoryk interaktív kiválasztása a terminálon.

4.3.10.  <jtfileutil> üzenetcsoport

Filé műveletek:
<makedir>
directory létrehozása a terminálon,
<makedirs>
többszörös mélységű directory létrehozása,
<delete>
filé/directory törlése,
<exists>
létezeik-e a megadott filé/directory,
<isfile>
filé-e a megadott directory bejegyzés,
<isdirectory>
directory-e a megadott directory bejegyzés,
<rename>
filé/directory átnevezése (mozgatása) a terminálon.

4.3.11.  <jtmessage> üzenetcsoport

Az üzenetben meghatározott kontrollnak kell továbbítani egy kontroll-specifikus üzenetet, pl. browse lapozás, progress bar léptetés, ikon, felirat, tooltip változtatás.

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.

5.  Szerver oldali objektumok

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.

5.1.  A kontrollok értékéről

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:

checkbox
logikai érték, a gomb állapota,
combo box
a kiválasztott index (1-től számozódik),
get mező
a getben editált változó, melynek típusát a picture-ből állapítja meg a rendszer,
rádió button
logikai érték, a gomb állapota,
table
a kiválasztott sorok indexéből képzett array,
tabpane
a kiválasztott fül indexe (1-től számozódik).

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.

5.2.  A jtelem osztály

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  

setdialogid
Összekapcsolja a kontrollt és a dialogboxot.
dialogid
Ehhez a dialogboxhoz tartozik a kontroll.
itemlist
A beágyazott kontrollok listája. Jelenleg a jtmenu, jtpanel, jtsplitpane, jttabpane, jttoolbar kontrolloknak van nem üres itemlist-je.
xmlout
A kontrollok ezzel adják meg saját XML leírásukat.
xmlname
A kontroll osztályának neve.
xmladd
Egyes kontrollok xmlout metódusa úgy működik, hogy meghagyják ugyan a jtelemtől örökölt xmlout-ot, viszont felüldefiniálják xmladd-ot, amit a jtelem-beli xmlout mindig meghív. Ezzel a kontrollok speciális kiegészítéseket fűzhetnek az általános jtelem-beli xmlout-hoz.
xmlput
Beállítja egy kontroll értékét. Az új értéket szövegesen, XML formátumban kell megadni.
xmlget
Kiolvassa egy kontroll értékét. Az értéket szövegesen, XML formátumban kapjuk.
savestate
Megjegyzi a kontroll aktuális értékét laststate-ben.
laststate
A kontroll terminálnak elküldött utolsó értékét tartalmazza.
changed
Az aktuális érték és laststate összehasonlításával megállapítja, hogy változott-e a kontroll értéke az utolsó küldés óta. Csak a megváltozott értékeket kell küldeni a terminálnak.

Alkalmazási programoknak  

varget
Kiolvassa a kontroll értékét. Az értéket Clipper típusú (C, N, D, L, A) változóként kapjuk.
varput
Értéket ad egy kontrollnak. Az új értéket Clipper változóként kell átadni.
top
A kontroll tetejének koordinátája.
left
A kontroll bal szélének koordinátája.
bottom
A kontroll aljának koordinátája.
right
A kontroll jobb szélének koordinátája.
halign
Előírja, hogy a kontroll belsejében levő szöveg merre legyen igazítva vízszintesen.
valign
Előírja, hogy a kontroll belsejében levő szöveg merre legyen igazítva függőlegesen.
alignx
Előírja, hogy a kontroll az alatta/fölötte levő kontrollokhoz melyik részével illeszkedjen víszintesen (jobb/bal szélével, közepével).
aligny
Előírja, hogy a kontroll az mellette levő kontrollokhoz melyik részével illeszkedjen függőlegesen (aljával, tetejével, közepével).
name
A kontroll azonosítója, egyedinek kell lennie.
text
A kontroll szövege. Egyes kontrolloknál ez egyben a kontroll értéke is.
tooltip
A kontroll tooltipjének a szövege.
icon
Egyes kontrollokhoz (label, button, menü) ikon rendelhető. Az ikonnak a jterminal.jar-beli készletben létező jpeg, gif, vagy png filének kell lennie.
image
Olyan kép, amit futás közben a szerverről küldünk át a terminálnak.
border
A defaulttól eltérő keret adható itt meg.
valid
Egy flag, ami előírja, hogy a kontrollal történt Jáva akciót jelenteni kell. A menük és push buttonok akcióit mindig jelenti a terminál.
escape
Ezzel a flaggel jelölt kontrollok akciójánál nem kell vizsgálni, hogy a kontrollok kitöltése megfelelő-e.
enabled
Ez a flag letiltja/engedélyezi a kontrollt.
focusable
Adható-e fókusz a kontrollra?
actionblock
Ha egy kontrollnak van akcióblokkja, akkor azt a getmessage automatikusan végrehajtja, ha a kontrollból akció érkezik. Az ilyeneknek a valid attribútumával sem kell foglalkozni.
changetext
Menet közben megváltoztatja a kontroll szövegét, pl. egy checkbox feliratát, amit tipikus esetben csak a dialogbox inicializálásakor szoktunk megadni. (Azonnal küldi az üzenetet.)
changetooltip
Menet közben megváltoztatja a kontroll tooltipjét. (Azonnal küldi az üzenetet.)
changeicon
Menet közben megváltoztatja a kontroll ikonját. (Azonnal küldi az üzenetet.)
changeimage
Menet közben új bináris képet küld egy kontrollnak. (Azonnal küldi az üzenetet.)
changeenabled
Menet közben negváltoztatja kontroll engedélyezettségét. (Azonnal küldi az üzenetet.)

5.3.  Grafikus komponens osztályok jtlib-ben

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.

jtalert
Clipper messageboxok létrehozásához.
jtbrowse
Funkcionálisan a Clipper TBrowse utóda (és éppenséggel a TBrowse osztály leszármazottja), helyette a jttable, jtbrowsearray, jtbrowsetable objektumok használata ajánlott.
jtbrowsearray
A jttable osztály leszármazottja, ami fel van készítve array-k browseolására.
jtbrowsetable
A jttable osztály leszármazottja, ami fel van készítve táblaobjektumok browseolására.
jtcheck
Checkbox.
jtcombo
Lista előre meghatározott elemekkel, az elemekből egyet lehet kiválasztani.
jtdialog
Legfelső szintű grafikus elem, maga a dialogbox
jtfilechooser
A szokásos filé kiválasztó dialogbox. A terminál filérendszerében lehet választani.
jtget
Szövegmező, ami a Jáva szövegmezőnél sokkal intelligensebb, mivel Clipper-szerű picture-rel (formázó/szerkesztő sablonnal) van ellátva.
jtglue
Olyan grafikus elem, aminek akármilyen mérete lehet, így alkalmas hézagok kitöltésére.
jtlabel
Statikus felirat.
jtmenu
Legördülő menü. A menü többszintű lesz, ha a menüelemek helyére újabb menüket teszünk.
jtmenuitem
Egy menüelem (sor) a legördülő menüben.
jtmenusep
Elválasztó vonal (szeparátor) a menüben.
jtmenucheck
Checkboxként működő menüitem.
jtmenuradio
Rádió buttonként működő menüitem.
jtpanel
Összetett grafikus elem, ami további elemeket tartamazhat.
jtprogbar
Progressz indikátor.
jtpush
Push button (egyszerű nyomógomb).
jtradio
Rádió button.
jttable
A jtbrowse olyan változata, ami nem örököl a Clipperes TBrowse-ból, így nem gyökerezik bele a régi könyvtárakba. Az egyszerűség ára, hogy a browse nem jeleníthető meg lokálisan, hanem csak a terminálban.
jttabpane
Fülekkel ellátott panelek, amik egymást takarják, és a füllel lehet kiválasztani, hogy melyik kerüljön felülre.
jttoolbar
A panel egy olyan speciális formája, amire bizonyos egérműveleteket támogat a Jáva.
jtpassw
Jelszavak bekérésére specializált szövegmező. Egyrészt "*"-ként jeleníti meg a begépelt karaktereket, másrészt nem a jelszót küldi vissza eredményként, hanem annak MD5 hash kódját.

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.

6.  Szerver oldali programozás

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.

6.1.  Messageloop

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:

  1. Ha a search get egy fix pozícionálású dialogboxban volna, és azt dialogboxot az msk2dlg kódgenerátorral készítettük volna, akkor a dialogboxnak volna search nevű attribútuma, ezen keresztül a getre így hivatkozhatnánk: dlg:search.

  2. A második lehetőség (kikommentezve) látható a példában: a dialogbox név alapján meg tudja keresni a kontrolljait.

  3. Végül, a dialogbox varinst metódusával létre lehet hozni a dialogboxban egy beágyazott objektumot, aminek éppen a dialogbox kontrolljai az attribútumai. Ennek a beágyazott objektumnak, mint attribútumnak a neve var. Tehát a getet így lehet elővenni: dlg:var:search.

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).

6.2.  Szerkesztő sablonok (picture)

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:

bár ez utóbbiakat a check boxok miatt nem fogjuk gyakran használni.

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.

6.3.  Browse (jtbrowse osztály)

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é:

saveposblock
Amikor a terminálban a felhasználó le-fel mozog a browse sávkurzorával, akkor a szerver ezt a mozgást nem követi az adatforrás pozicionálásával. Azért, hogy később azonosíthassuk, hogy a felhasználó melyik adatrekordot választotta ki, a browseolandó lap átküldése előtt minden sorhoz meg kell jegyezni az adatforrás pozícióját. Pl. tömb browsolásakor minden browse-beli sorhoz fel kell jegyeznünk egy tömbindexet. Ezt végzi a saveposblock. A saveposblock tehát egy (browse) sorindexhez olyan adatot rendel, ami azonosítja az adatforrás sorát.

restposblock
Amikor a terminál egy akciót jelent, a feldolgozás során rá kell pozícionálnunk az adatforrás azon rekordjára, ami a browseban ki van választva. Ehhez a restposblock-ot használjuk, ami a kiválasztott sorindex és a saveposblock által elmentett adat alapján pozícionálja az adatforrást.

A jtbrowse osztály olyan default saveposblock-kal és restposblock-kal inicializálódik, ami egy minimális működést lehetővé tesz: az adatforrás elejére lehet pozícionálni, és onnan előrefelé lehet lapozni. A jtbrowse-ban az alábbi metódusokkal navigálunk:

pageprev
Betölti az előző lapot, feltéve, hogy a browse előzőleg már pozícionálva volt.
pagenext
Betölti a következő lapot, feltéve, hogy a browse előzőleg már pozícionálva volt.
pagefirst
Betölti a legelső lapot, ezzel a browse pozícionált állapotba kerül.
pagelast
Betölti a legutolsó lapot, ezzel a browse pozícionált állapotba kerül.
pagereload
Újratölti az aktuális lapot, feltéve, hogy a browse előzőleg már pozícionálva volt.
pagecurrent
Az adatforrás aktuális pozíciójától kezdve betölt egy lapot, ezzel a browse pozícionált állapotba kerül.

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.


Jegyzetek:

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.