Knjižnice za sestavljanje uporabniških vmesnikov

Na zadnjih predavanjih se bomo (čas je že!) naučili delati programe, ki izgledajo, kot izgledajo običajni programi - z okenčki, gumbi... Python tega sam po sebi ne zna. Za tole potrebujemo kako primerno knjižnico. Obstaja jih več; pri tem predmetu bomo uporabili Qt. Izbral sem ga, ker je najresnejša tovrstna knjižnica, ki je popolnoma prenosljiva na različne operacijske sisteme in jo lahko uporabljamo iz različnih jezikov. (Poleg tega pa ga, priznam, sam dobro poznam, ker ga vsakodnevno uporabljam). Zavedajte pa se, da bomo na predavanjih ponovno videli le gole osnove. Če se hočete teh reči zares naučiti, ga morate tudi zares uporabljati. Leta.

Qt je zaresna reč. Na njem temelji znaten del Linuxov (konkretno, vsi, ki uporabljajo namizje KDE). Je ogromen: ima kakih 800 razredov, ki vsebujejo vse mogoče, vključno s predvajanjem zvoka, delom s podatkovnimi bazami, spletnim brskalnikom (vključno z JavaScriptom)... Čeprav je napisan v C++, ga je mogoče uporabljati v vseh mogočih jezikih; kar se boste naučili na Windowsih v Pythonu boste lahko uporabljali na Linuxu v ... Rubyju, če hočete.

Top

Vsi spodobni pisatelji pripeljejo zgodbe v krog, tako da se na nek način začnejo tam, kjer so se končale. Tudi mi storimo tako. Programiranje 1 smo začeli - spomnimo se - s programom, ki smo ga napisali za slovensko vojsko (vi od tega sicer niste dobili nič, ves denar sem pobasal jaz).

Vojska ga navdušeno uporablja. V zadnji treh mesecih se ni zgodilo, da bi slovenski top zgrešil cilj. Po drugi strani pa se je general Novak pritožil - in pravzaprav mu ne moremo oporekati - da uporabniški vmesnik našega program ni ravno vrhunec tehnologije. Po domače: nobenega napredka ne kaže v primerjavi s časom črno-belih, točneje, zeleno-črnih monitorjev. Postavili so nam ultimat: hočejo spodoben program. In kjer vojska postavi ultimat, lahko le salutiramo.

