Programmering i Ruby

Den Pragmatiske Programmerers Veiledning

Forrige < Innhold ^
Neste >

Ruby Tk



Applikasjonsarkivet til Ruby inneholder flere utvidelser som tilbyr grafiske brukergrensesnitt (GUI) til Ruby, deriblant utvidelser for Tcl/Tk, GTK, OpenGL og så videre.

Tk utvidelsen følger med i hoveddistribusjonen og fungerer både på Unix og Windows systemer. For å bruke den, må du ha Tk installert. Tk er et stort system, og det har blitt skrevet hele bøker om det, så vi vil ikke kaste bort tid og energi på å gå så veldig dypt inn i selve Tk. I stedet vil vi konsentrere oss om hvordan man får tilgang til Tk sine fasiliteter i fra Ruby. Du kan trenge en av disse referansebøkene for å effektivt kunne bruke Tk sammen med Ruby. Biblioteksbindingen vi bruker ligner mest på bindingen Perl bruker, så du vil antagelig ønske å få tak i en kopi av Learning Perl/Tk  eller Perl/Tk Pocket Reference .

Tk arbeider utifra en komposisjonsmodell---det vil si, du begynner med å lage en beholder widget (slik som TkFrame eller TkRoot) og deretter lager du de widgets som skal være inne i beholderen, slik som knapper og tekst. Når du er klar for å starte det grafiske brukergrensesnittet, kaller du Tk.mainloop. Tk sin motor tar da kontroll over programmet, viser widgets og kaller din kode når den mottar hendelser i fra grensesnittet.

En enkel Tk applikasjon

En enkel Tk applikasjon skrevet i Ruby kan se omtrent slik ut:

require 'tk'
root = TkRoot.new { title "Ex1" }
TkLabel.new(root) {
  text  'Hello, World!'
  pack  { padx 15 ; pady 15; side 'left' }
}
Tk.mainloop

Figur 15.1: Hello world program skjermbilde

La oss ta en nærmere titt på koden. Etter å ha lastet inn tk utvidelsesmodulen, lager vi en rotnivå vindusramme ved hjelp av TkRoot.new. Deretter lager vi en tekst widget som blir barnet til rotrammen. Helt til slutt pakker vi rotrammen og går inn i grensesnittets hendelsesløkke.

Det er en god vane å angi rotrammen eksplisitt, men du kunne ha utelatt den---sammen med de ekstra opsjonene---og kuttet dette ned til en tre-linjers snutt:

require 'tk'
TkLabel.new { text 'Hello, World!' }
Tk.mainloop

Det er alt du trenger å gjøre! Utrusted med en av de Perl/Tk bøkene vi refererte til i starten av dette kapittelet, kan du nå lage alle de sofistikerte grafiske brukergrensesnittene du trenger. Men på den andre siden, hvis du ønsker å få med deg litt mer detaljer, så kommer de her nå.

Widgets

Det er lett å lage widgets. Ta navnet på den ønskede widget, slik det er gitt i Tk dokumentasjonen og sett Tk foran. For eksempel blir widgets Label, Button og Entry til klassene TkLabel, TkButton, and TkEntry. Du lager en instans av en widget ved å bruke new, som du ville gjort for et hvert annet objekt. Hvis du ikke angir en forelder til en gitt widget, brukes rotnivå vindusrammen. Vanligvis ønsker vi å spesifisere forelderen til en gitt widget sammen med mange andre opsjoner---farge, størrelse og så videre. Vi trenger også å kunne få informasjon tilbake fra våre widgets mens programmet kjører, ved å sette opp tilbakekall og delt data.

Å sette widget opsjoner

Dersom du ser i en Tk referanse manual (for eksempel den skrevet for Perl/Tk), vil du legge merke til at opsjoner til widgets vanligvis listes med en bindestrek---slik som en kommandoline opsjon gjerne er. I Perl/Tk blir opsjoner sendt til en widget i en Hash. Du kan gjøre dette i Ruby også, men et alternativ er å sende opsjonene ved hjelp av en kodeblokk; opsjonsnavnet brukes som et metodenavn inne i blokken og argumenter til opsjonen viser seg som argumenter til metodekallet. Widgets tar en forelder som første argument, fulgt av en valgfri hash fylt med opsjoner eller kodeblokken med opsjoner. Dette gjør at de to følgende variantene er ekvivalente.

