Julkaistu Tietoyhteys-lehden numerossa 1/1997 sivuilla 24-25 ja 32.

Esimerkkiaineisto zip-pakettina.


Jorma Sajaniemi
saja@cs.joensuu.fi

Joensuun yliopisto
Tietojenkäsittelytieteen laitos
PL 111, 80101 Joensuu

&

Karjalan Tietovalta Oy
PL 321, 80101 Joensuu


Graafinen käyttöliittymä nopeasti Tcl/Tk:lla

	Tcl/Tk on helppokäyttöinen skriptikieli, jolla saa nopeasti
	aikaan graafisia sovelluksia. Järjestelmä on ilmainen
	ja se toimii sekä UNIX:issa, Windows:issa että Mac:issa.

Graafisen käyttöliittymän rakentaminen on useimmiten työlästä ja hankalaa. Lisäksi monet välineet edellyttävät laajojen kirjastojen tarkkaa hallintaa ennenkuin edes yksinkertaiset asiat ovat tehtävissä. Tcl/Tk tarjoaa sekä kirjastopohjaisista ratkaisuista että perinteisten ohjelmointikielten visuaalisista toteutuksista (Visual Basic, ...) olennaisesti poikkeavan ratkaisun.

Tcl/Tk-skriptit voivat muiden skriptikielien tapaan kutsua muita ohjelmia, mutta lisäksi ne voivat tavanomaisten ohjelmointikielten tapaan sisältää ohjausrakenteita, tiedostojen ja muuttujien käsittelyä, funktioita, lausekkeita jne. Graafisen käyttöliittymän muodostamiseksi on käytettävissä voimakkaat komennot tekstikenttien, painikkeiden, valikoiden ja muiden tavanomaisten vuorovaikuttimien luomiseen. Skriptit kirjoitetaan normaaliin tekstimuotoon, joten niiden hahmottaminen ja muokkaaminen on helppoa.

Visuaalikielten tarjoamaa mahdollisuutta suunnitella näyttö piirtämällä se kuvaruudulle ei Tcl/Tk:ssa ole, mutta tätä piirrettä ei tarvitakaan. Järjestelmän sisältämä näytön muodostaja ("geometrian hoitaja") huolehtii asiasta automaattisesti skriptin antamien ohjeiden rajoissa. Tämä automatiikka huolehtii myös näytön komponenttien kokojen ja sijaintien muutoksista silloin, kun käyttäjä muuttelee ikkunoiden kokoja skriptin suorituksen aikana.

Tcl/Tk koostuu itse asiasta kahdesta eri kielestä. Tcl (Tool Command Language) muodostaa kielen ytimen ja sillä tehdyillä skripteillä on puhtaasti merkkipohjainen käyttöliittymä. Tcl-skripti toteutetaan antamalla se Tcl-tulkille (tclsh). Tcl-kielen yksi perusajatuksista on kuitenkin mahdollisuus kielen laajentamiseen ja Tk (Toolkit) onkin tällainen laajennus, joka sisältää graafisen käyttöliittymän muodostamiseen tarvittavat uudet komennot. Lisäksi Tk sisältää automaattisen tapahtumasilmukan, joka käsittelee käyttäjän kaikki toimenpiteet skriptin suorituksen aikana. Tcl/Tk-skriptit suoritetaan wish-tulkilla (WIndowing SHell).

Yksinkertainen editori

Kuvassa 1 on esimerkki yksinkertaisesta editorista, jonka avulla on mahdollista muokata tekstiä normaaleilla perustoiminnoilla. Lisäksi editorissa on varmistuskysymys, jos käyttäjä yrittää tallentaa tekstiään olemassaolevan tiedoston päälle. Yksinkertaisuuden vuoksi käsiteltävän tiedoston nimi on annettava suoraan pääikkunaan.

Kuva 1: Yksinkertaisen editorin ulkoasu UNIX:illa

Editorin toteuttava skripti on kokonaisuudessaan kuvassa 2. Skriptin alussa näytön komponentit luodaan (komennoilla text, frame, label, entry sekä button) ja sijoitetaan (pack) ikkunaan. Tämän jälkeen skripti määrittelee kaksi proseduuria (avaaTiedosto, tallennaTiedosto), jotka tulevat aikanaan suoritettavaksi, kun käyttäjä painaa vastaavia painikkeita. Skriptiä suorittaessaan Tcl/Tk-tulkki jää lopuksi suorittamaan tapahtumasilmukkaa eli odottamaan käyttäjän toimenpiteitä ja reagoimaan niihin.

#!/usr/bin/wish
#
# Yksinkertainen editori

text   .teksti -background white
pack   .teksti -fill both -expand yes

