Back to site
Since 2004, our University project has become the Internet's most widespread web hosting directory. Here we like to talk a lot about web servers, web development, networking and security services. It is, after all, our expertise. To make things better we've launched this science section with the free access to educational resources and important scientific material translated to different languages.

PyQt kroz primere

PyQt kroz primere (Sesija 1)


Objavljeno: 2009-03-01 23:38

Uvod

Ova serija uputstava je inspirisana dvema stvarima:

  • Konferencijom LatinoWare 2008, gde sam predstavio ovu aplikaciju, kao uvod u PyQt programiranje.
  • Manjkom (po mom skromnom mišljenju) PyQt uputstava koja prikazuju način na koji volim da razvijam aplikacije.

Ovo drugo može da zvuči pomalo ratoborno, ali nije tako. Ne kažem da su druga uputstva pogrešna, ili loša, samo kažem da ne funkcionišu na način na koji ja volim da radim.

Lično ne verujem u pristup da se nešto objašnjava, a zatim se kaže “Sada ću vam pokazati kako se to stvarno radi”. Ne verujem u “dečije” primere. Ja verujem u to da ste dovoljno pametni da stvari učite jednom, i da odmah učite pravu stvar.

Dakle, u ovoj seriji uputstava ću razvijati malu TODO aplikaciju (“to do” – za uraditi), koristeći alate i procedure koje koristim kada stvarno programiram, osim razvojnog okruženja Eric IDE. To je zato što su razvojna okruženja stvar ličnih sklonosti, i za ovde razmatrani projekat zaista nisu puno bitna.

Jedna druga stvar, koju neću dodati, je testiranje jedinica. Iako je to veoma važno, mislim da bi odvraćalo pažnju od stvarnog rada. Ukoliko je to problem, ono se može dodati u kasnijoj verziji ovog uputstva.

Potrebno

Morate imati instalirane sledeće programe:

  • Python: Ja koristim 2.6, očekujem da će verzija 2.5, ili čak 2.4, raditi, ali ih ne proveravam.
  • Elixir: Ovo je potrebno za pozadinski mehanizam (backend). On zahteva SQLAlchemy, a mi ćemo kao našu bazu podataka koristiti SQLite. Ako instalirate Elixir, sve ostalo bi trebalo automatski da se instalira.
  • PyQt: Ja ću koristiti verziju 4.4
  • Uređivač teksta po vašem izboru

Ovo uputstvo ne podrazumeva poznavanje Elixir-a, PyQt-a, ili baza podataka, ali podrazumeva radno znanje Python-a. Ukoliko još uvek ne znate python, ovo nije još pravo uputstvo za vas.

Potpun kod za ovu sesiju možete dobiti ovde: Izvorni kodovi (kliknite na dugme “Preuzimanje”).

Pošto se ovo uputstvo u potpunosti hostuje na GitHub-u, slobodni ste da dodajete unapređenja, modifikacije, pa čak i cele nove sesije ili opcije!

Sesija 1: Osnove

Pozadinski mehanizam (backend)

Najnovija verzija ove sesije (u RST formatu) je uvek dostupna na GitHub-ovom glavnom stablu, kao tut1.txt

Pošto razvijamo TODO aplikaciju, potreban nam je pozadinski mehanizam koji obavlja skladištenje, povraćaj i generalno upravljanje TODO zadacima.

Da bih to uradio na najjednostavniji mogući način, uradiću to koristeći Elixir, “Deklarativni sloj nad SQLAlchemy objektno-relacionim maperom”.

Ako je ovo zvučalo zastrašujuće, nemojte biti zabrinuti. To znači “način kreiranja objekata koji se automatski pohranjuju u bazu podataka”.

Ovde se nalazi kod, sa komentarima, za naš pozadinski mehanizam, nazvan todo.py. Nadajmo se da nećemo morati ponovo da ga gledamo, sve do mnogo kasnijeg mesta u ovom uputstvu!