TkLabel.new(parent_widget) {
  text    'Hello, World!'
  pack('padx'  => 5,
       'pady'  => 5,
       'side'  => 'left')
}
# or
TkLabel.new(parent_widget, text => 'Hello, World!').pack(...)

En liten advarsel når du bruker kodeblokk varianten: skopet til variablene er ikke som du tror. Blokken evalueres faktisk i konteksten til objektet til den gitte widget og ikke i kallerens. Det betyr at kallerens instansvariabler ikke er tilgjengelig i blokken, mens lokale variabler fra skopet rundt blokken og globale variable (ikke at du noensinne bruker dem) vil være tilgjengelige. Vi vil vise sending av opsjoner ved hjelp av begge metodene i eksemplene som følger.

Avstander (slik som opsjonene padx og pady i disse eksemplene) antas å være målt i piksler, men kan også spesifiseres i andre enheter ved å bruke en av de følgende endelsene ``c'' (centimeter), ``i'' (inch eller tomme), ``m'' (millimeter), eller ``p'' (punkt).

Hente ut data fra en widget

Vi kan få informasjon tilbake fra våre widgets ved å bruke tilbakekallsrutiner og ved å binde variabler.

Tilbakekallsrutiner er veldig enkle å sette opp. Opsjonen command (vist i kallet til TkButton i neste eksempel) tar inn et Proc-objekt som vil bli kjørt når en hendelse avfyrer tilbakekallet. Her bruker vi Kernel::proc for å konvertere blokken {exit} om til et Proc-objekt.

TkButton.new(bottom) {
  text "Ok"
  command proc { p mycheck.value; exit }
  pack('side'=>'left', 'padx'=>10, 'pady'=>10)
}

Vi kan også binde en Ruby variabel opp mot verdien til en Tk widget ved å benytte TkVariable som mellomledd. Dette ser vi i neste eksempel. Legg merke til hvordan TkCheckButton settes opp; dokumentasjonen sier at variable opsjonen tar en var reference som argument. Vi lager en Tk variabel referanse ved å bruke TkVariable.new. Kaller man så mycheck.value vil man få tilbake ``0'' eller ``1'', beroende på om avkrysningsboksen er avkrysset eller ikke. Denne mekanismen kan benyttes for enhver ting som størrer var referanser, slik som radioknapper og tekstfelt.

mycheck = TkVariable.new

TkCheckButton.new(top) {   variable mycheck   pack('padx'=>5, 'pady'=>5, 'side' => 'left') }

Sette/hente opsjoner dynamisk

I tillegg til å kunne sette opsjonene til en widget når man lager den, kan man også rekonfigurere en widget mens den kjører. Hver widget støtter metoden configure , som tar en Hash eller en kodeblokk, på samme måte som new. Vi kan forandre det første eksempelet slik at teksten endres når en knapp trykkes:

lbl = TkLabel.new(top) { justify 'center'
  text    'Hello, World!';
  pack('padx'=>5, 'pady'=>5, 'side' => 'top') }
TkButton.new(top) {
  text "Cancel"
  command proc { lbl.configure('text'=>"Goodbye, Cruel World!") }
  pack('side'=>'right', 'padx'=>10, 'pady'=>10)
}

Når Cancel trykkes, vil teksten endre seg umiddelbart fra ``Hello, World!'' til ``Goodbye, Cruel World!''

Du kan også spørre widgets om verdien av spesifikke opsjoner ved å bruke cget:

require 'tk'
b = TkButton.new {
  text     "OK"
  justify  'left'
  border   5
}
b.cget('text') » "OK"
b.cget('justify') » "left"
b.cget('border') » 5

Eksempel applikasjon

Her følger et litt lengre eksempel, som viser en skikkelig applikasjon---en ``pig Latin'' generator. (pig Latin er en engelsk variant av "røverspråk") Tast inn en frase, som for eksempel ``Ruby rules,'' og ``Pig It'' knappen vil oversette det til pig Latin.

Figur 15.2: Pig Latin program skjermbilde

require 'tk'

class PigBox   def pig(word)     leadingCap = word =~ /^A-Z/     word.downcase!     res = case word       when /^aeiouy/         word+"way"       when /^([^aeiouy]+)(.*)/         $2+$1+"ay"       else         word     end     leadingCap ? res.capitalize : res   end

  def showPig     @text.value = @text.value.split.collect{|w| pig(w)}.join(" ")   end

  def initialize     ph = { 'padx' => 10, 'pady' => 10 }     # common options     p = proc {showPig}

    @text = TkVariable.new     root = TkRoot.new { title "Pig" }     top = TkFrame.new(root)     TkLabel.new(top) {text    'Enter Text:' ; pack(ph) }     @entry = TkEntry.new(top, 'textvariable' => @text)     @entry.pack(ph)     TkButton.new(top) {text 'Pig It'; command p; pack ph}     TkButton.new(top) {text 'Exit'; command {proc exit}; pack ph}     top.pack('fill'=>'both', 'side' =>'top')   end end