frame  .kehys          -relief sunken -borderwidth 3
pack   .kehys          -side left

label  .kehys.ohje     -text "Tiedosto:"
entry  .kehys.nimi     -textvariable tiedostoNimi
button .kehys.avaa     -text "Avaa" -command avaaTiedosto
button .kehys.tallenna -text "Tallenna" -command tallennaTiedosto
pack   .kehys.ohje .kehys.nimi .kehys.avaa .kehys.tallenna -side left

button .lopeta -text "Lopeta" -command "destroy ."
pack   .lopeta -side right

proc avaaTiedosto {} {
  global tiedostoNimi
  .teksti delete 1.0 end
  set f [open $tiedostoNimi r]
  while {![eof $f]} {
    .teksti insert end [read $f]
  }
  close $f
}

proc tallennaTiedosto {} {
  global tiedostoNimi
  set vastaus 0
  if [file exists $tiedostoNimi] {
    set vastaus [tk_dialog .varmistusIkkuna "Tiedosto on olemassa!" \
                "Kirjoitanko kuitenkin?" question 0 OK Peru]
  }
  if {$vastaus == 0} {
        set f [open $tiedostoNimi w]
        puts $f [.teksti get 1.0 end]
        close $f
  }
}

Kuva 2: Yksinkertaisen editorin toteuttava skripti

Näytön komponentit nimetään polkunimien tapaan siten, että esimerkiksi ".kehys.avaa" on kehyksessä "kehys" oleva painike "avaa". Komponentin luomisen yhteydessä voidaan vaikuttaa sen ulkonäköön (esimerkiksi "-relief sunken" tarkoittaa upottamista ympäristön suhteen). Komponentin sijoittaminen ikkunaan tehdään omalla komennollaan (pack), jolloin voidaan puolestaan ohjata komponentin sijaintia ("-side left") ja sen käyttäytymistä ikkunan kokoa muutettaessa ("-fill both -expand yes" saa tekstikentän käyttämään aina kaiken vapaan tilan).

Näytön komponentteja luovat komennot luovat samalla uusia komentoja, jotka ovat käytettävissä skriptin myöhemmissä vaiheissa. Esimerkiksi komento "text .teksti -background white" luo näytölle tulevan tekstialueen lisäksi myös uuden komennon ".teksti", jota käytetään myöhemmin tekstialueen käsittelyyn. Esimerkiksi tiedoston avaamisen yhteydessä tällä komennolla poistetaan tekstialueen vanha sisältö (".teksti delete 1.0 end", missä 1.0 viittaa tekstin ensimmäiseen merkkiin).

Muuttujien nimet alkavat dollarilla silloin, kun viitataan muuttujan arvoon. Sen sijaan viitattaessa muuttujan nimeen ei dollaria käytetä. Niinpä "set vastaus 0" asettaa muuttujalle vastaus arvoksi nollan ja "$vastaus" palauttaa muuttujan arvon. Skriptikielten tapaan komentojen ja muuttujien nimet voivat muodostua vasta suoritushetkellä, joten edellisen sijoituksen jälkeen sijoitus "set alku$vastaus 3" tarkoittaisi arvon 3 sijoittamista muuttujalle alku0.

Yksinkertaisuuden vuoksi esimerkkiskripti ei tarkista onko avattava tiedosto olemassa. Tästä ei kuitenkaan ole mitään haittaa, koska jos tiedostoa ei löydy, antaa tulkki asiasta ilmoituksen mutta se ei lopeta skriptin toimintaa. Avaa-painikkeen painamisesta käynnistynyt toimenpidesarja kylläkin keskeytyy, mutta tulkki jatkaa tästä huolimatta toimintaansa normaalisti käyttäjän seuraavan toimenpiteen kohdalla.

Hieman dynaamisuutta

Kuvan 3 skripti alkaa proseduurin (viivaKuva) määrittelyllä. Jos muuttujan kuvaNro arvo on esimerkiksi 5, niin tämä proseduuri luo komponentit .alusta5 ja .vieritys5, joista edellinen on piirtoalusta ja jälkimmäinen siihen liittyvä vierityspalkki. (Näiden keskinäinen kytkentä näyttää hieman monimutkaiselta, mutta se toteutetaan onneksi aina samalla tavalla.) Proseduuri saa parametrinaan (pisteet) joukon koordinaattipareja, joiden avulla se piirtää keltaisen viivan, joka kulkee näiden pisteiden kautta. Lisäksi proseduuri piirtää alustalle numeroasteikon.
#!/usr/bin/wish
#
# Viivakuvasto tiedoston perusteella

