Języki skryptowe Python
Wykład 9 Graficzne interfejsy użytkownika
Janusz Szwabiński
Plan wykładu:
Kilka uwag na temat projektowania GUI Przegląd bibliotek GUI Pierwsze kroki w Tkinter
Materiały do wykładu:
http://tkinter.unpythonic.net/wiki/ (http://tkinter.unpythonic.net/wiki/) https://wiki.python.org/moin/GuiProgramming (https://wiki.python.org/moin/GuiProgramming) Mark Roseman, Modern Tkinter for Busy Python Developers Mark Summerfield, Rapid GUI Programming with Python and Qt
Kilka uwag na temat projektowania GUI
Imagine users as very intelligent but very busy (Alan Cooper, About Face 2.0)
No matter how cool your interface is, less of it would be better (j.w.)
Im większy jest obiekt na ekranie i im bliżej kursora myszki się znajduje, tym łatwiej w niego kliknąć (prawo Fitta)
dobry interfejs graficzny czyni program łatwym i intuicyjnym w obsłudze GUI decyduje o popularności programu tandetny program z bardzo dobrym GUI często będzie bardziej popularny niż nawet najlepszy program bez dobrego interfejsu użytkownika elementy na brzegach ekranu wydają się większe
Po pierwsze nie przeszkadzać! In [1]: from IPython.display import Image Image(filename='images/konqueror.png')
Out[1]:
Po drugie elementy łatwe do znalezienia!
In [2]: Image(filename='images/bad-functionaloverload1.jpg')
Out[2]:
Po trzecie konwencje są dobre! In [3]: Image(filename='images/ui_conventions.png')
Out[3]:
Po czwarte poziomy projektowania
Każdy interfejs projektujemy na dwóch poziomach:
szata graficzna układ elementów na ekranie funkcjonalność zdarzenia i ich obsługa
Podstawowymi klockami do budowy graficznej części interfejsu są kontrolki (ang. widgets):
przyciski paski przewijania teksty pola tekstowe ...
Natomiast funkcjonalność realizuje się poprzez funkcje zwrotne (ang. callback) przypisane zdarzeniom.
Po piąte papier ciągle żywy In [4]: #źródło: http://www.mobiloud.com/blog/2013/01/build-app-with-no-prog ramming/ Image(filename='images/ui_paper_design.png')
Out[4]:
Przegląd bibliotek GUI Nazwa Opis URL
najpopularniejszy zestaw narzędzi do tworzenia GUI, http://tkinter.unpythonic.net/wiki/ Tkinter dołączany do każdej dystrybucji (http://tkinter.unpythonic.net/wiki/) Pythona
bardzo popularna nakładka na WxPython http://www.wxpython.org/ (http://www.wxpython.org/) bibliotekę WxWidgets
PyQt nakładka na bibliotekę Qt http://pyqt.sourceforge.net/ (http://pyqt.sourceforge.net/)
nakładka na biblioteki https://wiki.gnome.org/action/show/Projects/PyGObject PyGObject GLib/GObject/GIO/GTK+ (https://wiki.gnome.org/action/show/Projects/PyGObject)
PyFLTK nakładka na bibliotekę FLTK http://pyfltk.sourceforge.net/ (http://pyfltk.sourceforge.net/)
pythonowa warstwa abstrakcji http://www.cosc.canterbury.ac.nz/greg.ewing/python_gui/ PyGUI wykorzystująca systemowe (http://www.cosc.canterbury.ac.nz/greg.ewing/python_gui/) biblioteki GUI
Jython dostęp do biblioteki Swing http://www.jython.org/ (http://www.jython.org/)
platforma stanowiąca część Sugar http://wiki.sugarlabs.org/ (http://wiki.sugarlabs.org/) projektu OLPC
Więcej pod adresem https://wiki.python.org/moin/GuiProgramming (https://wiki.python.org/moin/GuiProgramming)
Pierwsze kroki w Tkinter
zestaw narzędzi do tworzenia GUI element standardowej biblioteki Pythona (https://docs.python.org/2/library/tkinter.html (https://docs.python.org/2/library/tkinter.html)) interfejs do Tk GUI Toolkit łatwy w użyciu przenośny
Pierwsze okno w Tkinter
In [2]: from Tkinter import * # otwieramy główne okno aplikacji root = Tk() #uruchamiamy pętlę zdarzeń root.mainloop()
Trochę kosmetyki: In [3]: from Tkinter import * root = Tk() # tytuł okna root.title(u'Witaj świecie') # rozmiar okna root.geometry('250x200+200+200') root.mainloop ()
Najważniejsze kontrolki
Nazwa Opis
Button prosty przycisk używany do wykonania jakiegoś polecenia lub innej operacji
Canvas "płótno", kontrolka umożliwiająca rysowanie obiektów
CheckButton reprezentuje zmienną mogącą przyjmować jedną z dwóch wartości
Entry pole do wprowadzania tekstu
Frame kontrolka używana do grupowania innych kontrolek
Label kontrolka umożliwiająca wyświetlenie tekstu lub obrazu
Listbox lista elementów do wyboru
Menu panel menu
MenuButton wpis w menu
Message kontrolka do wyświetlania tekstu (zawijanie)
kontrolka wyboru, występuje w grupach odpowiadających różnym wartościom tej samej RadioButton zmiennej
Scale suwak do ustalania wartości numerycznej zmiennej
Scrollbar pasek przesuwania
Text wyświetlanie tekstu z formatowaniem
Kosmetyki ciąg dalszy:
In [4]: root = Tk() txt = 'Witaj świecie' root.title(txt) root.geometry('250x200+0+0') # przygotowujemy etykietę lbl = Label(root, text=txt) # umieszczamy ją u dołu ekranu lbl.pack(side=BOTTOM) #uruchamiamy program root.mainloop()
W stronę programu przyciski In [4]: %%writefile progs/GUI/witaj-5.py
# -*- coding: utf-8 -*- from Tkinter import *
#funkcja zwrotna def quit(): import sys; sys.exit()
root = Tk() txt = 'Witaj świecie' root.title(txt) root.geometry('250x200+0+0') #etykieta wstawiona u góry ekranu lbl = Label(root,text=txt) lbl.pack(side=TOP) #przycisk btn = Button(root, text="Koniec", command=quit) #wstawiamy go u dołu ekranu btn.pack(side=BOTTOM) #uruchamiamy pętlę zdarzeń root.mainloop()
Overwriting progs/GUI/witaj-5.py
In [5]: !python progs/GUI/witaj-5.py
własność command obiektu Button definiuje funkcję zwrotną, czyli akcję, która zostanie wykonana po naciśnięciu przycisku jego naciśnięcie wyłapywane jest w pętli zdarzeń (mainloop())
To samo tylko w ujęciu obiektowym: In [6]: %%writefile progs/GUI/witaj-6.py
# -*- coding: utf-8 -*-
from Tkinter import *
class Example: def __init__(self,master): self.lbl = Label(master,text="Witaj świecie!") self.lbl.pack(side=TOP) self.btn = Button(master,text="Koniec",command=self.quit) self.btn.pack(side=BOTTOM) def quit(self): import sys; sys.exit()
if __name__ == "__main__": root = Tk() root.title("Witaj świecie") root.geometry('250x200+0+0') ex = Example(root) root.mainloop()
Overwriting progs/GUI/witaj-6.py
In [6]: !python progs/GUI/witaj-6.py
W stronę programu menu In [8]: %%writefile progs/GUI/witaj-7.py
# -*- coding: utf-8 -*-
from Tkinter import *
def nf(): print "Tworzę nowy plik..."
def makeFileMenu(): # menu "Plik" File_button = Menubutton(mBar, text='Plik', underline=0) File_button.pack(side=LEFT, padx="1m") File_button.menu = Menu(File_button)
#dodaj pozycje "Nowy" i "Zakończ" w menu "Plik" File_button.menu.add_command(label='Nowy...', underline=0, command=nf) File_button.menu.add_command(label='Zakończ', underline=0, command='exit') #wskaźnik powrotny do menu File_button['menu'] = File_button.menu return File_button
root = Tk() root.title('Witaj świecie') root.geometry('250x200+0+0')
# menu mBar = Frame(root, relief=RAISED, borderwidth=2) mBar.pack(side=TOP,fill=X) File_button = makeFileMenu() mBar.tk_menuBar(File_button)
# etykieta lbl = Label(root,text="Witaj świecie") lbl.pack(side=BOTTOM)
root.mainloop()
Overwriting progs/GUI/witaj-7.py
In [7]: !python progs/GUI/witaj-7.py
Tworzę nowy plik... Tworzę nowy plik... Tworzę nowy plik...
I jeszcze jeden przykład menu: In [20]: %%writefile progs/GUI/witaj-7a.py # -*- coding: utf-8 -*-
from Tkinter import * def donothing(): filewin = Toplevel(root) button = Button(filewin, text="Do nothing button") button.pack()
root = Tk() menubar = Menu(root) filemenu = Menu(menubar, tearoff=0) filemenu.add_command(label="New", command=donothing) filemenu.add_command(label="Open", command=donothing) filemenu.add_command(label="Save", command=donothing) filemenu.add_command(label="Save as...", command=donothing) filemenu.add_command(label="Close", command=donothing)
filemenu.add_separator()
filemenu.add_command(label="Exit", command=root.quit) menubar.add_cascade(label="File", menu=filemenu) editmenu = Menu(menubar, tearoff=0) editmenu.add_command(label="Undo", command=donothing)
editmenu.add_separator()
editmenu.add_command(label="Cut", command=donothing) editmenu.add_command(label="Copy", command=donothing) editmenu.add_command(label="Paste", command=donothing) editmenu.add_command(label="Delete", command=donothing) editmenu.add_command(label="Select All", command=donothing)
menubar.add_cascade(label="Edit", menu=editmenu) helpmenu = Menu(menubar, tearoff=0) helpmenu.add_command(label="Help Index", command=donothing) helpmenu.add_command(label="About...", command=donothing) menubar.add_cascade(label="Help", menu=helpmenu)
root.config(menu=menubar) root.mainloop()
Overwriting progs/GUI/witaj-7a.py
In [8]: !python progs/GUI/witaj-7a.py
W stronę programu Canvas i Scale
In [21]: %%writefile progs/GUI/demo-1.py
# -*- coding: utf-8 -*-
from Tkinter import * import string class Demo(Frame): def createWidgets(self): #przycisk "Koniec" self.QUIT = Button(self, text='KONIEC', foreground='red', command=self.quit) self.QUIT.pack(side=LEFT, fill=BOTH)
# Scena self.draw = Canvas(self, width="5i", height="5i")
# Kontrola prędkości self.speed = Scale(self, orient=HORIZONTAL, label="Prędkość piłki", from_=-100, to=100) self.speed.pack(side=BOTTOM, fill=X)
# Piłka self.ball = self.draw.create_oval("0i", "0i", "0.10i", "0.10 i", fill="red") self.x = 0.05 self.y = 0.05 self.velocity_x = 0.3 self.velocity_y = 0.5
self.draw.pack(side=LEFT)
def moveBall(self, *args): #zmień prędkość na przeciwną na brzegach canvas if (self.x > 5.0) or (self.x < 0.0): self.velocity_x = -1.0 * self.velocity_x if (self.y > 5.0) or (self.y < 0.0): self.velocity_y = -1.0 * self.velocity_y
#zmiana położenia odpowiadająca prędkości deltax = (self.velocity_x * self.speed.get() / 100.0) deltay = (self.velocity_y * self.speed.get() / 100.0) self.x = self.x + deltax self.y = self.y + deltay
#rysowanie piłki self.draw.move(self.ball, "%ri" % deltax, "%ri" % deltay) self.after(10, self.moveBall)
def __init__(self, master=None): Frame.__init__(self, master) Pack.config(self) self.createWidgets() self.after(10, self.moveBall)
demo = Demo()
demo.mainloop()
Overwriting progs/GUI/demo-1.py
In [9]: !python progs/GUI/demo-1.py Zarządzanie geometrią
pack najprostszy (najszybszy) sposób dzięki kontenerowi Frame można budować skomplikowane interfejsy grid przypomina tabele HTML umożliwia tworzenie skomplikowanych interfejsów każda kontrolka ma przynajmniej jedną komórkę kontrolki mogą się rozciągać na wiele komórek place najbardziej precyzyjny sposób rozmieszczenie kontrolek na podstawie podanych współrzędnych uciążliwe (raczej nie dla leniwych)
Przykład wykorzystania funkcji grid:
In [23]: %%writefile progs/GUI/demo-2.py
# -*- coding: utf-8 -*-
from Tkinter import *
root = Tk() root.title("Formularz") #Label(root,text="Imię").grid(row=0,sticky=W) Label(root,text="Imię").grid(row=0) Label(root,text="Nazwisko").grid(row=1,sticky=W)
e1 = Entry(root) e2 = Entry(root) e1.grid(row=0,column=1) e2.grid(row=1,column=1)
root.mainloop()
Overwriting progs/GUI/demo-2.py
In [24]: !python progs/GUI/demo-2.py
Obsługa zdarzeń
prosta i wygodna funkcje zwrotne można przypisać każdemu zdarzeniu dla każdej kontrolki funkcjami zwrotnymi mogą być: wyrażenia lambda zwykłe funkcje użytkownika metody zdarzenia reprezentowane są za pomocą tzw. deskryptorów
Typy zdarzeń:
Źródło Zdarzenia
Klawiatura KeyPress, KeyRelease
Myszka ButtonPress, ButtonRelease, Motion, Enter, Leave, MouseWheel
Visibility, Unmap, Map, Expose, FocusIn, FocusOut, Circulate, Okno Colourmap, Gravity, Reparent, Property, Destroy, Activate, Deactivate
Kwalifikatory myszka:
Wartość Opis
1 lewy przycisk
2 środkowy przycisk
3 prawy przycisk
4 kółko w górę
5 kółko w dół
Kwalifikatory klawiatura:
Wartość Opis
AZ poszczególne litery
BackSpace klawisz BackSpace
......
Modyfikatory:
Źródło Wartość
myszka Double, Triple, B1, B2,...
klawiatura Control, Shift, Alt, Meta
Any
Przykłady deskryptorów:
Propagacja zdarzeń: cztery poziomy dowiązania kontrolka okno nadrzędne (root lub TopLevel) klasa aplikacja zdarzenie "przechodzi" w dół powyższego łańcucha aż do natrafienia na odpowiednią funkcję zwrotną aby przerwać dalszą propagację, funkcja zwrotna musi zwrócić wartość break
Dowiązania:
do kontrolki widget.bind(descriptor, callback, add=None) do okna nadrzędnego toplevel.bind(descriptor, callback, add=None) do klasy widget.bind_class(class_name, descriptor, callback, add=None) do całej aplikacji widget.bind_all(descriptor, callback, add=None)
In [26]: %%writefile progs/GUI/demo-3.py
# -*- coding: utf-8 -*-
from Tkinter import *
root = Tk()
def callback(event): print "Kliknąłeś w punkcie: ", event.x, event.y, "w chwili: ", event.time
frame = Frame(root, width=100, height=100) frame.bind("
root.mainloop()
Overwriting progs/GUI/demo-3.py
In [11]: !python progs/GUI/demo-3.py
Kliknąłeś w punkcie: 17 22 w chwili: 866125018 Kliknąłeś w punkcie: 78 27 w chwili: 866125674 Kliknąłeś w punkcie: 41 49 w chwili: 866126242 Kliknąłeś w punkcie: 78 66 w chwili: 866126738 Kliknąłeś w punkcie: 15 84 w chwili: 866127810 Kliknąłeś w punkcie: 22 47 w chwili: 866128482 Kliknąłeś w punkcie: 22 47 w chwili: 866128674 Kliknąłeś w punkcie: 22 47 w chwili: 866128842 Kliknąłeś w punkcie: 22 47 w chwili: 866129018 Kliknąłeś w punkcie: 22 47 w chwili: 866129202 Wybrane własności zdarzeń:
Własność Opis
num numer przycisku myszki, który został wciśnięty
height/width wysokość/szerokość wyeksponowanego okna
keycode kod wciśniętego klawisza
time czas wystąpienia zdarzenia
x/y położenie myszki względem kontrolki
x_root/y_root położenie myszki względem okna głównego
char wciśnięty klawisz (jako znak)
widget kontrolka, dla której wystąpiło zdarzenie
W stronę programu wczytywanie plików
In [2]: %%writefile progs/GUI/demo-4.py
from Tkinter import * from tkFileDialog import askopenfilename
root = Tk() filename = askopenfilename(filetypes=[("allfiles","*"),("pythonfile s","*.py")]) print filename
Overwriting progs/GUI/demo-4.py
In [13]: !python progs/GUI/demo-4.py
/home/szwabin/Dropbox/Zajęcia/PythonIntro/9_GUI/9_gui.ipynb