%2d  # -*- kodni raspored: utf-8 -*-
%2d 
%2d  """Jednostavan pozadinski mehanizam za TODO aplikaciju, koristeci Elixir"""
%2d 
%2d  import os
%2d  from elixir import *
%2d 
%2d  dbdir=os.path.join(os.path.expanduser("~"),".pyqtodo")
%2d  dbfile=os.path.join(dbdir,"tasks.sqlite")
%2d 
%2d  class Task(Entity):
%2d      """
%2d      Zadatak za vasu TODO listu.
%2d      """
%2d      using_options(tablename='tasks')
%2d      text = Field(Unicode,required=True)
%2d      date = Field(DateTime,default=None,required=False)
%2d      done = Field(Boolean,default=False,required=True)
%2d      tags  = ManyToMany("Tag")
%2d 
%2d      def __repr__(self):
%2d          return "Task: "+self.text
%2d 
%2d 
%2d  class Tag(Entity):
%2d      """
%2d      Oznaka koju mozemo da primenimo na zadatak.
%2d      """
%2d      using_options(tablename='tags')
%2d      name = Field(Unicode,required=True)
%2d      tasks = ManyToMany("Task")
%2d 
%2d      def __repr__(self):
%2d          return "Tag: "+self.name
%2d 
%2d 
%2d  saveData=None
%2d 
%2d  def initDB():
%2d      if not os.path.isdir(dbdir):
%2d          os.mkdir(dbdir)
%2d      metadata.bind = "sqlite:///%s"%dbfile
%2d      setup_all()
%2d      if not os.path.exists(dbfile):
%2d          create_all()
%2d 
%2d      # Ovo je kako Elixir 0.5.x i 0.6.x radi
%2d      # Da, donekle je ruzno, ali je potrebno za Debian
%2d      # i Ubuntu i druge distribucije.
%2d 
%2d      global saveData
%2d      import elixir
%2d      if elixir.__version__ < "0.6":
%2d          saveData=session.flush
%2d      else:
%2d          saveData=session.commit
%2d 
%2d 
%2d 
%2d  def main():
%2d 
%2d      # Inicijalizacija baze podataka
%2d      initDB()
%2d 
%2d      # Kreiranje dve oznake
%2d      green=Tag(name=u"green")
%2d      red=Tag(name=u"red")
%2d 
%2d      #Kreiranje novih oznaka i njihovo "kacenje"
%2d      tarea1=Task(text=u"Buy tomatos",tags=[red])
%2d      tarea2=Task(text=u"Buy chili",tags=[red])
%2d      tarea3=Task(text=u"Buy lettuce",tags=[green])
%2d      tarea4=Task(text=u"Buy strawberries",tags=[red,green])
%2d      saveData()
%2d 
%2d      print "Green Tasks:"
%2d      print green.tasks
%2d      print
%2d      print "Red Tasks:"
%2d      print red.tasks
%2d      print
%2d      print "Tasks with l:"
%2d      print [(t.id,t.text,t.done) for t in Task.query.filter(Task.text.like(ur'%l%')).all()]
%2d 
%2d  if __name__ == "__main__":
%2d      main()

Glavni prozor

Počnimo sada sa zabavnim delom: PyQt!

Preporučujem korišćenje dizajnera (softvera) za kreiranje vaših grafičkih interfejsa. Da, neki ljudi se žale na interfejs dizajnere. Ja kažem da svoje vreme treba da trošite pišući kod za one delove za koje ne postoje dobri alati.

I, ovde se nalazi Qt Designer fajl za naš glavni prozor: window.ui. Ne brinite o svom tom XML-u, samo otvorite fajl u svom dizajneru ;-)

Evo kako prozor izgleda u dizajneru:

Glavni prozor, u dizajneru.


Ono što vidite je “Glavni prozor”. Ova vrsta prozora vam daje da imate meni, trake sa alatima, statusne trake, i to je tipičan prozor za standardnu aplikaciju.