PigBox.new Tk.mainloop

Litt på siden: Håndtering av geometrien

I kodeeksemplene i dette kapittelet vil du ofte se bruk av widget metoden pack. Det er en viktig metode, viser det seg---dersom du glemmer å kalle den, får du ikke se din widget. Metoden pack er en kommando som gir beskjed til kontrolleren som styrer geometrien om å plasse vår widget i følge de beskrankninger vi har angitt. Tre kommandoer kan sendes til geometrikontrollere:

Kommando Spesifisering av plassering
pack Fleksibel plassering med beskrankninger.
place Absolutt posisjonering.
grid Tabell (rad/kolonne) posisjonering.

Da pack er den kommandoen som brukes oftest, vil vi holde oss til den i våre eksempler.

Binde hendelser

Våre widgets er utsatt for den virkelige verden; de blir trykket på, musen beveger seg over dem, brukeren flytter kursoren inn i dem med Tab-tasten; alle disse tingene med mer genererer hendelser som vi kan fange. Du kan lage en binding mellom en hendelse på en spesifikk widget og en kodeblokk ved hjelp av metoden bind hos din widget.

La oss for eksempel anta at vi har laget en knapp som viser et bilde. Vi ønsker at bildet skal endre seg når brukeren har muspekeren over knappen.

image1 = TkPhotoImage.new { file "img1.gif" }
image2 = TkPhotoImage.new { file "img2.gif" }

b = TkButton.new(@root) {   image    image1   command  proc { doit } }

b.bind("Enter") { b.configure('image'=>image2) } b.bind("Leave") { b.configure('image'=>image1) }

Aller først lager vi to GIF bildeobjekter i fra filer på harddisken, ved hjelp av TkPhotoImage. Dernest lager vi en knapp (som kalles ``b'' for button), som viser bildet image1. Så binder vi hendelsen ``Enter'' slik at den dynamisk endrer bildet som vises på knappen til image2, og ``Leave'' hendelsen slik at den bytter tilbake til image1.

Dette eksempelet viser de enkle hendelsene ``Enter'' og ``Leave.'' Ben hendelsen gitt som argument til bind kan være sammensatt av flere understrenger, separert med bindestrek, av rekkefølgen modifikator-modifikator-type-detalj. Modifikatorene er listet opp i Tk referansen og inkluderer Button1, Control, Alt, Shift, og så videre. Type er navnet på hendelsen (tatt utifra navnekonvensjonene til X11) og inkluderer hendelser som ButtonPress, KeyPress, og Expose. Detail er enten et tall fra 1 til 5 for knapper eller en keysym(??) for innput fra tastaturet. For eksempel, en binding som som vil kjøres når musens første knapp slippes samtidig med at Control tasten holdes inne, kunne spesifiseres slik:

Control-Button1-ButtonRelease
eller
Control-ButtonRelease-1

Selve hendelsen kan inneholde visse felt, slik som tidspunktet og koordinatene til hendelsen. bind kan sende disse videre til tilbakekallsrutinen ved å bruke hendelsesfeltkoder (event field codes). Disse brukes på samme måte som printf spesifikasjoner. For eksempel, for å få tak i x og y koordinatene til en musbevegelse, spesifiserer du kallet til bind med tre parametre. Den andre parameteren er Proc-objektet som skal brukes som tilbakekallsrutine, og den tredje parameteren er hendelsens feltstreng.

canvas.bind("Motion", proc{|x, y| do_motion (x, y)}, "%x %y")

Canvas

Tk tilbyr en Canvas (lerret) widget som du kan bruke til å tegne på og lage PostScript utput. Her følger en enkel liten kodebit (tilpasset i fra distribusjonen) som tegner rette linjer. Klikk og hold musknapp 1 for å starte en linje, som så strekkes som en gummistrikk når du beveger musen rundt forbi. Slipp musknapp 1 og linjen blir tegnet i den posisjonen. Musknapp 2 vil dumpe en PostScript representasjon av det som er på lerret, passende for utskrift

require 'tk'

