divendres, 2 de setembre del 2011

Python i Tkinter

En un post anterior vaig presentar la construcció d'interfases gràfiques amb Python i Glade. En ewl post d'avui presento la construcció d'interfaces gràfiques amb Python fent us d'una llibreria clàssica i que agermana el llenguatge amb el Tcl/Tk: em refereixo a Tkinter (o tkinter, amb el nom en minúscula, a partir de Python 3.0). Tkinter és una interface entre Python i Tk, d'aqui el nom. Tk és quelcom de diferrent de Python, però tots dos es troben a sistemes Unix. Tkinter permet a Python utilitzar Tk.

Tkinter permet desenvolupar  interfases gràfiques senzilles de forma ràpida. En aquest post faig una senzilla GUI d'exemple. Es tracta d'una pantalla que té dues caixa d'entrada, superior i inferior, i dos botons. En prémer el botó de l'esquerra la informació que hi ha a la caixa superior es mourà a la caixa inferior; i si no hi ha informació a la caixa superior es mostrarà un missatge d'avís. De forma similar, en prémer el botó de la dreta  la informació que hi ha la caixa inferior es mourà a la caixa superior; i si no hi ha  informació a la caixa inferior es mostrarà un missatge d'avís.

El codi és el següent:

#!/usr/bin/python
# coding: latin-1
#
# http://www.tutorialspoint.com/python/
# http://www.astro.washington.edu/users/rowen/TkinterSummary.html
# http://effbot.org/tkinterbook/

import tkMessageBox
from Tkinter import *
# alternativa
# import Tkinter
# Tkinter.Tk()

class App:
	def __init__(self, root):
		Label(root, text='Caixa superior').grid(row=1,column=1, ipadx=5, ipady=5, padx=5, pady=5)
		self.EntryDalt = Entry(root)
		self.EntryDalt.grid(row=1,column=2, ipadx=5, ipady=5, padx=5, pady=5)
		Label(root, text='Caixa inferior').grid(row=2,column=1, ipadx=5, ipady=5, padx=5, pady=5)
		self.EntryBaix = Entry(root)
		self.EntryBaix.grid(row=2,column=2, ipadx=5, ipady=5, padx=5, pady=5)
		Label(root, text='---------------').grid(row=3,column=1, columnspan=2, ipadx=5, ipady=5, padx=5, pady=5)
		Button1 = Button(root, text="Passa de dalt a baix", command=self.Mostra1)
		Button1.grid(row=4,column=1, ipadx=5, ipady=5, padx=5, pady=5)
		Button2 = Button(root, text="Passa de baix a dalt", command=self.Mostra2)
		Button2.grid(row=4,column=2, ipadx=5, ipady=5, padx=5, pady=5)
		
	# de dalt a baix.
	# no passa si no hi ha res a la caixa de dalt.
	def Mostra1(self):
		sEntryDalt = self.EntryDalt.get()

		if sEntryDalt == '':
			tkMessageBox.showinfo("Avís!", "No hi ha text a la caixa superior")
		else:
			# passa de dalt a baix
			self.EntryBaix.delete(0, END)		
			self.EntryDalt.delete(0, END)		
			self.EntryBaix.insert(0,sEntryDalt)

	# de baix a dalt
	# no passa si no hi ha text a la caixa de baix
	def Mostra2(self):
		sEntryBaix = self.EntryBaix.get()

		if sEntryBaix == '':
			tkMessageBox.showinfo("Avís!", "No hi ha text a la caixa inferior")
		else:
			# passa de baix a dalt
			self.EntryDalt.delete(0, END)		
			self.EntryBaix.delete(0, END)		
			self.EntryDalt.insert(0,sEntryBaix)

if __name__ == "__main__":
	root = Tk()
	root.title("Prova TK")
	App(root)	
	root.mainloop()

Uns comentaris al codi anterior,

Primer de tot, la importació de les llibreries: he utilitzat Tkinter i tkMessageBox. tkMessageBox em proporciona un popup de missatges que es pot configurar per a que sigui  d'informació, d'avís, d'error, amb botons d'OK, Cancel, Sí, no... tkMessageBox no és Tkinter, tot i que també es recolza en Tk per a proporcionar el widget de caixa de missatges.

Segon. M'en vaig al final, al main:


if __name__ == "__main__":
	root = Tk()
	root.title("Prova Tkinter")
	App(root)	
	root.mainloop()


Primer de tot, cal dir que una interface amb Tk es pot conceptualitzar com un arbre de widgets, en els que uns són fills (estan inclosos) en d'altres, fins arribar a un primer nivell on tindrem un widget arrel. El primer de tot, doncs, és obtenir  el widget arrel, amb


	root = Tk()

Ara  ja puc afegir (incloure) widgets  fills.

Un cop tinc el widget arrel, el puc configurar. Per exemple, li dono un títol "Prova Tkinter"

	root.title("Prova Tkinter")


A continuació, fent us d'una classe que anomeno App creo la interface.

	App(root)


Finalment, inicio l'interface  invocant el "bucle d'esdeveniments"

	root.mainloop()


Ara és el moment d'analitzar la classe App.

A la classe App he fet que el constructor sigui l'encarregat de col·locar els diferents widgtets a la finestra.
A més, he creat  dos mètodes Mostra1 i Mostra2 que seran la resposta als esdeveniments de prémer el botó esquerra i dret respectivament.