Tekst na vrhu, “Type Here” (upišite ovde), je tu jer je meni još uvek prazan, i poziva vas da dodate nešto u njega.

Velika pravougaona stvar sa natpisima “Task” (zadatak), “Date” (datum) i “Tags” (tagovi – oznake), je grafički objekat (widget), zvani QTreeView, koji je zgodan za prikazivanje stvari sa ikonama, nekoliko kolona i, možda, hijerarhijskom strukturom (otud ime “tree” – stablo). Mi ćemo ga upotrebiti za prikaz naše liste zadataka.

Možete videti kako ovaj prozor izgleda koristeći opciju “Form” -> “Preview”, u dizajneru. Ovo je ono šta ćete dobiti:

Pogled na glavni prozor, sa prikazom liste zadataka.

Možete probati da promenite veličinu prozora, i ovaj grafički objekat će upotrebiti sav raspoloživi prostor i razvući prozor unutar njega. Ovo je važno: prozori koji ne “podnose” pravilno promenu veličine izgledaju neprofesionalno i nisu odgovarajući.

U Qt-u se ovo radi korišćenjem rasporeda (layout). U ovom konkretnom slučaju, pošto imamo samo jedan grafički objekat, ono što radimo je da kliknemo na pozadinu forme i izaberemo “Layout” -> “Layout Horizontally” (Vertikalno bi ovde imalo potpuno isti efekat).

Kada budemo radili konfiguracioni okvir za dijalog, naučićemo više o rasporedima.

Sada se slobodno igrajte sa dizajnerom i ovom formom. Možete probati menjanje rasporeda, dodati nove stvari, menjati svojstva grafičkih objekata, eksperimentisati kako vam volja, učenje dizajnera je vredno uloženog truda!

Korišćenje našeg glavnog prozora

Sada ćemo ovaj prozor, koji smo napravili, učiniti delom stvarnog programa, tako da možemo početi da da ga teramo da radi.

Prvo moramo da kompajliramo naš .ui fajl u python kod. To možete uraditi sa ovom komandom:

pyuic4 window.ui -o windowUi.py

Hajde sada da pogledamo main.py, glavni fajl naše aplikacije:

%2d  # -*- kodni raspored: utf-8 -*-
%2d 
%2d  """Korisnički interfejs za vašu aplikaciju"""
%2d 
%2d  import os,sys
%2d 
%2d  # Uvoženje Qt modula
%2d  from PyQt4 import QtCore,QtGui
%2d 
%2d  # Uvoženje kompajliranog UI modula
%2d  from windowUi import Ui_MainWindow
%2d 
%2d  # Kreiranje klase za naš glavni prozor
%2d  class Main(QtGui.QMainWindow):
%2d      def __init__(self):
%2d          QtGui.QMainWindow.__init__(self)
%2d 
%2d          # Ovo je uvek isto
%2d          self.ui=Ui_MainWindow()
%2d          self.ui.setupUi(self)
%2d 
%2d  def main():
%2d      # Još jednom, ovo je opšte namene, biće isto u
%2d      # skoro svakoj aplikaciji koju napišete
%2d      app = QtGui.QApplication(sys.argv)
%2d      window=Main()
%2d      window.show()
%2d      # Ovo je exec_ jer je exec rezervisana reč u Python-u
%2d      sys.exit(app.exec_())
%2d 
%2d 
%2d  if __name__ == "__main__":
%2d      main()

Kao što možete videti, ovo uopšte nije specifično za našu TODO aplikaciju. Šta god bilo u tom .ui fajlu, radilo bi sasvim fino sa ovim!

Jedini interesantan deo je klasa Main. Ta klasa koristi kompajlirani ui fajl i tu ćemo smestiti logiku korisničkog interfejsa naše aplikacije. Nikada nemojte ručno uređivati .ui fajl, ili generisani python fajl!

