Języki skryptowe Pythonprac.im.pwr.wroc.pl/~szwabin/assets/prog/lec/9.pdf · Mark Summerfield,...
Transcript of Języki skryptowe Pythonprac.im.pwr.wroc.pl/~szwabin/assets/prog/lec/9.pdf · Mark Summerfield,...
Języki skryptowe Python
Wykład 9 Graficzne interfejsy użytkownika
Janusz Szwabiński
Plan wykładu:
Kilka uwag na temat projektowania GUIPrzegląd bibliotek GUIPierwsze 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 DevelopersMark 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łudzeGUI decyduje o popularności programu tandetny program z bardzo dobrym GUI często będziebardziej popularny niż nawet najlepszy program bez dobrego interfejsu użytkownikaelementy na brzegach ekranu wydają się większe
Po pierwsze nie przeszkadzać!
In [1]: from IPython.display import ImageImage(filename='images/konqueror.png')
Po drugie elementy łatwe do znalezienia!
In [2]: Image(filename='images/bad-functionaloverload1.jpg')
Po trzecie konwencje są dobre!
Out[1]:
Out[2]:
In [3]: Image(filename='images/ui_conventions.png')
Po czwarte poziomy projektowania
Każdy interfejs projektujemy na dwóch poziomach:
szata graficzna układ elementów na ekraniefunkcjonalność zdarzenia i ich obsługa
Podstawowymi klockami do budowy graficznej części interfejsu są kontrolki (ang. widgets):
przyciskipaski przewijaniatekstypola tekstowe...
Natomiast funkcjonalność realizuje się poprzez funkcje zwrotne (ang. callback) przypisane zdarzeniom.
Po piąte papier ciągle żywy
Out[3]:
In [4]: #źródło: http://www.mobiloud.com/blog/2013/01/build-app-with-no-programming/Image(filename='images/ui_paper_design.png')
Przegląd bibliotek GUI
Out[4]:
Nazwa Opis URL
Tkinter
najpopularniejszy zestaw narzędzido tworzenia GUI, dołączany do każdej dystrybucjiPythona
http://tkinter.unpythonic.net/wiki/(http://tkinter.unpythonic.net/wiki/)
WxPythonbardzo popularna nakładka nabibliotekę WxWidgets
http://www.wxpython.org/ (http://www.wxpython.org/)
PyQt nakładka na bibliotekę Qt http://pyqt.sourceforge.net/ (http://pyqt.sourceforge.net/)
PyGObjectnakładka na bibliotekiGLib/GObject/GIO/GTK+
https://wiki.gnome.org/action/show/Projects/PyGObject(https://wiki.gnome.org/action/show/Projects/PyGObject)
PyFLTK nakładka na bibliotekę FLTK http://pyfltk.sourceforge.net/ (http://pyfltk.sourceforge.net/)
PyGUIpythonowa warstwa abstrakcjiwykorzystująca systemowebiblioteki GUI
http://www.cosc.canterbury.ac.nz/greg.ewing/python_gui/(http://www.cosc.canterbury.ac.nz/greg.ewing/python_gui/)
Jython dostęp do biblioteki Swing http://www.jython.org/ (http://www.jython.org/)
Sugarplatforma stanowiąca częśćprojektu OLPC
http://wiki.sugarlabs.org/ (http://wiki.sugarlabs.org/)
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 GUIelement 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życiuprzenośny
Pierwsze okno w Tkinter
In [2]: from Tkinter import *# otwieramy główne okno aplikacjiroot = Tk()#uruchamiamy pętlę zdarzeńroot.mainloop()
Trochę kosmetyki:
In [3]: from Tkinter import *root = Tk()
# tytuł oknaroot.title(u'Witaj świecie')# rozmiar oknaroot.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)
RadioButtonkontrolka wyboru, występuje w grupach odpowiadających różnym wartościom tej samejzmiennej
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 ekranulbl.pack(side=BOTTOM)#uruchamiamy programroot.mainloop()
W stronę programu przyciski
In [4]: %%writefile progs/GUI/witaj-5.py
# -*- coding: utf-8 -*-from Tkinter import *
#funkcja zwrotnadef quit(): import sys; sys.exit() root = Tk()txt = 'Witaj świecie'root.title(txt)root.geometry('250x200+0+0')#etykieta wstawiona u góry ekranulbl = Label(root,text=txt)lbl.pack(side=TOP)#przyciskbtn = Button(root, text="Koniec", command=quit)#wstawiamy go u dołu ekranubtn.pack(side=BOTTOM)#uruchamiamy pętlę zdarzeńroot.mainloop()
In [5]: !python progs/GUI/witaj-5.py
własność command obiektu Button definiuje funkcję zwrotną, czyli akcję, która zostanie wykonanapo naciśnięciu przyciskujego naciśnięcie wyłapywane jest w pętli zdarzeń (mainloop())
To samo tylko w ujęciu obiektowym:
Overwriting progs/GUI/witaj-5.py
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()
In [6]: !python progs/GUI/witaj-6.py
W stronę programu menu
Overwriting progs/GUI/witaj-6.py
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')
# menumBar = Frame(root, relief=RAISED, borderwidth=2)mBar.pack(side=TOP,fill=X)File_button = makeFileMenu()mBar.tk_menuBar(File_button)
# etykietalbl = Label(root,text="Witaj świecie")lbl.pack(side=BOTTOM)
root.mainloop()
In [7]: !python progs/GUI/witaj-7.py
I jeszcze jeden przykład menu:
Overwriting progs/GUI/witaj-7.py
Tworzę nowy plik...Tworzę nowy plik...Tworzę nowy plik...
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()
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
Overwriting progs/GUI/witaj-7a.py
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.10i", 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()
In [9]: !python progs/GUI/demo-1.py
Overwriting progs/GUI/demo-1.py
Zarządzanie geometrią
packnajprostszy (najszybszy) sposóbdzięki kontenerowi Frame można budować skomplikowane interfejsy
gridprzypomina tabele HTMLumożliwia tworzenie skomplikowanych interfejsówkażda kontrolka ma przynajmniej jedną komórkękontrolki mogą się rozciągać na wiele komórek
placenajbardziej precyzyjny sposóbrozmieszczenie kontrolek na podstawie podanych współrzędnychuciąż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()
In [24]: !python progs/GUI/demo-2.py
Obsługa zdarzeń
prosta i wygodnafunkcje zwrotne można przypisać każdemu zdarzeniu dla każdej kontrolkifunkcjami zwrotnymi mogą być:
wyrażenia lambdazwykłe funkcje użytkownikametody
zdarzenia reprezentowane są za pomocą tzw. deskryptorów
Overwriting progs/GUI/demo-2.py
<Modyfikator-Typ-Kwalifikator>dopuszczalnych jest wiele modyfikatorów jednocześnienie wszystkie sekcje deskryptora są wymagane
Typy zdarzeń:
Źródło Zdarzenia
Klawiatura KeyPress, KeyRelease
Myszka ButtonPress, ButtonRelease, Motion, Enter, Leave, MouseWheel
OknoVisibility, Unmap, Map, Expose, FocusIn, FocusOut, Circulate, 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:
<Double-Button-1> powójne kliknięcie lewym przyciskiem myszki<KeyPress-Tab> wciśnięcie tabulatora<Control-B1-Motion> przeciąganie myszki z wciśniętym jej lewym przyciskiem i wciśniętymklawiszem Control
Propagacja zdarzeń:
cztery poziomy dowiązaniakontrolkaokno nadrzędne (root lub TopLevel)klasaaplikacja
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("<Button-1>", callback)frame.pack() root.mainloop()
In [11]: !python progs/GUI/demo-3.py
Overwriting progs/GUI/demo-3.py
Kliknąłeś w punkcie: 17 22 w chwili: 866125018Kliknąłeś w punkcie: 78 27 w chwili: 866125674Kliknąłeś w punkcie: 41 49 w chwili: 866126242Kliknąłeś w punkcie: 78 66 w chwili: 866126738Kliknąłeś w punkcie: 15 84 w chwili: 866127810Kliknąłeś w punkcie: 22 47 w chwili: 866128482Kliknąłeś w punkcie: 22 47 w chwili: 866128674Kliknąłeś w punkcie: 22 47 w chwili: 866128842Kliknąłeś w punkcie: 22 47 w chwili: 866129018Kliknął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","*"),("pythonfiles","*.py")])print filename
In [13]: !python progs/GUI/demo-4.py
Overwriting progs/GUI/demo-4.py
/home/szwabin/Dropbox/Zajęcia/PythonIntro/9_GUI/9_gui.ipynb