class Draw   def do_press(x, y)     @start_x = x     @start_y = y     @current_line = TkcLine.new(@canvas, x, y, x, y)   end   def do_motion(x, y)     if @current_line       @current_line.coords @start_x, @start_y, x, y     end   end   def do_release(x, y)     if @current_line       @current_line.coords @start_x, @start_y, x, y       @current_line.fill 'black'       @current_line = nil     end   end   def initialize(parent)     @canvas = TkCanvas.new(parent)     @canvas.pack     @start_x = @start_y = 0     @canvas.bind("1", proc{|e| do_press(e.x, e.y)})     @canvas.bind("2", proc{ puts @canvas.postscript({}) })     @canvas.bind("B1-Motion", proc{|x, y| do_motion(x, y)}, "%x %y")     @canvas.bind("ButtonRelease-1",                  proc{|x, y| do_release (x, y)}, "%x %y")   end end

root = TkRoot.new{ title 'Canvas' } Draw.new(root) Tk.mainloop

En håndfull musklikk, og voila! Et mesterverk:

Figur 15.3: ``We couldn't find the artist, so we had to hang the picture....''

Scrolling

Med mindre du planlegger å tegne bittesmå bilder, er nok det forrige eksempelet ikke så veldig nyttig. TkCanvas, TkListbox og TkText kan settes opp til å bruke rullegardiner (scrollbars), slik at du kan arbeide med et mindre utsnitt av hele bildet.

Kommunikasjonen mellom en rullegardin og en widget er toveis. Flyttes rullegardinen må den tilhørende widget endre det den fiser frem. Men også når en widget endres, må rullegardinen oppdateres for å vise den nye posisjonen.

Siden vi ikke har brukt lister noe særlig ennå, vil rullegardineksempelet vårt bruke en rullende liste med tekst. I den neste kodebiten begynner vi med å lage en ganske alminnelig TkListbox. Deretter lager vi en TkScrollbar. Rullegardinets tilbakekallsrutine (som settes med command) vil kalle listens widgets yview metode, som endrer verdien til den synlige porsjonen av listen i y-retningen.

Etter at den tilbakekallsrutinen er satt opp, lager vi den motsatte bindingen også: når listen føler behov for å rulle, angir vi den passende rekkevidden til rullegardinet med TkScrollbar#set. Vi kommer til å bruke akkurat denne kodebiten i et fullstendig funksjonelt program i neste avsnitt.

list_w = TkListbox.new(frame, 'selectmode' => 'single')

scroll_bar = TkScrollbar.new(frame,                   'command' => proc { |*args| list_w.yview *args })

scroll_bar.pack('side' => 'left', 'fill' => 'y')

list_w.yscrollcommand(proc { |first,last|                              scroll_bar.set(first,last) })

Bare en liten ting til

Vi kunne holdt på med Tk på enda noen hundre sider, men det hører hjemme i en annen bok. Det neste programmet er vårt siste Tk eksempel---en enkel GIF bildefremviser. Du kan velge en GIF fil fra rullegardinlisten og en liten versjon av bildet vil bli vist. Det er bare noen ting til vi har lyst til å nevne først.

Har du noensinne sett en applikasjon som gjør muspekeren om til et "opptatt" symbol (som et timeglass) og så glemmer å gjøre den normal etterpå? Det er et hendig lite triks i Ruby som unngår at slik skjer. Husker du hvordan File.new bruker en blokk for å forsikre seg om at filen blir lukket etter at man er ferdig med å bruke den? Vi kan gjøre en lignende ting med metoden busy, som vist i det neste eksempelet.

Dette programmet demonstrer også noen enkle manipulasjoner man kan gjøre med en TkListbox---legge elementer til listen, sette opp et tilbakekall på slipp av musknappen,[Du ønsker sannsynligvis knappeslipp og ikke knappetrykk, da knappetrykkhendelsen selekterer widget.] og hente ut det nåværende valget.

Inntill nå har vi brukt TkPhotoImage utelukkende til å vise frem ikoner direkte, men du kan også forstørre, redusere og vise deler av bilder også. Her bruker vi reduksjon til å skalere bildet ned for visning.

require 'tk'

def busy   begin     $root.cursor "watch" # Set a watch cursor     $root.update # Make sure it updates  the screen     yield # Call the associated block   ensure     $root.cursor "" # Back to original     $root.update   end end

$root = TkRoot.new {title 'Scroll List'} frame = TkFrame.new($root)