Uporabniški vmesnik narišemo s programom QtDesigner, ki ste si ga namestili skupaj s PyQtjem (upam, da to tudi na Windowsih deluje enako -- če ne, pa ga poberite (najbrž tule in namestite posebej). Kako rišemo vmesnik, tule ne bomo opisovali, saj na nivoju, do katerega se bomo spustili tule, to ni nič bolj zapleteno kot pisanje dokumenta v Wordu.

Uporabniški vmesnik je sestavljen iz dialogov/oken, v katere zlagamo komponente. Komponentam se v Qtju (in tudi še kje drugje) pravi widget, kar pogosto prevajamo kot gradnik, včasih jim bomo rekli tudi kontrole. Naj vas terminološka nekonsistentnost ne bega: različne besede so plod nejasnosti prevodov in moje šlamparije, ne kakšne resnične razlike.

V primeru na sliki imamo dialog (Top), v katerega smo postavili dve škatli (groupBox in groupBox_2) in jima nadeli naslova \"Podatki o strelu\" in \"Izračun\". Škatle v tem in večini drugih primerov nimajo druge vloge, kot da \"grupirajo\" kontrole v dialogu.

Znotraj prve škatle je pet reči: dve labeli (label, label_2), eno vidimo kot \"Hitrost\", drugo kot \"Kot\"; dva line edita (leHitrost in leKot) in tipka \"Izračunaj\" (btIzracun). (V pomanjkanju jasnih prevodov bomo v izogib dvoumnosti govorili o \"line editih\" in \"combo boxih\". Tako pač je, vseeno smo tehniki, ne slavisti.)

Druga škatla vsebuje labelo (lbIzracun).

Nekatere kontrole imajo imena, kot jim jih je določil Qt, kot na primer label ali groupBox_2. Drugim smo imena ročno spremenili v kaj bolj razumljivega, na primer leHitrost. Pri tem bomo (ne povsem konsistentno) uporabljali madžarsko notacijo; imena line editov bomo začenjali z le, tipke bodo bt in tako naprej. Madžarska notacija je sicer iz mode, pri sestavljanju uporabniških vmesnikov pa nam bo prišla prav, ker bomo z njo razlikovali med kopico podobnih imen,na primer btIzracun in lbIzracun.

Dialog, ki smo ga sestavili v Qt Designerju, shranimo, recimo v datoteko \"top.ui\". Ta vsebuje opis dialoga v obliki, ki ga znajo prebrati skoraj vsi jeziki, ki podpirajo PyQt, med njimi je seveda tudi Python. (Kogar zanima, kako je datoteka videti, naj jo odpre v poljubnem urejevalniku ali pa jo potegne kar v spletni brskalnik, pa bo morda prijetno presenečen nad elegantnostjo rešitve - čeprav navsezadnje ni nič posebnega, vsaj dandanes bi človek nekaj takšnega itak pričakoval.)

Zdaj pa pride še programiranje. Presenetljivo malo ga je; večino časa bomo preživeli ob brskanju po dokumentaciji. Najprej poglejmo, kaj lahko storimo z datoteko .ui. Očitno jo bomo naložili; to storimo s funkcijo PyQt5.uic.loadUi.

>>> from PyQt5 import QtWidgets, uic
>>> app = QtWidgets.QApplication([])
>>> dlg = uic.loadUi("top.ui")
>>> dlg
<PyQt5.QtWidgets.QDialog object at 0x04546588>

Najprej smo uvozili potrebne module (po to in ono pa bomo kasneje skočili še v modul QtCore).

V naslednji vrstici, app = QtWidgets.QApplication([]) inicializiramo Qtjevo aplikacijo. Kaj vse se zgodi ob tem, nas ne zanima (in tudi jaz ne vem, samo predstavljam si). To je pač nekaj, kar moramo narediti, da Qt deluje. Če tega ne zgodimo takoj v začetku, bo Python v najboljšem primeru javil napako, verjetneje pa se bo usul, kakor se programi usujejo na najgrši možen način - zaprl se bo, po možnosti s kakšnim grdim oknom, v katerem bo uporabniku ponujal, da nas za tole prijavi Microsoftu.

Nato, končno, uvozimo dialog. Rezultat, shranili smo ga v dlg, je objekt, ki predstavlja dialog, ki smo ga prejle narisali. Njegovi atributi so različne kontrole, njihova imena so enaka imenom, ki smo jim jih dali (ali pa jih pustili nespremenjene) ob sestavljanju dialoga.

>>> dlg.label_2
<PyQt5.QtWidgets.QLabel object at 0x04546930>
>>> dlg.btIzracun
<PyQt5.QtWidgets.QPushButton object at 0x045469C0>
>>> dlg.leHitrost
<PyQt5.QtWidgets.QLineEdit object at 0x04546660>
>>> dlg.lbIzracun
<PyQt5.QtWidgets.QLabel object at 0x04546A98>

In kaj je tale dlg.lbIzracun? Objekt, ki predstavlja labelo, seveda. Ta ima različne metode, povezane z labelami. Labele vsebujejo besedilo, torej imam, očitno, metodo, s katero lahko to besedilo preberemo ali pa spremenimo.

>>> dlg.lbIzracun.text()
'Ni podatkov.'

Natančno to, kar smo napisali vanjo. Preden jo spremenimo, pa pokažimo dialog, da bomo kaj videli.

>>> dlg.show()

Zdaj vidimo dialog (morda se je pokazal v ozadju trenutnega okna, tako da ga moramo poiskati med odprtimi okni. Tudi proti temu se da pomagati, a za zdaj pustimo.

Zdaj spremenimo vsebino labele.

>>> dlg.lbIzracun.setText("Benjamin")

Od okolja, v katerem reč poskušamo, je odvisno, ali bo sprememba (takoj) vidna ali ne. V ukazni vrstici to navadno deluje. Karkoli torej počnemo z dlg.lbIzracun - mu spreminjamo besedilo, velikost, pisavo, barvo ... -, se dogaja z ustrezno labelo v dialogu.

Na podoben način se lahko igramo z, recimo, line editom. Tokrat storimo obratno: v dialogu v vrstico vtipkajmo ana in potem to preberimo.

>>> dlg.leHitrost.text()
'ana'

Seveda lahko tudi vsebino line edita spreminjamo (setText), jo pobrišemo (clear) ...

Odtod je načrt relativno jasen: ko uporabnik pritisne tipko, je potrebno iz line editov prebrati hitrost in kot, izračunati razdaljo in jo vstaviti v labelo, kjer Benjamin, ki se je vanjo naselil zdaj, roko na srce, nima kaj iskati.

In tule je najpomembnejše sporočilo današnjih predavanj: "ko uporabnik pritisne tipko". Kako naj dosežemo, da bomo izvedeli, da je uporabnik pritisnil tipko? Hm, naredimo zanko v slogu while uporabnik-še-ni-pritisnil-tipke: čakaj, ki se vrti, vse dokler se uporabnik ne zmiga. Da ta ideja ni preveč dobra, hitro vidimo: v resnih programih lahko uporabnik naredi sto in eno reč. Bomo program napisali tako, da bo preverjal, ali je uporabnik naredil katero od teh sto in enih reči?

Osnovni princip, ki se ga moramo navaditi pri programiranju "pravih" programov, torej takšnih, ki imajo uporabniške vmesnike, je ta, da program ničesar ne počne samoiniciativno, temveč se le odziva na zahteve. Program v začetku samo nastavi, vse, kar je potrebno nastaviti, prebere podatke, pokaže dialoge, menuje, okna ... potem pa reče okolju (v našem primeru Qt-ju), naj mu pove, ko bo uporabnik naredil kaj zanimivega.

Qtjevi objekti nimajo le metod, temveč tudi nekaj, čemur rečemo signali. (In še marsikaj, recimo nekaj, čemur rečemo sloti, dogodki ... in vse to bi nam lahko prišlo prav, vendar bomo tokrat spoznali le mehanizem, ki nam ga dajejo signali.) Objekti tipa QPushButton imajo med drugim signal clicked, ki se sproži, ko uporabnik klikne tipko.

>>> dlg.btIzracun.clicked
<bound signal clicked of QPushButton object at 0x1106ba048>

Na ta signal bomo - za zdaj, da vidimo, kako reč deluje - priključili funkcijo, ki bo ob pritisku na tipko kaj izpisala.

>>> def f():
...     print("Pust me pr mer!")
>>> dlg.btIzracun.clicked.connect(f)

Pozorno poglejmo: dlg je dialog, dlg.btIzracun je QPushButton znotraj tega dialoga, dlg.btIzracun.clicked je signal QPushButton-a znotraj tega dialoga in dlg.btIzracun.clicked.connect je metoda, s katero lahko na ta signal povežemo funkcijo (na isti signal lahko povežemo poljubno število funkcij).

Zdaj, ko smo to naredili, se vsakič, ko pritisnemo tipko Izračunaj, izpiše, da smo jo pritisnili.

Zdaj pa res lahko sestavimo celoten program.

import math
from PyQt5 import QtWidgets, uic

def izracun():
    hitrost = float(dlg.leHitrost.text())
    kot = float(dlg.leKot.text())
    razdalja = hitrost ** 2 * math.sin(2 * math.radians(kot)) / 9.81
    dlg.lbIzracun.setText(f"Krogla bo preletela {razdalja:.2f}")

app = QtWidgets.QApplication([])
dlg = uic.loadUi("top.ui")
dlg.btIzracun.clicked.connect(izracun)
dlg.show()
app.exec()

Nova je le še zadnja vrstica: app.exec(). Z njo Qt-ju povemo, da smo naredili vse, kar smo nameravali in naj ples odslej vodi on.

Bi lahko bilo preprosteje?

Mimogrede: risar.stoj() ni naredil drugega, kot poklical exec(). Ker predtem nismo povezali nobenih signalov, smo imeli vtis, da risar.stoj() program ustavi, v resnici pa tam preži na sistemske dogodke - kot je recimo ta, da uporabnik zapre okno in s tem ustavi program.

Pretvarjanje valut

Lotimo se nekoliko težje naloge: pretvarjali bomo iz evrov v druge valute. Program bo izgledal, kot kaže slika. Preprost je: na levi je line edit, v katerega vpišemo znesek v evrih, skrajno desno je combo box, s katerim izbiramo valute. Vmes sta ne ena, temveč dve labeli: EUR = je fiksna, v drugi pa je znesek v evrih, pretvorjen v drugo valuto.

Naloga ni težja od topa (samo na pogled, samo na pogled!) zato, ker bi bilo računanje težje. Z vidika programiranja se bomo morali malo potruditi, da pridobimo svežo tečajno listo. Z vidika uporabniškega vmesnika pa bo novo to, da ne bo več tipke "Izračunaj", temveč bomo drugo labelo, preračunani znesek, spremenili vsakič, ko uporabnik spreminja znesek v evrih ali pa spremeni valuto.

Risanja je manj kot prej. Line edit, labelo in combo box poimenujemo leEvro, lbPretvorjeni in cbValuta.

Zdaj pa program. Najprej poglejmo, kako preberemo tečajno listo.

import urllib.request

tecaji = {}
socket = urllib.request.urlopen("http://www.nlb.si/?a=tecajnica&type=individuals&format=txt")
for line in socket:
    line = line.decode("ascii")
    if line.startswith("001"):
        podatki = line.split()
        valuta = podatki[5]
        tecaj = float(podatki[6].replace(",", "."))
        tecaji[valuta] = tecaj

Po tečajno listo se odpravimo na NLB, na stran http://www.nlb.si/?a=tecajnica&type=individuals&format=txt. Sicer bi bila primernejša Banka Slovenije ( http://www.bsi.si/_data/tecajnice/dtecbs.xml), a nam bo preprosteje brati NLBjeve (ko boste (še bolj) veliki in pametni, boste videli, da je preprosteje brati one iz Banke Slovenije, vendar tega še ne znamo).

Podatki na strani NLB so namreč takšni:

Num Date____ Bank Type_______ NCu CCu Buy___________ Sell__________
001 20120102 NLB_ individuals 840 USD 0001,317100000 0001,270600000
001 20120102 NLB_ individuals 826 GBP 0000,851400000 0000,821300000
001 20120102 NLB_ individuals 756 CHF 0001,238800000 0001,194900000
001 20120102 NLB_ individuals 191 HRK 0007,668500000 0007,397400000

in tako naprej.

Modul urllib.request je namenjen branju podatkov s spleta. Omogoča marsikaj, tudi dostop pred varnega protokola https, vzdrževanje sej, piškotke in podobno. Tule se bomo zadovoljili s funkcijo urlopen, ki ji kot argument podamo URL strani, ki bi jo radi prebrali. Kar vrne, je objekt, ki se obnaša tako kot datoteka! Praktično, ne? Če znamo delati z datotekami, znamo tudi brati spletne strani. Manjša nerodnost je, da ne gre za besedilno datoteko temveč binarno, torej ne bomo dobivali nizov temveč bajte. Razlog je v tem, da urlopen ne more vedeti, v katerem standardu (ASCII, utf-8, cp1250?) je sestavljena stran, ki jo beremo. Pač pa to v tem primeru vemo mi: gre za ASCII, zato bomo vsako vrstico sproti spremenili iz bajtov v niz z line = line.decode("ascii").

Stran beremo po vrsticah in če se začne z "001", razbijemo vrstico glede na presledke. V petem stolpcu je ime valute in v šestem nakupna cena (denimo, da bomo vzeli kar to). Ceno moramo spremeniti v float, za kar spet pokličemo funkcijo float, le vejico spremenimo v piko (NLB daje tudi podatke, namenjene računalniku, v obliki, ki je pravopisno pravilna, ne pa računalniško berljiva). Prebrano zlagamo v slovar tecaji, katerega ključi so imena valut, vrednosti pa nakupni tečaji.

Zdaj pa še pravo delo. Glavno delo bo opravljala funkcija pretvori, ki bo prebrala znesek v evrih in izbrano valuto, preračunala znesek in ustrezno spremenila labelo. Kako beremo line edit, že vemo: znesek nas čaka v dlg.leEvro.text(). Trenutno izbrano besedilo iz combo boxa dobimo z njegovo metodo currentText. Iz slovarja tecaji preberemo tečaj izbrane valute in, kot prav tako znamo že od prej, spremenimo besedilo labele.

from PyQt5 import QtWidgets, uic

def pretvori(s):
    evrov = float(dlg.leEvro.text())
    valuta = dlg.cbValuta.currentText()
    tecaj = tecaji[valuta]
    dlg.lbPretvorjeni.setText(f"{evrov * tecaj:.2f}")

app = QtWidgets.QApplication([])
dlg = uic.loadUi("valute.ui")
dlg.cbValuta.addItems(list(tecaji.keys()))
dlg.leEvro.textChanged.connect(pretvori)
dlg.cbValuta.currentIndexChanged.connect(pretvori)
dlg.show()
app.exec()

Funkciji sledi še inicializacija. Najprej naložimo dialog. Nato napolnimo combo box, kar storimo tako, da pokličemo metodo addItems in ji kot argument podamo ključe slovarja s tečaji. (Metoda addItems pričakuje seznam; ker keys() ne vrača seznama, tisto, kar vrne, pretvorimo v seznam, tako da pokličemo seznamov konstruktor list.

Sledi povezovanje signalov. QLineEdit ima signal textChanged, ki se sproži vsakič, ko uporabnik spremeni vsebino line edita, QComboBox pa currentIndexChanged, ki se sproži, ko uporabnik zamenja valuto. Oba povežemo tako, da pokličeta funkcijo pretvori.

Tu moramo popaziti še na en detajl: signala imata tudi argument. Line editov signal pove, kakšno je novo besedilo in combo boxov, kakšen novi izbor. Funkcija pretvori bo to dobila kot argument, poimenovali smo ga s. Ker pa je njegova vrednost tule odvisna od tega, kdo funkcijo pokliče, si z njim ne moremo dosti pomagati, zato ga ignoriramo. (Navadno ni tako: navadno so argumenti, ki jih dobimo od signalov, uporabni.)

Isto, malo lepše

Kot vidimo, glavni trik programiranja s Qtjem ni v golem programerskem znanju; tega imate tisti, ki pridno sledite predmetu, že kar nekaj, le še kilometrine vam manjka. Za uspešno programiranje uporabniških vmesnikov moramo

  • poznati Qt (ali, če bi delali s kako drugo knjižnico, pač ono, drugo knjižnico) in, predvsem, vedeti, kje najti, česar ne vemo,
  • znati dobro organizirati podatke v programu.

Knjižnice pridejo in gredo, učimo se jih vedno znova (no, Qt je precej starejši od vas, delati so ga začeli leta 1991 in izdali 1995; preživel je to, da ga je kupila Nokia, da je Nokia prodala dušo Microsoftu, prepustila Qt Digiji..., vendar mu še vedno gre odlično). Organizacija programa pa je nekaj, kar je večno, neodvisno od tega, v katerem okolju in jeziku delate in istočasno nekaj, česar se boste učili in v čemer se boste izpopolnjevali vse življenje.

Dober način organizacije, ki vam je moral postati pri Programiranju 1 vsaj malo domač, je, da zapakiramo stvari v razrede. Brez njih bodo naši programi špagetasta skrpucala. Ne le zaradi organizacije; objekte se splača uporabljati tudi in predvsem zato, ker programi običajno nimajo le enega dialoga, temveč desetine ali stotine, zato je prav, da so vse funkcije, ki se nanašajo na posamezni dialog, zbrane v enem razredu ali modulu.

Prestavimo top. Tule je najprej koda, ki smo jo napisali zgoraj

import math
from PyQt5 import QtWidgets, uic

def izracun():
    hitrost = float(dlg.leHitrost.text())
    kot = float(dlg.leKot.text())
    razdalja = hitrost ** 2 * math.sin(2 * math.radians(kot)) / 9.81
    dlg.lbIzracun.setText(f"Krogla bo preletela {razdalja:.2f} metrov")

app = QtWidgets.QApplication([])
dlg = uic.loadUi("top.ui")
dlg.btIzracun.clicked.connect(izracun)
dlg.show()
app.exec()

Zdaj pa ista reč malo bolj objektno.

import math
from PyQt5 import QtWidgets, uic

class Top:
   def __init__(self):
      self.dlg = uic.loadUi("top.ui")
      self.dlg.btIzracun.clicked.connect(self.izracun)
      self.dlg.show()

   def izracun(self):
      hitrost = float(self.dlg.leHitrost.text())
      kot = float(self.dlg.leKot.text())
      razdalja = hitrost ** 2 * \
         math.sin(2 * math.radians(kot)) / 9.81
      self.dlg.lbIzracun.setText("Krogla bo preletela {razdalja:.2f} m")

app = QtWidgets.QApplication([])
top = Top()
app.exec()

Nič dramatičnega: dlg je postal self.dlg, izracun pa self.izracun. Branje dialoga in povezovanje signalov je šlo v konstruktor, sestavljanje aplikacije pa smo pustili zunaj. (Zakaj? Predstavljajte si, da bi imeli več dialogov, ne le topovskega. Aplikacijo je vseeno potrebno inicializirati le enkrat, ne v vsakem dialogu posebej!)

Odštevalnik

Tale mali uvod v uporabniške vmesnike končajmo s primerom, ki nam bo še malo odprl vrata v svet. Spoznali bomo še nekaj Qtjevih kontrol in par pomožnih zadev.

Naredili bomo odštevalnik, ki te spomni, da je jajce kuhano ali čaj pripravljen. Kontroli na vrhu sta spin boxa. Prvi kaže čas v minutah, drugi v sekundah. Da izgleda, kot mora, smo jima precej povečali pisavo. Spodaj sta dva stara znanca, tipki. Njihova imena so sbMinute, sbSekunde, btZvok in btZacni. S spin boxoma nastavimo čas. Ko pritisnemo Začni, se začne odštevanje in ko pride do 0, zaigra zvok, ki ga poprej izberemo z \"Izberi zvok\".

from PyQt5 import QtCore, QtWidgets, QtMultimedia, uic

class Odstevalnik:
    def __init__(self):
        self.sound = QtMultimedia.QSound("ringing.wav")

        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.odstejSekundo)

        self.dlg = uic.loadUi("odstevalnik.ui")
        self.dlg.btZacni.clicked.connect(self.zacni)
        self.dlg.btZvok.clicked.connect(self.izberiZvok)
        self.dlg.show()

    def odstejSekundo(self):
        sec = self.dlg.sbSekunde.value()
        if sec > 0:
            self.dlg.sbSekunde.setValue(sec - 1)
        else:
            min = self.dlg.sbMinute.value()
            if min == 0:
                self.timer.stop()
                self.sound.play()
            else:
                self.dlg.sbMinute.setValue(min - 1)
                self.dlg.sbSekunde.setValue(59)

    def izberiZvok(self):
        fn = QtWidgets.QFileDialog.getOpenFileName(
            None, "Izberi zvocno datoteko", ".",
            "Zvocne datoteke (*.wav)")
        if fn:
            self.sound = QtMultimedia.QSound(fn)

    def zacni(self):
        self.timer.start(1000)

app = QtWidgets.QApplication([])
odstevalnik = Odstevalnik()
app.exec()

Vse se bo sukalo okrog objekta tipa QTimer. Uporabljati ga je mogoče na različne načine. Lahko mu, na primer, rečemo, naj čez toliko in toliko časa pokliče določeno funkcijo (točneje, čez toliko in toliko časa sproži signal, na ta signal pa pripnemo svojo funkcijo). V našem primeru mu bomo rekli, naj vsako sekundo (oziroma vsakih 1000 milisekund) pokliče funkcijo self.odstejSekundo. V __init__u zato sestavimo QTimer in povežemo njegov signal timeout() s funkcijo self.odstejSekundo. Ostala opravila konstruktorja so nam že znana: naložimo dialog, povežemo signale tipk, pokažemo dialog. Mimogrede nastavimo še privzeti zvok: objekt tipa QSound, ki mu kot argument podamo ime datoteke z zvokom.

Ob pritisku na tipko "Začni" se pokliče funkcija zacni, ki proži self.timer in mu naroči, naj sproža signal vsakih 1000 milisekund, self.timer.start(1000). Funkcija odstejSekundo uporablja dve metodi spin boxov, value(), ki vrne trenutno vrednost, in setValue(i), ki jo nastavi. Kar mora funkcija početi, je dolgovezno, a preprosto: prebere stanje sekundnega števca. Če še ni na ničli, ga nastavi na vrednost, manjšo za 1. Če je, prebere minute. Če so tudi te 0, je čas za godbo: ustavi timer in sproži zvok (pokliče self.sound.play() - spet ena reč, ki jo najdemo v dokumentaciji). Če še niso na ničli, pa zmanjša minutni števec in postavi sekundnega na 59.

Preostane nam še metoda izberiZvok, s katero uporabnik izbere datoteko .wav, ki jo želi poslušati od dopolnitvi časov. Tu pokličemo funkcijo QtWidgets.QFileDialog.getOpenFileName, ki odpre običajni dialog za izbor datoteke; na MS Windows bo izgledal tako kot na MS Windows, na Macu bo izgledal kot na Macu in v Linuxu kot v Linuxu. Kot rezultat vrne bodisi ime izbrane datoteke, bodisi prazen niz, če je uporabnik preklical izbor. Če vrnjeni niz ni prazen, nastavimo self.sound. Sicer self.sound ostane, kot je bil.

Tale odštevalnik zna predvajati le datoteke .wav. Če želimo .mp3 (ali .m4a ali karkoli) namesto QSound uporabimo malenkost bolj zapleteni QMediaPlayer. V konstruktorju spremenimo nastavljanje zvoka v

self.zvok = QtMultimedia.QMediaPlayer()
self.zvok.setMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile("ringing.wav")))