proc viivaKuva pisteet {
  global kuvaNro

  canvas    .alusta$kuvaNro   -width 500 -height 50 -background green \
		              -xscrollcommand ".vieritys$kuvaNro set" \
                              -scrollregion {0 0 5000 50}
  scrollbar .vieritys$kuvaNro -orient horizontal -width 10 \
                              -command ".alusta$kuvaNro xview"
  pack      .alusta$kuvaNro   -fill x
  pack      .vieritys$kuvaNro -fill x

  eval ".alusta$kuvaNro create line $pisteet -width 2 -fill yellow"

  for {set i 0} {$i <= 5000} {set i [expr $i +100]} {
    .alusta$kuvaNro create line $i 0 $i 37 -width 2 -fill grey
    .alusta$kuvaNro create text $i 45 -text $i
  }
}

set f [open viivat.txt r]
set kuvaNro 0

while {[gets $f rivi] >= 0} {
  viivaKuva $rivi
  incr kuvaNro
}

close $f

button .lopeta -text Sulje -command "destroy ."
pack   .lopeta

Kuva 3: Komponenttien dynaaminen muodostaminen

Proseduurin määrittely ei tietenkään tarkoita sen suorittamista eikä siis luo yhtään komponenttia. Proseduuria kuitenkin kutsutaan silmukassa, jossa luetaan tiedostoa (viivat.txt) rivi kerrallaan. Kunkin rivin kohdalla skripti kutsuu proseduuria ja antaa sille parametriksi juuri lukemansa rivin. Siten tiedoston jokaista riviä kohti syntyy oma viivansa vierityspalkkeineen ja lopullisen kuvan koko riippuu täysin tiedoston rivien lukumäärästä. Kuvan 4 esimerkki on syntynyt tiedostosta, jossa on neljä riviä.

Kuva 4: Esimerkki viivastosta, kun tiedostossa on neljä riviä

Komponenttien dynaaminen muodostaminen tapahtuu Tcl/Tk:ssa kätevästi, koska se ei eroa millään tavalla komponenttien normaalista muodostamisesta. Komponenttien nimissä on toki käytettävä muuttujia (".vieritys$kuvaNro"), mutta niin tehdään itse asiassa usein muutenkin. Monestihan sama komponenttiryhmä esiintyy useissa paikoissa samanlaisena, jolloin riittää kirjoittaa sen muodostaminen kertaalleen proseduuriin, jonka parametrina on komponenttiryhmän sijainnin ilmoittava polkunimi. Kun komponentit nimetään proseduurin sisällä suhteellisesti ("$sijainti.painike"), niin ryhmien luominen on helppoa. Tällä samalla tavalla voidaan myös helpottaa eri skripteissä tarvittavien samankaltaisten osien muodostamista.

Laajennettavuutta moneen suuntaan

Kielen perusominaisuuksiin kuuluu mahdollisuus omien komentojen tekemiseen. Skripteissä määriteltävät proseduurit ovat jo tällaisia, mutta ne suoritetaan tulkitsemalla, joka saattaa olla kriittisissä kohdissa liian hidasta. Tällöin voidaan tulkkia laajentaa kirjoittamalla uusia komentoja C-kielellä ja linkittämällä nämä alkuperäiseen tulkkiin. Tällä menettelyllä esimerkiksi monimutkaista laskentaa suorittavaa skriptiä voidaan tehostaa merkittävästi.

Tulkki voidaan myös laittaa toimimaan yhdessä jollain muulla kielellä toimivan erillisen ohjelman kanssa. Tällä tavalla on toteutettu esimerkiksi yhteys Prolog-ohjelmiin, jolloin skripti huolehtii pelkästään käyttöliittymästä ja varsinainen käsittely hoidetaan kokonaan muulla kielellä.

Tcl/Tk onkin moneen suuntaan muunnettavissa oleva väline, mutta jo sinälläänkin aivan riittävä vaativien ja monimutkaisten sovellusten laatimiseen. Erityisenä etuna on skriptien lyhyys ja selkeys moneen muuhun välineeseen verrattuna.

Lisätietoja:

J.K.Ousterhout: Tcl and the Tk Toolkit. Addison-Wesley, 1995.

B.B.Welch: Practical Programming in Tcl and Tk. Prentice Hall, 1995.

P.Tarau & B.Demoen: Language Embeddind by Dual Compilation and State Mirroring. In M.Fromherz, A.Kusalik, & O.Nytro (eds.), Proceedings of 6th Workshop on Logic Programming Environments, Santa Margherita Ligure, 1994, 15-20.

http://www.cis.rl.ac.uk/struct/AISD/VSG/publications/cookbook/index.html

ftp://ftp.funet.fi/pub/languages/tcl/tk/