Hajde da to kažem ovim rečima: AKO UREĐUJETE UI FAJL (BEZ KORIŠĆENJA DIZAJNERA), ILI GENERISANI PYTHON FAJL, RADITE TO POGREŠNO! GREŠITE! EPSKA GREŠKA!. Nadam se da je ovo doprlo do vas, jer postoji najmanje jedan vodič (uputstvo) koji vam govori da to radite. NE RADITE TO!!!,

Samo stavite vaš kod u ovu klasu i sve će biti dobro.

Tako, ako pokrenete main.py, učinićete da aplikacija radi. Ona neće raditi nšta interesantno, pošto moramo našem korisničkom interfejsu da prikačimo pozadinski mehanizam, ali to je već sesija 2.


PyQt kroz primere (Sesija 2)


Objavljeno: 2009-03-03 13:09

Potrebno

Ukoliko još niste, pogledajte prvo Sesiju 1.

Svi fajlovi za ovu sesiju nalaze se ovde: Sesija 2 na GitHub-u

Sesija 2: Priključivanje stvari

U Sesiji 1 smo napravili glavni prozor naše aplikacije, skeletnu aplikaciju koja prikazuje pomenuti prozor, i jednostavan pozadinski mehanizam, zasnovan na Elixir-u.

Nismo, međutim, povezali obe stvari. A povezivanje stvari je važan korak koji preduzimamo u ovoj sesiji.

Prvo ćemo raditi na našem main.py.

Učitavanje podataka

Prva stvar je da moramo da koristimo naš pozadinski mehanizam, pa moramo da uvezemo todo.py.

To radimo u liniji 13

Zatim, u liniji 25 radimo prvi pravi novi posao: uzimamo listu zadataka i stavljamo ih u našu listu.

Ispitajmo to u detalje. Evo koda od linija 25 do 35:

%2d # Uradimo nešto interesantno: učitavanje sadržaja baze podataka
%2d # u naš grafički objekat sa listom zadataka
%2d for task in todo.Task.query().all():
%2d     tags=','.join([t.name for t in task.tags])
%2d     item=QtGui.QTreeWidgetItem([task.text,str(task.date),tags])
%2d     item.task=task
%2d     if task.done:
%2d         item.setCheckState(0,QtCore.Qt.Checked)
%2d     else:
%2d         item.setCheckState(0,QtCore.Qt.Unchecked)
%2d     self.ui.list.addTopLevelItem(item)

Sećate se našeg pozadinskog mehanizma, todo.py? U njemu smo definisali Task (zadatak) objekte koji se smeštaju u bazu podataka. U slučaju da ne znate Elixir, todo.Task.query().all() uzima listu svih Task objekata iz baze podataka.

Zatim oznakama (tags) dodeljujemo oznake, odvojene zapetama, koje će izgledati poput “home,important”.

I sada, vidimo naš prvi Qt-vezani kod.

Prvo: self.ui.list je grafički objekat (widget). Sve što stavimo u prozor, koristeći dizajner, je dostupno korišćenjem self.ui.object_name, a object_name je podešeno korišćenjem dizajnera. Ako kliknete desnim dugmetom na naš prozor možete videti da je ime grafičkog objekta list:

Ime objekta je list.

To takođe možete videti (i promeniti ga) u Object Inspector-u i u Property Editor-u.

To nije samo neki grafički objekat. To je QTreeWidget, koristan za prikazivanje kitnjastih listi sa više kolona i stabala.

Možete pročitati dosta toga o ovom grafičkom objektu, u njegovom uputstvu, ali hajde da damo vrlo skraćenu verziju:

  • Kreiramo QTreeWidgetItem objekte. Oni uzimaju listu stringova, koji se prikazuju u kolonama grafičkog objekta. U ovom slučaju, dodajemo tekst zadatka (“Buy groceries” – kupiti namirnice), datum dospeća, i oznake.
  • Podešavamo stavku item.task na zadatak. Ovo zato da bismo kasnije znali koji zadatak je opisan određenom stavkom. Možda nije najelegantniji način, ali je daleko najlakši.
  • Svaku stavku dodajemo u grafički objekat koristeći addTopLevelItem, tako da su sve one istog nivoa (ne hijerarhijski, kao “deca” i “roditelji”)
  • Ovo će raditi kao lista

