Materjalid koostas ja kursuse viib läbi
Tartu Ülikooli arvutiteaduse instituudi programmeerimise õpetamise töörühm
< eelmine | 4. nädala sisukord | järgmine > |
4.2 Graafikaraamistik tkinter. Tahvel
Pythoni graafiliste võimalustega oleme juba kokkupuutunud seoses kilpkonnaga (moodul turtle). Sel nädalal tegeleme Pythoni graafikaraamistikuga tkinter. Eesmärke on seejuures mitu. Ühelt poolt annab see võimaluse saada graafilise kujundusega programme - on ju enamik igapäevaselt kasutatavaid programme graafilised. Teiselt poolt tutvume natuke teistmoodi programmeerimisstiiliga.
Moodul tkinter (koos alammooduliga tkinter.ttk) kuulub Pythoni standardsete moodulite hulka ja töötab erinevatel operatsioonisüsteemidel. Nimetatud moodulid põhinevad levinud teegil nimega Tk, mida kasutatakse ka teistes programmeerimiskeeltes.
Käesolev materjal põhineb tkinteri versioonil 8.5, mis on Python 3 (ja ka Thonnyga) kaasas. Selles versioonis tehtud kasutajaliidesed on vastava operatsioonisüsteemi (Windows, Mac OS, Linux) konkreetsele platvormile omase välimusega.
Tkinter on väga suurte võimalustega, millest siin vaatleme vaid väikest osa. Paraku ei ole head ühist dokumentatsiooni, kust kõike vaadata saaks. Mõned materjalid sisaldavad siiski päris palju infot ka.
- http://www.tkdocs.com/ – hea materjal Tk ja tkinter-i põhimõtete õppimiseks.
Käesolevas osas tutvume tkinteri abil tehtavate programmide üldise ülesehitusega ja võimalusega teha pilte tahvlil (canvas). Nuppude, siltide jt kasutajaliidese võimalustega tegeleme osas 4.3.
Harilik graafikaaken koosneb:
- raamist
- tahvlitest ja
- atomaarsetest komponentidest.
Raam kujutab endast tavaliselt akna põhimikku, mille ülemises ääres on tiitliriba. Erirolli mängib juurraam, mis algatab tkinteri elutsükli. Nii raam kui ka tahvel on konteinerid, mille sisse on võimalik paigutada teisi graafikaelemente. Nii võime näiteks lisada raamile mõne omaloodud tahvli ning sellele omakorda atomaarseid elemente. Viimased kujutavad endast joonise nähtavaid komponente, näiteks programmide kasutajaliidestest tuttavad nupud, sildid ning muud taolised elemendid. Lisaks sellele on võimalik tahvlile otse mitmesuguseid graafilisi kujutisi joonistada.
Koostame programmi, mis loob ja kuvab juurakna.
Alustame sellest, et impordime kõik moodulis tkinter olevad klassid ja funktsioonid:
from tkinter import *
Siis loome juurakna. Raami tiitliks paneme "Tere, maailm!":
raam = Tk() raam.title("Tere, maailm!")
Pärast graafiliste elementide loomist tuleb siseneda Tkinteri põhitsüklisse — vastasel juhul ei toimu ekraanil midagi:
raam.mainloop()
Olles selle tsükli sees, teostab programm raamis olevate elementide paigutamist ning jälgib kasutaja sisendit. Programm töötab tsüklis kuni põhiraami sulgemiseni.
TAHVEL (CANVAS)
Tahvel kujutab endast ristkülikukujulist ala, mille peale saab joonistada graafilisi kujutisi. (Sõna canvas üks vasteid on lõuend, mis ehk olekski mõneti täpsem - lõuendile ju joonistataksegi. Mitmetes materjalides on siiski juurdunud vasteks tahvel.) Paneme tähele, et koordinaadistiku alguspunkt asub tahvli ülemises vasakus nurgas ning x-koordinaat suureneb vasakult paremale ja y-koordinaat ülevalt alla. Mõõtühikuteks on pikslid (ehk ekraanitäpid) ja seega koordinaatide väärtusteks on täisarvud.
Lisame nüüd tühja tahvli raamile loomist:
from tkinter import * raam = Tk() raam.title("Tühi tahvel") # loome tahvli laiusega 600px tahvel = Canvas(raam, width=600) # paigutame tahvli raami ja teeme nähtavaks tahvel.pack() # siseneme põhitsüklisse raam.mainloop()
Peale laiuse võib tahvlile ette anda mitmeid teisi argumente. Nende loetelu kuvatakse, kui küsida dokumentatsiooni tahvli kohta (ehk kirjutada käsurealt):
>>> print(Canvas.__init__.__doc__)
Kopeerige ja käivitage ülalolev tühja tahvliga raami programm.
Täiendage tahvli loomise käsku, muutes
- tahvli taustavärvi (nt
background="red"
); - tahvli kõrgust (height).
Tahvlile saab lisada mitmesuguseid graafikaelemente: jooni, kaari, pilte, ringe, hulknurkasid, ristkülikuid ja teksti. Iga sellise objekti loomiseks on olemas vastav käsk (mis algab create_), millele antakse ette kujundi positsioon ja stiil. Vaatleme mõnda neist lähemalt.
Murdjoon, mille tippudeks on (x0, y0), (x1, y1), ..., (xn, yn), joonistatakse create_line(x0, y0, x1, y1, ..., xn, yn, option, ... )
abil.
Näited:
# üks horisontaalne sirglõik tahvel.create_line(50,50,100,50) # horisontaalne sirglõik ja vertikaalne sirglõik tahvel.create_line(50,150,100,150,100,200) # sirglõik paksusega 4px tahvel.create_line(50,100,100,250, width=4) # rohelist värvi nool paksusega 4px tahvel.create_line(350,50,350,100, width=4, fill="green", arrow=LAST)
Argumendi väärtus arrow=LAST kujundab joone lõppu nooleotsa.
Ristküliku, mille ülemise vasakpoolse tipu koordinaadid on (x0, y0) ja alumise parempoolse tipu koordinaadid on (x1, y1), saame create_rectangle (x0, y0, x1, y1, option, ... )
abil.
Näited:
# seest tühi ristkülik (ruut) mustade servadega tahvel.create_rectangle(150,50,200,100) # seest tühi ristkülik kollaste servadega, mille paksus on 2px tahvel.create_rectangle(150,150,200,200, width=2, outline="yellow") # mustade servadega roheline ristkülik tahvel.create_rectangle(150,250,200,300, fill="green")
Hulknurga tippudega (x0, y0), (x1, y1), ... saame create_polygon(x0, y0, x1, y1, ..., option, ...)
abil.
Näited:
# mustade servadega punane kolmnurk tahvel.create_polygon(150,350,150,400,200,375, fill="red", outline="black")
Ovaali joonistab create_oval(x0, y0, x1, y1, option, ... )
.
Ovaal on mõtteliselt piiratud ristkülikuga, mille ülemise vasakpoolse tipu koordinaadid on (x0, y0) ja alumise parempoolse tipu koordinaadid on (x1, y1). Ovaali joonistamisel peate seega arvestama ristkülikuga, mis seda ümbritseb.
Näited:
# roheliste servadega punane ovaal(ring) tahvel.create_oval(10,10,100,100, fill="red", outline="green")
Teksti saame tahvlile create_text(x0, y0, option, ... )
abil. Tekst tuleb ette anda argumendiga text
. Teksti koht sõltub koordinaatidest (x0, y0) ning argumendist anchor
. Vaikimisi on punkt (x0, y0) teksti keskel.
Näited:
# musta värvi tekst "Tere!" tahvel.create_text(50,50, text="Tere!") # sinist värvi teksti "Tere!" alumine vasak punkt on (50, 50) tahvel.create_text(50,50, text="Tere!", anchor=SW, fill="blue")
Argumendi anchor
teised võimalikud väärtused on CENTER ja ilmakaartele vastavad N, NE, E, SE, S, W ja NW.
Kui joonistusel on korduvaid elemente, siis võib olla abi tsüklitest. Teeme näiteks Haapsalu ja Sillamäe lipud.
from tkinter import * raam = Tk() raam.title("Haapsalu lipp") # loome tahvli laiusega 600px ja kõrgusega 300 px tahvel = Canvas(raam, width=600, height = 300) # ühe joone kõrgus kõrgus = 100 # tsüklimuutuja i = 0 while i < 3: # esimene ja kolmas joon if i == 0 or i == 2: #if i % 2 == 0: # sinine väike ristkülik tahvel.create_rectangle(0, i * kõrgus, 200, (i + 1) * kõrgus, fill="blue", outline="blue") # valge suur ristkülik tahvel.create_rectangle(200, i * kõrgus, 600, (i + 1) * kõrgus, fill="white", outline="white") # teine joon else: # valge väike ristkülik tahvel.create_rectangle(0, i * kõrgus, 200, (i + 1) * kõrgus, fill="white", outline="white") # sinine suur ristkülik tahvel.create_rectangle(200, i * kõrgus, 600, (i + 1) * kõrgus, fill="blue", outline="blue") # tsüklimuutuja suurendamine i += 1 # paigutame tahvli raami ja teeme nähtavaks tahvel.pack() # siseneme põhitsüklisse raam.mainloop()
from tkinter import * raam = Tk() raam.title("Sillamäe lipp") # loome tahvli laiusega 880px ja kõrgusega 560px tahvel = Canvas(raam, width=880, height = 560) #sinine taust tahvel.create_rectangle(0, 0, 880, 560, fill="blue", outline="blue") # tsüklimuutuja i = 0 while i < 5: # kollane ristkülik vasakul tahvel.create_rectangle(0+i*80, 560-3*70-i*70, 2*80+i*80, 560-i*70, fill="yellow", outline="yellow") # kollane ristkülik paremal tahvel.create_rectangle(880-2*80-i*80, 560-3*70-i*70, 880-i*80, 560-i*70, fill="yellow", outline="yellow") i += 1 #tipp tahvel.create_rectangle(880-6*80, 0, 880-5*80, 70, fill="yellow", outline="yellow") # paigutame tahvli raami ja teeme nähtavaks tahvel.pack() # siseneme põhitsüklisse raam.mainloop()
Vaata ka näidislahenduse videot:
LISALUGEMINE. FUNKTSIOONI GRAAFIK
Ülesannete lahendamiseks pole seda materjali tarvis tingimata läbida.
Koostame programmi, mis joonistab mõne teie poolt valitud (matemaatilise) funktsiooni (nt y=x3) graafiku.
Kuigi Tkinter sobib hästi graafikute joonistamiseks, tekitab mõningast ebamugavust teistmoodi koordinaatide süsteem – oleme ju harjunud, et y kasvab ülespoole, mitte aga alla ja koordinaatide alguspunkt on meil ka harjumuspäraselt olnud pigem joonise keskel. Võtame abiks klassi Canvas funktsiooni move
, mis võimaldab tahvlil olevaid objekte horisontaalset ja vertikaalset telge mööda ümber tõsta. Seega paigutame kõik objektid harilikku koordinaadistikku ja siis rakendame funktsiooni move
. Peegelduse x-telje suhtes korraldame sellega, et funktsiooni väärtuste kogumisel kogume y asemel -y. Näiteprogramm püüab teha y=x graafikut:
from tkinter import * raam = Tk() # tahvli laius w = 500 # tahvli pikkus h = 500 tahvel = Canvas(raam, width=w, height=h, bg="white") # vertikaalne telg tahvel.create_line(0, h/2, 0, -h/2, arrow=LAST) # horisontaalne telg tahvel.create_line(-w/2, 0, w/2, 0, arrow=LAST) # joonistame lõigud (x1,f(x1)) - (x2,f(x2)) x1 = -w//2 while x1 < w//2: x2 = x1+1 # olgu alguseks lineaarne funktsioon y1 = x1 y2 = x2 # -y on selleks, et peegeldada x-telje suhtes tahvel.create_line(x1, -y1, x2, -y2) x1 += 1 # nihutame kõik objektid 250px võrra paremale ja alla tahvel.move(ALL, w/2, h/2) tahvel.pack() raam.mainloop()
Kopeerige ja käivitage ülalolev kood. Katsetage programmi ka teiste funktsioonidega (nt 5x, x**3, ...)
LISALUGEMINE. LIIKUVAD KUJUTISED
Ülesannete lahendamiseks pole seda materjali tarvis tingimata läbida.
Proovime joonistada osutitega kella, mis ennast aja jooksul värskendaks. Võrreldes eelmiste ülesannetega, kus tegemist oli sisuliselt staatiliste kujutistega, on meie praeguseks eesmärgiks uurida, kuidas võib muuta graafikaobjektide olekuid rakenduse töö ajal. Graafikaobjektide loomisel saab neile anda unikaalseid nimesid, mille järgi saab need hiljem tahvlil üles leida, nt
id = tahvel.create_line(x0,y0,...,xn,yn)
Kasutades nime saab näiteks objekti kustutada, nihutada või muuta tema argumente. Objektidega manipuleerimiseks kasutame klassis Canvas defineeritud meetodeid.
# kustutamine tahvel.delete(id) # nihutamine tahvel.move(id, x, y) # objekti argumentide tagastamine tahvel.itemcget(id, "width") # koordinaatide uuendamine tahvel.coords(id, x0,y0,...,xn,yn )
Antud ülesande kontekstis huvitab meid põhimõtteliselt viimane meetod, mille abil me saame osutite positsiooni uuendada. Tekitame uue raami ja tahvli. Kella keskpunkt olgu tahvli keskel.
from tkinter import * raam = Tk() raam.title("Kell") # tahvli laius w = 500 # tahvli pikkus h = 500 tahvel = Canvas(raam, width=w, height=h, bg="white") # kella raam tahvel.create_oval(10,10,w-10,h-10) # kella keskpunkt tahvel.create_oval(w/2-5,h/2-5,w/2+5,h/2+5,fill="black")
Joonistame sekundiosuti (joon) ja salvestame tema andmedmuutujasse sek_id
sek_id = tahvel.create_line(w/2,h/2,w/2,20,fill="red")
Kuna osuti üks ots on fikseeritud kella keskel, siis meid huvitavad ainult liikuva otsa koordinaadid mingil ajahetkel t. Kui on antud sekundite arv sekundid
, siis on võimalik arvutada vastavad punkti koordinaadid x ja y:
from math import * # osuti liikuva tipu koordinaadid # arvutame sekundiosuti pikkuse r = min(w/2,h/2)-20 # arvutame x koordinaadi x = r*cos(pi/2-sekundid/60.0*2*pi) # arvutame y koordinaadi y = -r*sin(pi/2-sekundid/60.0*2*pi)
Järgmise sammuna loome funktsiooni, mis loeb jooksvalt aega ja uuendab sekundiosuti positsiooni. Uus funktsioon luuakse võtmesõna def
abil, funktsiooni sisu on taandatud. Täpsemalt käsitleme seda kahe nädala pärast.
import time def uuenda(): # loeme jooksva sekundi sekundid = time.localtime().tm_sec # osuti liikuva tipu koordinaadid # arvutame sekundiosuti pikkuse r = min(w/2,h/2)-20 # arvutame x koordinaadi x = r*cos(pi/2-sekundid/60.0*2*pi) # arvutame y koordinaadi y = -r*sin(pi/2-sekundid/60.0*2*pi) # uuendame osuti positsiooni tahvel.coords(sek_id, 0, 0, x, y) # nihutame keskele tahvel.move(sek_id, w/2, h/2) # ootame 1 sekundi ja siis uuendame kellaaega uuesti raam.after(1000, uuenda)
Kutsuge funktsioon uuenda
välja enne Tkinteri põhitsüklisse sisenemist.
uuenda() tahvel.pack() raam.mainloop()
Kokkupandud kood on järgmine:
from tkinter import * from math import * import time raam = Tk() raam.title("Kell") # tahvli laius w = 500 # tahvli pikkus h = 500 tahvel = Canvas(raam, width=w, height=h, bg="white") # kella raam tahvel.create_oval(10,10,w-10,h-10) # kella keskpunkt tahvel.create_oval(w/2-5,h/2-5,w/2+5,h/2+5,fill="black") sek_id = tahvel.create_line(w/2,h/2,w/2,20,fill="red") def uuenda(): # loeme jooksva sekundi sekundid = time.localtime().tm_sec # osuti liikuva tipu koordinaadid # arvutame sekundiosuti pikkuse r = min(w/2,h/2)-20 # arvutame x koordinaadi x = r*cos(pi/2-sekundid/60.0*2*pi) # arvutame y koordinaadi y = -r*sin(pi/2-sekundid/60.0*2*pi) # uuendame osuti positsiooni tahvel.coords(sek_id, 0, 0, x, y) # nihutame keskele tahvel.move(sek_id, w/2, h/2) #ootame 1 sekundi ja siis uuendame kellaaega uuesti raam.after(1000, uuenda) uuenda() tahvel.pack() raam.mainloop()
Käivitage rakendus. Täiendage kella. Lisage minuti- ja tunniosuti, mis samuti muudaks aja jooksul oma positsiooni.
< eelmine | 4. nädala sisukord | järgmine > |