list_w = TkListbox.new(frame, 'selectmode' => 'single')

scroll_bar = TkScrollbar.new(frame,                   'command' => proc { |*args| list_w.yview *args })

scroll_bar.pack('side' => 'left', 'fill' => 'y')

list_w.yscrollcommand(proc { |first,last|                              scroll_bar.set(first,last) }) list_w.pack('side'=>'left')

image_w = TkPhotoImage.new TkLabel.new(frame, 'image' => image_w).pack('side'=>'left') frame.pack

list_contents = Dir["screenshots/gifs/*.gif"] list_contents.each {|x|   list_w.insert('end',x) # Insert each file name into the list } list_w.bind("ButtonRelease-1") {   index = list_w.curselection[0]   busy {     tmp_img = TkPhotoImage.new('file'=> list_contents[index])     scale   = tmp_img.height / 100     scale   = 1 if scale < 1     image_w.copy(tmp_img, 'subsample' => [scale,scale])     tmp_img = nil # Be sure to remove it, the     GC.start      # image may have been large   } }

Tk.mainloop

Figur 15.4: Scroll List skjermbilde

Helt på tampen, noen ord om søppeltømming---vi hadde tilfeldigvis et par veldig store GIF filer liggende[De var tekniske dokumenter! Æresord!] når vi testet denne koden. Vi ønsket ikke å holde disse store bildene i minnet lengre enn nødvendig, så vi satt bildereferansen til nil og kalte på søppeltømmeren umiddelbart for å fjerne rasket.

Oversette i fra Perl/Tk dokumentasjon

Det var alt. Du må nå klare deg på egenhånd. Som oftest kan du enkelt oversette dokumentasjonen gitt for Perl/Tk til Ruby. Men det er noen få unntak; noen metoder er ikke implementert og der finnes ekstra funksjonalitet som ikke er dokumentert. Inntill en Ruby/Tk bok kommer ut, er det beste råd vi kan gi å spørre på nyhetsgruppen eller lese i kildekoden.

Men i hovedsak er det ganske enkelt å se hva som foregår. Husk at opsjoner kan gies som en hashtabell eller via en kodeblokk, og at skopet til kodeblokken er inne i den TkWidget som brukes, og ikke instansen av din klasse.

Å lage objekter

Perl/Tk:  $widget = $parent->Widget( [ option => value ] )
Ruby:     widget = TkWidget.new(parent, option-hash)
          widget = TkWidget.new(parent) { code block }

Det er ikke sikkert at du har behov for å lagre returverdien fra den nylagde widget, men den er der hvis du har. Ikke glem å pakke en widget (eller bruk en av de andre geometristyrende metodene), for da vises den ikke.

Opsjoner

Perl/Tk:  -background => color
Ruby:     'background' => color
          { background color }

Husk at skopet til kodeblokken er forskjellig.

Variabel referanser

Perl/Tk:  -textvariable => \$variable
          -textvariable => varRef
Ruby:     ref = TkVariable.new
          'textvariable' => ref
          { textvariable ref }

Bruk TkVariable for å koble en Ruby variabel til en variabel i en widget. Du kan da bruke value tilgangsmetodene i TkVariable (TkVariable#value og TkVariable#value=) for å påvirke innholdet i en widget direkte.

( In progress translation to Norwegian by NorwayRUG. $Revision: 1.9 $ )
$Log: ext_tk.xml,v $
Revision 1.9  2002/10/20 20:09:19  kent
skriveleif.

Revision 1.8  2002/09/23 19:38:50  kent
Første kladd ferdig.

Revision 1.7  2002/09/21 13:11:51  kent
Nesten frem til canvas med første kladd.

Revision 1.6  2002/09/21 09:56:26  kent
Frem til geometri håndtering.

Revision 1.5  2002/09/16 18:35:24  kent
Et par avsnitt, frem til "Getting Widget Data"

Revision 1.4  2002/09/14 08:37:30  kent
Såvidt begynt.


Forrige < Innhold ^
Neste >

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide".
Translation to norwegian by Norway Ruby User Group.
Copyright for the english original authored by David Thomas and Andrew Hunt:
Copyright © 2001 Addison Wesley Longman, Inc.
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at
http://www.opencontent.org/openpub/).

(Please note that the license for the original has changed from the above. The above is the license of the original version that was used as a foundation for the translation efforts.)

Copyright for the norwegian translation:
Copyright © 2002 Norway Ruby User Group.
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at
http://www.opencontent.org/openpub/).
Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.
Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.