Zatim, ako je zadatak završen (task.done==True) stavljamo “kvačicu” (checkmark) pored njega. Ako nije završen, ne stavljamo je.

Za mnoge jednostavne programe, ovo je sve što treba da znate o QtreeWidget-u. To je prilično kompleksan grafički objekat, i vrlo moćan, ali ovo će biti dovoljno za naš posao. Istražujte!

Dakle, šta ovo radi? Pokrenimo python main.py i saznajmo!

Lista zadataka sa našim zadacima - primerima.

Ako je vaš prozor prazan, probajte prvo sa pokretanjem python todo.py first, to će kreirati neke zadatke - primere.

Možete čak i “čekirati” zadatak, da ga označite kao završen! To je zato što smo primenili (delimično) snimanje izmenjenih zadataka...

Snimanje podataka

Dakle, ono što želimo je da, onda kada korisnik klikne na “kućicu” (checkbox), zadatak bude izmenjen u skladu sa tim, i sačuvan u bazi podataka.

U većini alata biste tražili povratne pozive. Ovde su stvari nešto drugačije. Kada neki Qt grafički objekat želi da vas o nečemu obavesti , kao na primer “Korisnik je kliknuo na ovo dugme”, ili “Meni za pomoć (Help menu) je aktiviran”, ili šta god bilo, oni odašilju signale (na trenutak idu uporedo).

Konkretno, ovde nas interesuje itemChanged signal QtreeWidget-a:

QTreeWidget.itemChanged ( stavka, kolona ) [signal]
Ovaj signal se odašilje onda kada se sadržaj kolone u navedenoj stavki menja.

I, kad god kliknete na ”kućicu”, ovaj signal se odašilje. Zašto je to važno? Zato što možete povezati sopstveni kod za neki signal, tako da se vaš kod izvrši svaki put kada se taj signal emituje! Taj vaš kod se u Qt slengu naziva slot (utičnica).

Ovo je poput povratnih poziva, osim što:

  1. Signal “ne zna” šta je povezano sa njim (odnosno, da li je išta povezano)
  2. Za neki signal možete povezati koliko god želite “utičnica”.

Ovo pomaže da vaš kod držite slobodno povezanim.

Mogli bismo definisati metod u Main klasi, i povezati ga sa signalom itemChanged, ali nema potrebe jer možemo da upotrebimo AutoConnect. Ukoliko u Main dodate metod sa specifičnim imenom, on će biti povezan sa tim signalom. Ime je oblika on_objectname_signalname.

Ovde je kod (linije 37 do 42):

%1d def on_list_itemChanged(self,item,column):
%1d     if item.checkState(0):
%1d         item.task.done=True
%1d     else:
%1d         item.task.done=False
%1d     todo.saveData()

Vidite li kako koristimo item.task da odrazimo checkState stavke (bilo da je “čekirana”, ili ne) u stanju zadatka?

Naredba todo.saveData(), na kraju, osigurava da svi podaci budu snimljeni u bazu podataka, putem Elixir-a.

AutoConnect je ono što ćete koristiti 90% vremena da dodate ponašanje vašoj aplikaciji. Većinu vremena ćete samo praviti prozore, dodavati grafičke objekte, i kačiti signale preko AutoConnect-a.

U nekim prilikama ovo neće biti dovoljno. Ali dotle još nismo stigli.

Ovo je bila prilično kratka sesija, ali spremite se za sledeću: radićemo Važne stvari sa Dizajnerom (Important Stuff with Designer (TM))!

U međuvremenu, možda budete želeli da pogledate ove strane:


Ovde možete videti šta se promenilo između starih i novih verzija:

