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

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 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 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 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 dopuszczalnych jest wiele modyfikatorów jednocześnie nie wszystkie sekcje deskryptora są wymagane

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

A­Z 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:

­ powójne kliknięcie lewym przyciskiem myszki ­ wciśnięcie tabulatora ­ przeciąganie myszki z wciśniętym jej lewym przyciskiem i wciśniętym klawiszem Control

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("", callback) frame.pack()

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