En el último post de este tema habíamos creado una ventana con una etiqueta a modo del típico hola mundo, esta vez haremos una introducción a los widgets usando los 2 tipos más básicos.
En el caso de una calculadora, usa un cajón de entrada de texto que llamaremos entry y de varios botones llamados buttons, también haremos uso de 2 widgets invisibles llamados frames, que nos sirven para organizar otros widgets.
También aplicaremos el paradigma orientado a objetos para hacer que nuestras aplicaciones sean más escalables, aunque en esta ocasión por ser una aplicación tan pequeña no nos sirve de mucho, pero les servirá a ustedes como una muestra para futuros proyectos.
import tkinter as tk
from tkinter import ttk
class Calculator(tk.Frame):
""" Clase para graficar la calculadora """
def __init__ (self, parent):
""" Class initialiser """
super().__init__(parent)
self.entry = ttk.Entry(parent, text = '')
self.entry.pack(fill = tk.X, padx = 10, pady = 5)
self.button_frame = ttk.Frame(parent)
self.button_frame.pack(padx = 5, pady = 5, ipadx = 5)
# ---------------------
seven = ttk.Button(self.button_frame, text = '7', command =
lambda : self.entry.insert('end',7))
seven.grid(row = 0, column = 0,stick = 'WE')
eight = ttk.Button(self.button_frame, text = '8', command =
lambda : self.entry.insert('end',8))
eight.grid(row = 0, column = 1,stick = 'WE')
nine = ttk.Button(self.button_frame, text = '9', command =
lambda : self.entry.insert('end',9))
nine.grid(row = 0, column = 2,stick = 'WE')
div = ttk.Button(self.button_frame, text = '/', command =
lambda : self.entry.insert('end','/'))
div.grid(row = 0, column = 3,stick = 'WE')
ret = ttk.Button(self.button_frame, text = '⮐', command =
lambda : self.entry.delete(len(self.entry.get()) -1,'end'))
ret.grid(row = 0, column = 4,stick = 'WE')
clear = ttk.Button(self.button_frame, text = 'C', command =
lambda : self.entry.delete(0,'end'))
clear.grid(row = 0, column = 5,stick = 'WE')
# ---------------------
four = ttk.Button(self.button_frame, text = '4', command =
lambda : self.entry.insert('end',4))
four.grid(row = 1, column = 0,stick = 'WE')
five = ttk.Button(self.button_frame, text = '5', command =
lambda : self.entry.insert('end',5))
five.grid(row = 1, column = 1,stick = 'WE')
six = ttk.Button(self.button_frame, text = '6', command =
lambda : self.entry.insert('end',6))
six.grid(row = 1, column = 2,stick = 'WE')
mul = ttk.Button(self.button_frame, text = '*', command =
lambda : self.entry.insert('end','*'))
mul.grid(row = 1, column = 3,stick = 'WE')
lpar = ttk.Button(self.button_frame, text = '(', command =
lambda : self.entry.insert('end','('))
lpar.grid(row = 1, column = 4,stick = 'WE')
rpar = ttk.Button(self.button_frame, text = ')', command =
lambda : self.entry.insert('end',')'))
rpar.grid(row = 1, column = 5,stick = 'WE')
# ---------------------
one = ttk.Button(self.button_frame, text = '1', command =
lambda : self.entry.insert('end',1))
one.grid(row = 2, column = 0,stick = 'WE')
two = ttk.Button(self.button_frame, text = '2', command =
lambda : self.entry.insert('end',2))
two.grid(row = 2, column = 1,stick = 'WE')
three = ttk.Button(self.button_frame, text = '3', command =
lambda : self.entry.insert('end',3))
three.grid(row = 2, column = 2,stick = 'WE')
minus = ttk.Button(self.button_frame, text = '-', command =
lambda : self.entry.insert('end','-'))
minus.grid(row = 2, column = 3,stick = 'WE')
power = ttk.Button(self.button_frame, text = '^', command =
lambda : self.entry.insert('end','**'))
power.grid(row = 2, column = 4,stick = 'WE')
sroot = ttk.Button(self.button_frame, text = '√', command =
lambda : self.entry.insert('end','//'))
sroot.grid(row = 2, column = 5,stick = 'WE')
# ---------------------
zero = ttk.Button(self.button_frame, text = '0', command =
lambda : self.entry.insert('end',0))
zero.grid(row = 3, column = 0,stick = 'WE')
comma = ttk.Button(self.button_frame, text = ',', command =
lambda : self.entry.insert('end',','))
comma.grid(row = 3, column = 1,stick = 'WE')
percent = ttk.Button(self.button_frame, text = '%', command
= lambda : self.entry.insert('end','%'))
percent.grid(row = 3, column = 2,stick = 'WE')
plus = ttk.Button(self.button_frame, text = '+', command =
lambda : self.entry.insert('end','+'))
plus.grid(row = 3, column = 3,stick = 'WE')
equal = ttk.Button(self.button_frame, text = '=', command =
self._equal)
equal.grid(row = 3, column = 4,stick ='WE', columnspan = 2)
def _equal(self):
result = eval(self.entry.get())
self.entry.delete(0,'end')
self.entry.insert('end',result)
def main(args):
root = tk.Tk()
root.title('Calculadora')
root.geometry('460x150')
root.resizable(False,False)
application = Calculator(root)
root.mainloop()
return 0
if __name__ == '__main__':
import sys
sys.exit(main(sys.argv))
¿Que son los widgets?
Un Widget son todos aquellos objetos que reconocemos de un formulario, como botones, cajas de texto, separadores, barras de desplazamiento, menús desplegables, entre otros, cada uno de estos widgets hereda de una clase widget y por tanto muchos de los métodos que hereda son muy similares. Vas a ver que la curva de aprendizaje no es muy difícil.
self.entry = ttk.Entry(parent, text = '')
self.entry.pack(fill = tk.X, padx = 10, pady = 5)
self.button_frame = ttk.Frame(parent)
self.button_frame.pack(padx = 5, pady = 5, ipadx = 5)
# ---------------------
seven = ttk.Button(self.button_frame, text = '7', command =
lambda : self.entry.insert('end',7))
seven.grid(row = 0, column = 0,stick = 'WE')
Este fragmento muestra tres tipos de widgets, entrys, buttons y frames, estos widgets son clases que usa como parámetro obligatorio un espacio en el cual ser representado, en el caso de entry será representado dentro de parent, al igual que frame, pero button será representado dentro de frame, así de esta forma vamos anidando un widget dentro de otro.
Los demás atributos de la clase dentro de los widgets son opcionales y realmente son un diccionario, muchos de las llaves del diccionario comparten llaves en común con otros widgets, solo es pensar con un poco de sentido común.
Disposición de los widgets.
self.button_frame.pack(padx = 5, pady = 5, ipadx = 5)
equal.grid(row = 3, column = 4,stick ='WE', columnspan = 2)
En tkinter hay 3 métodos para organizar widgets, la primera es #place, que tiene posiciones absolutas y está es la forma menos utilizada, la segunda es #pack que hace disposición de los widgets apilando uno sobre otro, está es la segunda forma más utilizada y por último #grid que dispone los widgets como si fuese una hoja de cálculo y es la más utilizada y la que en mi opinión queda más estética. Algo a tener en cuenta es que no es posible combinar distintas disposiciones entre sí en un mismo espacio de renderizado, es por eso que, para poder combinar varias disposiciones en una misma ventana, hay que anidar widgets dentro de frames.
Aquí podemos observar que el botón invoca una función pack(), esta función sirve para apilar el widget debajo de otro, los atributos dentro de pack difieren un poco respecto a grid.
fill: rellena el espacio del widget a lo ancho y a lo alto. usando tk.X rellena el espacio a lo ancho, tk.Y rellena el espacio verticalmente.
side: posiciona el widget al centro, izquierda, derecha, arriba o abajo, usando tk.LEFT, tk.RIGHT, tk.UP, tk.BOTTOM, tk.CENTER.
expand: permite que el widget cambie de tamaño si la ventana aumenta de tamaño.
padx: deja un margen en los costados en pixeles.
pady: deja un margen arriba y abajo.
ipadx: deja un margen dentro del mismo widget a la izquierda y derecha.
ipady: deja un margen dentro del widget arriba y abajo.
También podemos ver que los botones invocan una función grid() y está representado dentro de un frame, la función grid posiciona los objetos como si de una hoja de cálculo se tratara, tiene en común con la función pack los atributos padx, pady, ipadx, ipady. Los atributos propios de la función grid son:
row: indica la fila en la que se posiciona y esta empieza en cero.
column: indica la columna en la que se posiciona, empezando en cero.
Si dos widgets coinciden en los valores de filas y columnas, solo será representado el ultimo widget con la posición, sobrescribiendo las demás.
columnspan: indica la cantidad de espacios en columnas que puede expandirse el widget, esta es la forma en la que podemos aumentar la posibilidad de incrementar el ancho del widget.
rowspan: indica la cantidad de filas en la que se puede expandir el widget.
sticky: pega los bordes del widget según la dirección cardinal que se le indique, siendo 'N = norte, S = Sur, E = East, W = Oeste', y estas orientaciones se pueden mezclar 'NS' expande de norte a sur, 'WE' expande de este a oeste, etc.
La funcionalidad del código.
Si analizas el código tiene una estructura muy repetitiva y tienes razón si piensas que pudieras haber creado cada botón dentro de un bucle. Personalmente también lo intenté, pero por algún motivo y sin investigarlo mucho, el string que escribía al momento de presionar el botón no me coincidía con lo que debía arrojar, así que tuve que plasmar cada botón manualmente, ignorando esto porque se lo ustedes lo mejoraran. El método principal que hace la magia aquí es _equal, que corresponde al botón igual de la calculadora, este método toma el texto escrito dentro del entry o caja de texto y usando la función nativa de Python #eval, interpreta la cadena de texto y lo evalúa como si fuese una expresión matemática.
Otros botones ejecutan acciones sobre el mismo entry como insert, para introducir un caractér. El botón ret, toma toda la cadena de texto dentro del entry y retira el ultimo caractér.
No habiendo nada más que analizar, el código se explica mucho por si mismo, también debo advertir que contiene errores y es susceptible de mucha mejora. pero creo que la intensión es más que suficiente para que sirva de introducción a #tkinter, en futuras entregas iremos profundizando en más widgets y sus atributos.
no siendo más nos vemos en un próximo post.
Comments