Funkcija izberiZvok se spremeni v

fn = QtWidgets.QFileDialog.getOpenFileName(
    None, "Izberi zvočno datoteko", ".",
    "Zvočne datoteke (*.mp3 *.wav *.m4a)"
)
self.zvok.setMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(fn)))

Pred self.zvok.play() pa dodamo self.zvok.setVolume(100), da se bo bolj slišalo.

Naprej

Vse to je bilo samo za okus. Več nam čas ne dopušča. Enako preprosto kot vse to (a seveda s še več iskanja po dokumentaciji) lahko delamo klasične aplikacije z menuji, več hkrati odprtimi dokumenti, grafiko ... in seveda desetinami najrazličnejših dialogov in funkcij ... približno vse, kar ste kdaj videli na računalniku, razen grafično najbolj intenzivnih aplikacij, iger, je mogoče narediti tudi v Qtju.

Ko smo pri igricah omenimo PyGame. To je modul za sestavljanje igric. Ni kaka garažna šara ali modul, namenjen študentom prvega letnika VSŠ, tako kot risar. Temelji na povsem zaresni knjižnici za C, SDL (Simple DirectMedia Layer) in omogoča pisanje čisto zaresnih iger s čisto zaresno grafiko.

Na predavanjih tudi še nismo čisto čisto prav uvozili dialogov, ki jih pripravimo s Qt Designerjem. Običajno jih prevedemo v modul s programom pyuic5. O vsem tem vam ne bo težko poiskati informacij na spletu.

Qt Designer tudi ni edini način priprave dialogov v Qtju: sam ga v resnici ne uporabljam, temveč vse dialoge programiram, pri čemer si pomagam z orodji, ki smo si jih pripravili sami, v našem laboratoriju. Kot primer si lahko ogledate naš s Qt-jem napisan odprtokodni program Orange.

Last modified: Tuesday, 15 January 2019, 6:58 PM