Izmenjene linije: 34 Generisano pomoću by diff2html
© Yves Bailly, MandrakeSoft S.A. 2001
diff2html je zaštićen licencom .
Dodata linija: 13, 14, 15, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48
Uklonjena linija: Ni jedna

session1/main.py session2/main.py
34 linije
754 bajta
Poslednja izmena: Pon 2. mart 2009. 01:29:24
60 linija
1557 bajtova
Poslednja izmena: Čet 5. mart 2009. 02:03:03


1
# -*- kodni raspored: utf-8 -*-
1
# -*- kodni raspored: utf-8 -*-
2
2
3
"""Korisnički interfejs za našu aplikaciju"""
3
"""Korisnički interfejs za našu aplikaciju"""
4
4
5
import os,sys
5
import os,sys
6
6
7
# Uvoženje Qt modula
7
# Uvoženje Qt modula
8
from PyQt4 import QtCore,QtGui
8
from PyQt4 import QtCore,QtGui
9
9
10
# Uvoženje kompajliranog UI modula
10
# Uvoženje kompajliranog UI modula
11
from windowUi import Ui_MainWindow
11
from windowUi import Ui_MainWindow
12
12
13
# Uvoženje našeg pozadinskog mehanizma
14
import todo
15
13
# Kreiranje klase za naš glavni prozor
16
# Kreiranje klase za naš glavni prozor
14
class Main(QtGui.QMainWindow):
17
class Main(QtGui.QMainWindow):
15
    def __init__(self):
18
    def __init__(self):
16
        QtGui.QMainWindow.__init__(self)
19
        QtGui.QMainWindow.__init__(self)
17
20
18
        # Ovo je uvek isto
21
        # Ovo je uvek isto
19
         self.ui=Ui_MainWindow()
22
         self.ui=Ui_MainWindow()
20
         self.ui.setupUi(self)
23
         self.ui.setupUi(self)
21
24
25
        # Uradimo nešto interesantno: učitavanje sadržaja baze podataka
26
        # u naš grafički objekat sa listom zadataka
27
        for task in todo.Task.query().all():
28
            tags=','.join([t.name for t in task.tags])
29
            item=QtGui.QTreeWidgetItem([task.text,str(task.date),tags])
30
            item.task=task
31
            if task.done:
32
                item.setCheckState(0,QtCore.Qt.Checked)
33
            else:
34
                item.setCheckState(0,QtCore.Qt.Unchecked)
35
            self.ui.list.addTopLevelItem(item)
36
37
    def on_list_itemChanged(self,item,column):
38
        if item.checkState(0):
39
            item.task.done=True
40
        else:
41
            item.task.done=False
42
        todo.saveData()
43
44
22
def main():
45
def main():
46
    # Inicijalizacija baze podataka, pre rađenja bilo čega drugog
47
    todo.initDB()
48
23
    # Još jednom, ovo je opšte namene, biće isto u
49
    # Još jednom, ovo je opšte namene, biće isto u
24
    # skoro svakoj aplikaciji koju napišete
50
    # skoro svakoj aplikaciji koju napišete
25
    app = QtGui.QApplication(sys.argv)
51
    app = QtGui.QApplication(sys.argv)
26
    window=Main()
52
    window=Main()
27
    window.show()
53
    window.show()
28
    # Ovo je exec_ jer je exec rezervisana reč u Python-u
54
    # Ovo je exec_ jer je exec rezervisana reč u Python-u
29
    sys.exit(app.exec_())
55
    sys.exit(app.exec_())
30
56
31
57
32
if __name__ == "__main__":
58
if __name__ == "__main__":
33
    main()
59
    main()
34
60

Generisano pomoću diff2html-a, u petak 6. marta 2009. u 00:58:30
Komandna linija:
/home/ralsina/bin/diff2html session1/main.py session2/main.py





Published (Last edited): 10-03-2013 , source: http://lateral.netmanagers.com.ar/stories/BBS47.html