Vet aquí el constructor:

	def __init__(self, root):
		Label(root, text='Caixa superior').grid(row=1,column=1, ipadx=5, ipady=5, padx=5, pady=5)
		self.EntryDalt = Entry(root)
		self.EntryDalt.grid(row=1,column=2, ipadx=5, ipady=5, padx=5, pady=5)
		Label(root, text='Caixa inferior').grid(row=2,column=1, ipadx=5, ipady=5, padx=5, pady=5)
		self.EntryBaix = Entry(root)
		self.EntryBaix.grid(row=2,column=2, ipadx=5, ipady=5, padx=5, pady=5)
		Label(root, text='---------------').grid(row=3,column=1, columnspan=2, ipadx=5, ipady=5, padx=5, pady=5)
		Button1 = Button(root, text="Passa de dalt a baix", command=self.Mostra1)
		Button1.grid(row=4,column=1, ipadx=5, ipady=5, padx=5, pady=5)
		Button2 = Button(root, text="Passa de baix a dalt", command=self.Mostra2)
		Button2.grid(row=4,column=2, ipadx=5, ipady=5, padx=5, pady=5)


El que obtinc és

Els constructors dels widgets Label, Entry i Button prenen un primer paràmetre que és el widget pare del que pengen. Labels i Buttons, a més, prenen una paràmetre de configuració que, en aquest cas es refereix al text a mostrar en Labels i Buttons; els buttons, a més, prenent un tercer paràmetre de configuració que fa apunta al mètode que respondrà a l'esdeveniment per defecte del botó, que és fer-hi clic.

Com és que cada widget va al lloc que li correspon? la clau està al mètode grid() El mètode grid ens permet accedir al gestor de finestra Grid. Hi han d'altres tipus de gestor, com el gestor de finestra Pack, o el gestor de Finestra Place.

Grid és molt senzill de fer servir. La forma com ubica els widgets em recorda una mica a la maquetació amb taules en HTML. El mètode grid rep un conjunt de paràmetres. Per exemple:


Label(root, text='Caixa superior').grid(row=1,column=1, ipadx=5, ipady=5, padx=5, pady=5)


Grid conceptualitza la finestra com una graella rectangular de cel·les.  Cada cel·la pot acollir un widget. La posició d'una cel·la ve definida per la fila/columna. Les files es numeren de dalt a baix començant per l'1. Les columnes d'esquerra a dreta, començant per l'1. Puc agrupar cel·les adjacents en horitzontal i/o vertical.

Tenint en compte això, els paràmetres del Label "Caixa superior" s'expliquen fàcilment: posa el label a la cel·la de la fila 1, columna 1. L'ample de la cel·la s'ajusta automàticament al contingut. Tanmateix, per a fer que la interfase "respiri" dono un marge interior ("padding") entre el límit de la cel·la i la caixa d'entrada de 5píxels (ipadx=5, ipady=5) i un d'exterior entre cel·les, també de 5 píxels (padx=5, pady=5).

Amb la resta de widgets funciona igual. El resultat és que la meva finestra, conceptualment és una graella de dues columnes per quatre files.

A la fila 3 hi ha un exemple del que deia d'agrupar cel·les adjacents

Label(root, text='---------------').grid(row=3,column=1, columnspan=2, ipadx=5, ipady=5, padx=5, pady=5)

en aquest cas, el paràmetre columnspan em diu que la cel·la ocupa dues cel·les en horitzontal. Hi ha un paràmetre rowspan que funciona de la mateixa forma agrupant cel·les en vertical.

Per acabar, els mètodes que responen als esdeveniments Mostra1 i Mostra2 són gairebé idèntics,


	# de dalt a baix.
	# no passa si no hi ha res a la caixa de dalt.
	def Mostra1(self):
		sEntryDalt = self.EntryDalt.get()

		if sEntryDalt == '':
			tkMessageBox.showinfo("Avís!", "No hi ha text a la caixa superior")
		else:
			# passa de dalt a baix
			self.EntryBaix.delete(0, END)		
			self.EntryDalt.delete(0, END)		
			self.EntryBaix.insert(0,sEntryDalt)

	# de baix a dalt
	# no passa si no hi ha text a la caixa de baix
	def Mostra2(self):
		sEntryBaix = self.EntryBaix.get()

		if sEntryBaix == '':
			tkMessageBox.showinfo("Avís!", "No hi ha text a la caixa inferior")
		else:
			# passa de baix a dalt
			self.EntryDalt.delete(0, END)		
			self.EntryBaix.delete(0, END)		
			self.EntryDalt.insert(0,sEntryBaix)


A Mostra1, per exemple, primer de tot, s'obté el contingut del EntryDalt amb el mètode get.

		sEntryDalt = self.EntryDalt.get()


Si el valor obtingut és nul, mostra l'avís

		if sEntryDalt == '':
			tkMessageBox.showinfo("Avís!", "No hi ha text a la caixa superior")

En cas contrari, primer esborra les Entry (mètode delete(0,END))

		else:
			# passa de baix a dalt
			self.EntryDalt.delete(0, END)		
			self.EntryBaix.delete(0, END)	

I, finalment, emplena la caixa inferior

			self.EntryDalt.insert(0,sEntryBaix)


I fins aquí aquest senzill experiment per a prendre contacte amb Tkinter.

Per a més informació, podeu consultar:

La pàgina de Tkinter, http://www.python.org/topics/tkinter/doc.html
Thinking in Tkinter, http://www.ferg.org/thinking_in_tkinter/index.html
Tkinter: GUI programming with Python, http://infohost.nmt.edu/tcc/help/lang/python/tkinter.html
Python. GUI Programming (Tkinter), http://www.tutorialspoint.com/python/python_gui_programming.htm

Cap comentari:

Publica un comentari a l'entrada