Unidad 4 - 2022 PDF
Unidad 4 - 2022 PDF
Programación Orientada a
Objetos
Asignatura: Programación Orientada a Objetos
Licenciatura en Ciencias de la Computación
Licenciatura en Sistemas de Información
Tecnicatura en Programación Web
Departamento de Informática
FCEFN – UNSJ
Año 2022
1
Propósitos que
persigue la unidad
Que el estudiante distinga aspectos relevantes inherentes a la
implementación de lenguajes orientados a objetos en general y a Python en
particular.
2
Bibliografía
3
Modelo de Ejecución o Computadora Virtual
Las estructuras de datos de esta computadora virtual son las estructuras de datos
que en tiempo de ejecución se utilizan para la ejecución del programa.
4
Modelo de ejecución
5
Modelo de ejecución
Jerarquías de computadoras
6
Jerarquía de Computadoras – Modelo General
8
Cuestiones de Diseño e
Implementación (1)
Los Lenguajes Orientados a Objetos (LOO) encaran tres problemas
de Diseño de Software
9
Cuestiones de Diseño e
Implementación (2)
Existen distintas maneras para modificar un componente de
software de modo que pueda reutilizarse
12
Secuencia, interacción, eventos
Existen distintos tipos de programas:
• Secuenciales, también denominados batch, el programa inicia su ejecución, abre un
archivo, lee los datos, los procesa, y entrega un resultado, no hay intervención del
usuario final.
• Interactivos, en este tipo de programas, el usuario provee datos necesarios para la
ejecución del programa, el usuario decide qué parte del programa se ejecuta a
través de un menú de opciones, por ejemplo.
• Basados en eventos, el programa se presenta a través de una interface gráfica, y se
queda esperado a que el usuario o el sistema, produzca un evento, puede ser un clic
del mouse, una pulsación de una tecla, o un tick de reloj, que hace que el programa
se ejecute una parte del mismo, este tipo de programas pasa la mayor parte del
tiempo esperando eventos.
13
Evento
Los Eventos son las acciones sobre el programa, como por ejemplo:
• Clic sobre un botón
• Doble clic sobre una imagen para expandirla
• Arrastrar un icono
• Pulsar una tecla o una combinación de teclas
• Elegir una opción de un menú
• Escribir en una caja de texto
• Mover el mouse
• Seleccionar un elemento de una caja desplegable
• Seleccionar un día de un calendario
• Tick de un reloj, por ejemplo de un temporizador
14
Propiedades
Una propiedad define el tamaño de un objeto en pantalla, el título de una
ventana, la etiqueta de un botón, el evento al que responde el objeto, etc.
15
Interfaz gráfica de Usuario
(GUI)
Usando TKinter
16
Tkinter
Con Python hay varias posibilidades para programar una interfaz gráfica de usuario (GUI)
pero Tkinter es fácil de usar.
Es multiplataforma y, además, viene incluido con Python en su versión para Windows,
para Mac y para la mayoría de las distribuciones GNU/Linux.
Se le considera el estándar de facto en la programación GUI con Python.
18
Ejemplo: Convertidor Pulgadas a Centímetros
def calcular(self):
try:
from tkinter import * valor=float(self.pulgadasEntry.get())
from tkinter import ttk, messagebox self.__centimetros.set(2.54*valor)
class Aplicacion(): except ValueError:
__ventana=None messagebox.showerror(title='Error de tipo',
__pulgadas=None message='Debe ingresar un valor numérico')
__centimetros=None self.__pulgadas.set('')
def __init__(self): self.pulgadasEntry.focus()
self.__ventana = Tk()
self.__ventana.geometry('290x115') def testAPP():
self.__ventana.title('Conversor Pulgadas a Centímetros') mi_app = Aplicacion()
mainframe = ttk.Frame(self.__ventana, padding="3 3 12 12") if __name__ == '__main__':
mainframe.grid(column=0, row=0, sticky=(N, W, E, S)) testAPP()
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
mainframe['borderwidth'] = 2
mainframe['relief'] = 'sunken'
self.__pulgadas = StringVar()
self.__centimetros = StringVar()
self.pulgadasEntry = ttk.Entry(mainframe, width=7, textvariable=self.__pulgadas)
self.pulgadasEntry.grid(column=2, row=1, sticky=(W, E))
ttk.Label(mainframe, textvariable=self.__centimetros).grid(column=2, row=2, sticky=(W, E))
ttk.Button(mainframe, text="Calcular", command=self.calcular).grid(column=2, row=3, sticky=W)
ttk.Button(mainframe, text='Salir', command=self.__ventana.destroy).grid(column=3, row=3,
sticky=W)
ttk.Label(mainframe, text="pulgadas").grid(column=3, row=1, sticky=W)
ttk.Label(mainframe, text="es equivalente a").grid(column=1, row=2, sticky=E)
ttk.Label(mainframe, text="centímetros").grid(column=3, row=2, sticky=W)
for child in mainframe.winfo_children():
child.grid_configure(padx=5, pady=5)
self.pulgadasEntry.focus()
self.__ventana.mainloop()
19
Gestores de geometría
Los gestores de geometría definen cómo se
ubican los widgets en la ventana de la
aplicación, existen tres gestores: pack, grid y
place.
Cada ventana puede tener un gestor distinto, el programador
decide cual usar.
A la hora de diseñar las ventanas, se pueden utilizar unos
widgets especiales (marcos, paneles, etc.) que actúan como
contenedores de otros widgets.
Estos widgets contendores, se utilizan para agrupar varios
controles con el objeto de facilitar la operación a los usuarios.
En las ventanas que se utilicen podrá emplearse un gestor con la
ventana y otro diferente para organizar los controles dentro de
estos widgets.
20
Geometría Pack (I)
Con este gestor la organización de los widgets se hace teniendo en cuenta los lados de
una ventana: arriba (TOP), abajo (BOTTOM), derecha (RIGHT) e izquierda (LEFT).
Con este gestor de geometría, es posible hacer que los controles se ajusten a los cambios
de tamaño de la ventana.
import tkinter as tk
class Aplicacion(tk.Tk):
def __init__(self):
super().__init__()
self.title='Pack Geometry'
label_a = tk.Label(self, text="Label A", bg="yellow")
label_b = tk.Label(self, text="Label B", bg="orange")
label_c = tk.Label(self, text="Label C", bg="red")
label_d = tk.Label(self, text="Label D", bg="green")
label_e = tk.Label(self, text="Label E", bg="blue")
opts = { 'ipadx': 10, 'ipady': 10, 'fill': tk.BOTH }
label_a.pack(side=tk.TOP, **opts)
label_b.pack(side=tk.TOP, **opts)
label_a.pack(side=tk.TOP, **opts) label_a.pack(side=tk.TOP, **opts)
label_c.pack(side=tk.LEFT, **opts)
label_b.pack(side=tk.TOP, **opts) label_b.pack(side=tk.TOP, **opts)
label_d.pack(side=tk.LEFT, **opts)
label_c.pack(side=tk.LEFT, **opts) label_c.pack(side=tk.LEFT, **opts)
label_e.pack(side=tk.LEFT, **opts)
label_d.pack(side=tk.LEFT, **opts)
if __name__=='__main__':
app = Aplicacion()
app.mainloop()
21
Tkinter GUI application development cookbook-Chapter 2 – Pag. 98-102
Geometría Pack (II)
from tkinter import * self.ctext1.pack(side=TOP, fill=X, expand=True,
from tkinter import ttk, font padx=5, pady=5)
class Aplicacion(): self.contraseniaLbl.pack(side=TOP, fill=BOTH, expand=True,
__ventana=None padx=5, pady=5)
__clave=None self.ctext2.pack(side=TOP, fill=X, expand=True,
__usuario=None padx=5, pady=5)
def __init__(self): self.separ1.pack(side=TOP, fill=BOTH, expand=True,
self.__ventana = Tk() padx=5, pady=5)
self.__ventana.title("Acceso") self.boton1.pack(side=LEFT, fill=BOTH, expand=True,
fuente = font.Font(weight='bold') padx=5, pady=5)
self.usuarioLbl = ttk.Label(self.__ventana, text="Usuario:", self.boton2.pack(side=RIGHT, fill=BOTH, expand=True,
font=fuente) padx=5, pady=5)
self.contraseniaLbl = ttk.Label(self.__ventana, text="Contraseña:", self.ctext2.focus_set()
font=fuente) self.__ventana.mainloop()
self.__usuario = StringVar() def aceptar(self):
self.__clave = StringVar() if self.__clave.get() == 'tkinter':
self.__usuario.set('') print("Acceso permitido")
self.ctext1 = ttk.Entry(self.__ventana, print("Usuario: ", self.ctext1.get())
textvariable=self.__usuario, print("Contraseña:", self.ctext2.get())
width=30) else:
self.ctext2 = ttk.Entry(self.__ventana, print("Acceso denegado")
textvariable=self.__clave, self.__clave.set("")
width=30, show="*") self.ctext2.focus_set()
self.separ1 = ttk.Separator(self.__ventana, orient=HORIZONTAL)
self.boton1 = ttk.Button(self.__ventana, text="Aceptar", def testAPP():
command=self.aceptar) mi_app = Aplicacion()
self.boton2 = ttk.Button(self.__ventana, text="Cancelar", return 0
command=quit) if __name__ == '__main__':
self.usuarioLbl.pack(side=TOP, fill=BOTH, expand=True, testAPP()
padx=5, pady=5)
22
Geometría Pack (III)
Se definen las posiciones de los widgets dentro
de la ventana. Todos los controles se van
colocando hacia el lado de arriba (TOP),
excepto, los dos últimos, los botones, que se
situarán de la siguiente forma: el primer botón
hacia el lado de la izquierda (LEFT) y el segundo
self.usuarioLbl.pack(side=TOP, fill=BOTH, expand=True,
a su derecha (RIGHT). padx=5, pady=5)
self.ctext1.pack(side=TOP, fill=X, expand=True,
padx=5, pady=5)
‘side’: los valores posibles para la propiedad self.contraseniaLbl.pack(side=TOP, fill=BOTH, expand=True,
son: TOP (arriba), BOTTOM (abajo), LEFT padx=5, pady=5)
(izquierda) y RIGHT (derecha). Si se omite, el self.ctext2.pack(side=TOP, fill=X, expand=True,
padx=5, pady=5)
valor será TOP. self.separ1.pack(side=TOP, fill=BOTH, expand=True,
padx=5, pady=5)
self.boton1.pack(side=LEFT, fill=BOTH, expand=True,
´fill’ : la propiedad‘ fill' se utiliza para indicar al padx=5, pady=5)
gestor cómo expandir/reducir el widget si la self.boton2.pack(side=RIGHT, fill=BOTH, expand=True,
ventana cambia de tamaño. Tiene tres posibles padx=5, pady=5)
class Ventana(object):
__ventana=None
def __init__(self):
self.__ventana=Tk()
self.__ventana.title('Geometría de Celdas en Ventana')
for r in range(0, 5):
for c in range(0, 5):
cell = Entry(self.__ventana, width=10)
cell.grid(padx=5, pady=5, row=r, column=c)
cell.insert(0, '({}, {})'.format(r, c))
def ejecutar(self):
self.__ventana.mainloop()
if __name__=='__main__':
ventana=Ventana()
ventana.ejecutar()
24
Geometría Grid (II)
import tkinter as tk
class Aplicacion(tk.Tk):
def __init__(self):
super().__init__()
label_a = tk.Label(self, text="Label A", bg="yellow")
label_b = tk.Label(self, text="Label B", bg="orange")
label_c = tk.Label(self, text="Label C", bg="red")
label_d = tk.Label(self, text="Label D", bg="green")
label_e = tk.Label(self, text="Label E", bg="blue")
opts = { 'ipadx': 10, 'ipady': 10 , 'sticky': 'nswe' }
label_a.grid(row=0, column=0, **opts)
label_b.grid(row=1, column=0, **opts)
label_c.grid(row=0, column=1, rowspan=2, **opts)
label_d.grid(row=0, column=2, rowspan=2, **opts)
label_e.grid(row=2, column=0, columnspan=3, **opts)
if __name__=='__main__':
app = Aplicacion()
app.mainloop()
30
Ventanas modales y no modales (I)
Las ventanas hijas del ejemplo anterior son del tipo no modales porque mientras existen
es posible interactuar libremente con ellas, sin ningún límite, excepto que si se cierra la
ventana principal se cerrarán todas las ventanas hijas abiertas.
Un ejemplo evidente que usa ventanas no modales está en las aplicaciones ofimáticas
más conocidas, que permiten trabajar con varios documentos al mismo tiempo, cada uno
de ellos abierto en su propia ventana, permitiendo al usuario cambiar sin restricciones de
una ventana a otra.
El caso contrario, es el de las ventanas modales. Cuando una ventana modal está abierta
no será posible interactuar con otras ventanas de la aplicación hasta que ésta sea cerrada.
Un ejemplo típico es el de algunas ventanas de diálogo que se utilizan para establecer las
preferencias de las aplicaciones, que obligan a ser cerradas antes de permitirse la
apertura de otras.
Para demostrarlo, se utilizará el siguiente ejemplo en el que sólo es posible mantener
abierta sólo una ventana hija, aunque si la cerramos podremos abrir otra.
El método grab_set() se utiliza para crear la ventana modal y el método transiet() se
emplea para convertir la ventana de diálogo en ventana transitoria, haciendo que se
oculte cuando la ventana de aplicación sea minimizada
31
Ventanas modales y no modales (II)
from tkinter import *
from tkinter import ttk
class Aplicacion():
## variables de clase
ventana = 0
posx_y = 0
__ventanaPrincipal=None
__dialogo=None
def __init__(self):
self.__ventanaPrincipal = Tk()
self.__ventanaPrincipal.geometry('300x200+500+50')
self.__ventanaPrincipal.resizable(0,0)
self.__ventanaPrincipal.title("Ventana de aplicación")
boton = ttk.Button(self.__ventanaPrincipal, text='Abrir',
command=self.abrir)
boton.pack(side=BOTTOM, padx=20, pady=20)
self.__ventanaPrincipal.mainloop()
def abrir(self):
self.__dialogo = Toplevel()
Aplicacion.ventana+=1
Aplicacion.posx_y += 50
tamypos = '200x100+'+str(Aplicacion.posx_y)+ \
'+'+ str(Aplicacion.posx_y)
self.__dialogo.geometry(tamypos)
self.__dialogo.resizable(0,0)
ident = self.__dialogo.winfo_id()
titulo = str(Aplicacion.ventana)+": "+str(ident)
self.__dialogo.title(titulo)
boton = ttk.Button(self.__dialogo, text='Cerrar', Convierte la ventana 'self.__dialogo' en transitoria con respecto a su ventana
command=self.__dialogo.destroy) maestra 'self.__ventanaPrincipal'.
boton.pack(side=BOTTOM, padx=20, pady=20) Una ventana transitoria siempre se dibuja sobre su maestra y se ocultará cuando
self.__dialogo.transient(master=self.__ventanaPrincipal) la maestra sea minimizada. Si el argumento 'master' es omitido el valor, por
self.__dialogo.grab_set() defecto, será la ventana madre.
self.__ventanaPrincipal.wait_window(self.__dialogo)
def testAPP():
mi_app = Aplicacion() El método grab_set() asegura que no haya eventos de ratón o teclado que se envíen a
return(0) otra ventana diferente a 'self.__dialogo'. Se utiliza para crear una ventana de tipo
if __name__ == '__main__': modal que será necesario cerrar para poder trabajar con otra diferente. Con ello,
testAPP() también se impide que la misma ventana se abra varias veces. 32
Variables de control (I)
Las variables de control son objetos especiales que se asocian a los widgets para
almacenar sus valores y facilitar su disponibilidad en otras partes del programa. Pueden
ser de tipo numérico, de cadena y booleano.
Cuando una variable de control cambia de valor el widget que la utiliza lo refleja
automáticamente, y viceversa.
Las variables de control también se emplean para conectar varios widgets del mismo
tipo, por ejemplo, varios controles del tipo Radiobutton. En este caso tomarán un valor
de varios posibles.
Las variables de control se declaran de forma diferente en función al tipo de dato que
almacenan:
entero = IntVar() # Declara variable de tipo entera
unFlotante = DoubleVar() # Declara variable de tipo flotante
cadena = StringVar() # Declara variable de tipo cadena
booleano = BooleanVar() # Declara variable de tipo booleana
También se le puede dar un valor a una variable:
tituloAPP=StringVar(value=‘Recarga de Sube’)
33
Variables de control (II)
Método set()
El método set() asigna un valor a una variable de control. Se utiliza para modificar el valor o estado de un
widget:
nombre = StringVar()
id_art = IntVar()
nombre.set(‘Luis Artime’)
id_art.set(1)
blog = ttk.Entry(ventana, textvariable=nombre, width=25)
arti = ttk.Label(ventana, textvariable=id_art)
Método get()
El método get() obtiene el valor que tenga, en un momento dado, una variable de control. Se utiliza
cuando es necesario leer el valor de un control:
print(‘Usuario:', nombre.get())
print('Id artículo:', id_art.get())
Método trace()
El método trace() se emplea para "detectar" cuando una variable es leída, cambia de valor o es borrada:
widget.trace(tipo, función).
El primer argumento establece el tipo de suceso a comprobar: 'r' lectura de variable, 'w' escritura de
variable y 'u' borrado de variable. El segundo argumento indica la función que será llamada cuando se
produzca el suceso.
def cambia(*args):
print("Ha cambiado su valor")
def lee(*args):
print("Ha sido leido su valor")
variable = StringVar()
variable.trace("w", cambia)
variable.trace("r", lee)
variable.set("Hola") 34
print(variable.get())print(variable.get())
Estrategias para validar y calcular datos (I)
Cuando se construye una ventana con varios widgets se pueden seguir distintas
estrategias para validar los datos que se introducen durante la ejecución de un
programa:
• Una opción posible podría validar la información y realizar los cálculos después de
que sea introducida, por ejemplo, después de presionar un botón.
• Otra posibilidad podría ser haciendo uso del método trace() y de la opción
'command', para validar y calcular la información justo en el momento que un
widget y su variable asociada cambien de valor.
Ejemplo de la validación posterior a la introducción de los datos, ya se vio en el
programa de conversión de pulgadas a centímetros.
def calcular(self):
try:
valor=float(self.pulgadasEntry.get())
self.__centimetros.set(2.54*valor)
except ValueError:
messagebox.showerror(title='Error de tipo',
message='Debe ingresar un valor numérico')
self.__pulgadas.set('')
self.pulgadasEntry.focus()
35
Estrategias para validar y calcular datos (II)
def calcular(self, *args):
from tkinter import * if self.pulgadasEntry.get()!='':
from tkinter import ttk, messagebox try:
class Aplicacion(): valor=float(self.pulgadasEntry.get())
__ventana=None self.__centimetros.set(2.54*valor)
__pulgadas=None except ValueError:
__centimetros=None messagebox.showerror(title='Error de tipo',
def __init__(self): message='Debe ingresar un valor numérico')
self.__ventana = Tk() self.__pulgadas.set('')
self.__ventana.geometry('290x115') self.pulgadasEntry.focus()
self.__ventana.title('Conversor Pulgadas a Centímetros') else:
mainframe = ttk.Frame(self.__ventana, padding="5 5 12 5") self.__centimetros.set('')
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1) def testAPP():
mainframe.rowconfigure(0, weight=1) mi_app = Aplicacion()
mainframe['borderwidth'] = 2 if __name__ == '__main__':
mainframe['relief'] = 'sunken' testAPP()
self.__pulgadas = StringVar()
self.__centimetros = StringVar()
self.__pulgadas.trace('w', self.calcular)
self.pulgadasEntry = ttk.Entry(mainframe, width=7, textvariable=self.__pulgadas)
self.pulgadasEntry.grid(column=2, row=1, sticky=(W, E))
ttk.Label(mainframe, textvariable=self.__centimetros).grid(column=2, row=2, sticky=(W, E))
ttk.Button(mainframe, text='Salir', command=self.__ventana.destroy).grid(column=3, row=3, sticky=W)
ttk.Label(mainframe, text="pulgadas").grid(column=3, row=1, sticky=W)
ttk.Label(mainframe, text="es equivalente a").grid(column=1, row=2, sticky=E)
ttk.Label(mainframe, text="centímetros").grid(column=3, row=2, sticky=W)
for child in mainframe.winfo_children():
child.grid_configure(padx=5, pady=5) Se define una traza con la variable de entrada, ‘self.__pulgadas’, self.__pulgadas.trace(‘w’,
self.pulgadasEntry.focus() self.calcular), para detectar cambios en los valores ingresados. Si se producen cambios se
self.__ventana.mainloop() llama a la función 'self.calcular' para validación y para calcular el equivalente a centímetros
de las pulgadas ingresadas.
36
Menú de opciones (I)
Menús de opciones
Los menús pueden construirse agrupando en una barra de menú varios submenús
desplegables, mediante menús basados en botones, o bien, utilizando los típicos menús
contextuales que cambian sus opciones disponibles dependiendo del lugar donde se
activan en la ventana de la aplicación.
Entre los tipos de opciones que se pueden incluir en un menú se encuentran aquellas
que su estado representan un valor lógico de activada o desactivada (add_checkbutton);
las que permiten elegir una opción de entre varias existentes (add_radiobutton) y las
que ejecutan directamente un método o una función (add_command).
Las opciones de un menú pueden incluir iconos y asociarse a atajos o combinaciones de
teclas que surten el mismo efecto que si éstas son seleccionadas con un clic de ratón.
También, en un momento dado, pueden deshabilitarse para impedir que puedan ser
seleccionadas.
37
Menú de opciones (II)
import tkinter as tk def acercaDe(self, *args):
from tkinter import * acerca = Toplevel()
from tkinter import ttk, font acerca.geometry("320x200")
class App(tk.Tk): acerca.resizable(width=False, height=False)
__version='Version 1.0' acerca.title("Acerca de")
def __init__(self): marco1 = ttk.Frame(acerca, padding=(10, 10, 10, 10),
super().__init__() relief=RAISED)
self.fuente = font.Font(weight='normal') marco1.pack(side=TOP, fill=BOTH, expand=True)
barraMenu=Menu(self) etiq2 = Label(marco1, text="APP-Menú "+self.__version,
menuArchivo=Menu(barraMenu, tearoff=0) foreground='blue', font=self.fuente)
menuAyuda=Menu(barraMenu, tearoff=0) etiq2.pack(side=TOP, padx=10)
menuSalir=Menu(barraMenu, tearoff=0) etiq3 = Label(marco1,
menuArchivo.add_command(label="Nuevo", text="Mi Primer APP con Menú")
command=self.nuevo,accelerator="Ctrl+n") etiq3.pack(side=TOP, padx=10)
menuArchivo.add_command(label="Abrir", boton1 = Button(marco1, text="Salir",
command=self.abrir,accelerator='Ctrl+a') command=acerca.destroy)
menuArchivo.add_separator() boton1.pack(side=TOP, padx=10, pady=10)
menuArchivo.add_command(label="Guardar") boton1.focus_set()
menuArchivo.add_command(label="Guardar como...") acerca.transient(self)
menuAyuda.add_command(label='Acerca de...', command=self.acercaDe) self.wait_window(acerca)
menuSalir.add_command(label='Salir Ctrl+q', command=self.destroy) if __name__ == "__main__":
barraMenu.add_cascade(label="Archivo", menu=menuArchivo) app = App()
barraMenu.add_cascade(label="Ayuda", menu=menuAyuda) app.mainloop()
barraMenu.add_cascade(label="Salir", menu=menuSalir)
self.config(menu=barraMenu)
self.bind("<Control-n>",
lambda event: self.nuevo())
self.bind("<Control-a>",
lambda event: self.abrir())
self.bind("<Control-q>",
lambda event: self.destroy())
def nuevo(self):
print('Nuevo')
def abrir(self):
print('Abrir')
38
LabelFrame, RadioButton y CheckButton (I)
39
LabelFrame, RadioButton y CheckButton (II)
from tkinter import ttk, font
import tkinter as tk def cambiaValorMM(self):
class LabelConEstilo(tk.Tk) if self.valorMM.get()==0:
def __init__(self): self.__texto.set(self.__texto.get().upper())
super().__init__() else:
self.title('Ejemplo') if self.valorMM.get()==1:
self.resizable(0,0) self.__texto.set(self.__texto.get().lower())
self.geometry('255x195') def cambiarEstilo(self):
self.config(padx=5, pady=5) subrayado=' underline ' if self.subrayado.get()==True else''
fuente=font.Font(font='Verdana 10',weight='normal') negrita=' bold ' if self.negrita.get()==True else ''
self.valorMM=tk.IntVar() cursiva=' italic ' if self.cursiva.get()==True else ''
self.__texto=tk.StringVar() tachado=' overstrike ' if self.tachado.get()==True else ''
self.__texto.set('Texto ejemplo') fuente='Verdana 10'+subrayado+negrita+cursiva+tachado
# variables booleanas que controlan el estilo del texto self.textoLbl.configure(font=fuente)
self.subrayado=tk.BooleanVar() def testAPP():
self.negrita=tk.BooleanVar() app=LabelConEstilo()
self.cursiva=tk.BooleanVar() app.mainloop()
self.tachado=tk.BooleanVar() if __name__=='__main__':
# Elementos de la ventana testAPP()
self.textoLbl=ttk.Label(self, textvariable=self.__texto, font=fuente)
opts = {'ipadx': 15, 'ipady': 15 , 'sticky': 'nswe'}
self.textoLbl.grid(row=0, column=0, **opts, columnspan=2)
labelFrameSeleccione=tk.LabelFrame(self, text='Seleccione:', font=fuente,borderwidth=2,
relief='raised', padx=5, pady=5)
labelFrameSeleccione.grid(row=1, column=0, **opts)
labelFrameMarque=tk.LabelFrame(self, text='Marque:', font=fuente,borderwidth=2,
relief="raised")
labelFrameMarque.grid(row=1, column=1, **opts)
ttk.Radiobutton(labelFrameSeleccione, text='Mayúsculas', value=0, variable=self.valorMM,
command=self.cambiaValorMM).grid(row =2, column=0, columnspan=1, sticky='w')
ttk.Radiobutton(labelFrameSeleccione, text='Minúsculas', value=1, variable=self.valorMM,
command=self.cambiaValorMM).grid(row =3, column=0, columnspan=1,sticky='w')
ttk.Checkbutton(labelFrameMarque, text='Subrayado',variable=self.subrayado,
command=self.cambiarEstilo).grid(row=2, column=1, columnspan=1, sticky='w')
ttk.Checkbutton(labelFrameMarque, text='Negrita',variable=self.negrita,
command=self.cambiarEstilo).grid(row=3, column=1, columnspan=1, sticky='w')
ttk.Checkbutton(labelFrameMarque, text='Cursiva',variable=self.cursiva,
command=self.cambiarEstilo).grid(row=4, column=1, columnspan=1, sticky='w')
ttk.Checkbutton(labelFrameMarque, text='Tachado', variable=self.tachado,
command=self.cambiarEstilo),grid(row=5, column=1, columnspan=1, sticky='w') 40
self.valorMM.set(-1)
Ejemplo-Calculadora
Para poder implementar la calculadora, es necesario indagar
sobre funciones parciales, provistas en el módulo estándar de
Python, functools.
Partial
Ésta es probablemente una de las funciones más últiles en toda
la librería estándar functools
partial() recibe una función A con sus respectivos argumentos y
retorna una nueva función B que, al ser llamada, equivale a llamar a la función A con los
argumentos provistos.
Será utilizada a la hora de proveer el comando a ejecutar por cada botón del panel de la
calculadora.
ttk.Button(mainframe, text='0', command=partial(self.ponerNUMERO, '0')).grid(column=1, row=6, sticky=W)
ttk.Button(mainframe, text='+', command=partial(self.ponerOPERADOR, '+')).grid(column=2, row=6, sticky=W)
45
El Modelo (III)
import json
from pathlib import Path
from claseManejadorContactos import ManejadorContactos
from claseContacto import Contacto
class ObjectEncoder(object):
__pathArchivo=None
def __init__(self, pathArchivo):
self.__pathArchivo=pathArchivo
def decodificarDiccionario(self, d):
if '__class__' not in d:
return d
else:
class_name=d['__class__']
class_=eval(class_name)
if class_name=='ManejadorContactos':
contactos=d['contactos']
manejador=class_()
for i in range(len(contactos)):
dContacto=contactos[i]
class_name=dContacto.pop('__class__')
class_=eval(class_name)
atributos=dContacto['__atributos__']
unContacto=class_(**atributos)
manejador.agregarContacto(unContacto)
return manejador
def guardarJSONArchivo(self, diccionario):
with Path(self.__pathArchivo).open("w", encoding="UTF-8") as destino:
json.dump(diccionario, destino, indent=4)
destino.close()
def leerJSONArchivo(self):
with Path(self.__pathArchivo).open(encoding="UTF-8") as fuente:
diccionario=json.load(fuente)
fuente.close()
return diccionario
46
El Modelo (IV)
from claseContacto import Contacto
from claseObjectEncoder import ObjectEncoder
from claseManejadorContactos import ManejadorContactos
class RespositorioContactos(object):
__conn=None
__manejador=None
def __init__(self, conn):
self.__conn = conn
diccionario=self.__conn.leerJSONArchivo()
self.__manejador=self.__conn.decodificarDiccionario(diccionario)
def obtenerListaContactos(self):
return self.__manejador.getListaContactos()
def agregarContacto(self, contacto):
self.__manejador.agregarContacto(contacto)
return contacto
def modificarContacto(self, contacto):
self.__manejador.updateContacto(contacto)
return contacto
def borrarContacto(self, contacto):
self.__manejador.deleteContacto(contacto)
def grabarDatos(self):
self.__conn.guardarJSONArchivo(self.__manejador.toJSON())
47
La Vista (I)
import tkinter as tk
from tkinter import messagebox
from claseContacto import Contacto
class ContactList(tk.Frame):
def __init__(self, master, **kwargs):
super().__init__(master)
self.lb = tk.Listbox(self, **kwargs)
scroll = tk.Scrollbar(self, command=self.lb.yview)
self.lb.config(yscrollcommand=scroll.set)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.lb.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
def insertar(self, contacto, index=tk.END):
text = "{}, {}".format(contacto.getApellido(), contacto.getNombre())
self.lb.insert(index, text)
def borrar(self, index):
self.lb.delete(index, index)
def modificar(self, contact, index):
self.borrar(index)
self.insertar(contact, index)
def bind_doble_click(self, callback):
handler = lambda _: callback(self.lb.curselection()[0])
self.lb.bind("<Double-Button-1>", handler)
48
La Vista (II)
class ContactForm(tk.LabelFrame):
fields = ("Apellido", "Nombre", "Email", "Teléfono")
def __init__(self, master, **kwargs):
super().__init__(master, text="Contacto", padx=10, pady=10, **kwargs)
self.frame = tk.Frame(self)
self.entries = list(map(self.crearCampo, enumerate(self.fields)))
self.frame.pack()
def crearCampo(self, field):
position, text = field
label = tk.Label(self.frame, text=text)
entry = tk.Entry(self.frame, width=25)
label.grid(row=position, column=0, pady=5)
entry.grid(row=position, column=1, pady=5)
return entry
def mostrarEstadoContactoEnFormulario(self, contacto):
# a partir de un contacto, obtiene el estado
# y establece en los valores en el formulario de entrada
values = (contacto.getApellido(), contacto.getNombre(),
contacto.getEmail(), contacto.getTelefono())
for entry, value in zip(self.entries, values):
entry.delete(0, tk.END)
entry.insert(0, value)
def crearContactoDesdeFormulario(self):
#obtiene los valores de los campos del formulario
#para crear un nuevo contacto
values = [e.get() for e in self.entries]
contacto=None
try:
contacto = Contacto(*values)
except ValueError as e:
messagebox.showerror("Error de Validación", str(e), parent=self)
return contacto
def limpiar(self):
for entry in self.entries:
entry.delete(0, tk.END) 49
La Vista (III)
class NewContact(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.contacto = None
self.form = ContactForm(self)
self.btn_add = tk.Button(self, text="Confirmar", command=self.confirmar)
self.form.pack(padx=10, pady=10)
self.btn_add.pack(pady=10)
def confirmar(self):
self.contacto = self.form.crearContactoDesdeFormulario()
if self.contacto:
self.destroy()
def show(self):
self.grab_set()
self.wait_window()
return self.contacto
class UpdateContactForm(ContactForm):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
self.btn_save = tk.Button(self, text="Guardar")
self.btn_delete = tk.Button(self, text="Borrar")
self.btn_save.pack(side=tk.RIGHT, ipadx=5, padx=5, pady=5)
self.btn_delete.pack(side=tk.RIGHT, ipadx=5, padx=5, pady=5)
def bind_save(self, callback):
self.btn_save.config(command=callback)
def bind_delete(self, callback):
self.btn_delete.config(command=callback)
50
La Vista (IV)
class ContactsView(tk.Tk):
def __init__(self):
super().__init__()
self.title("Lista de Contactos")
self.list = ContactList(self, height=15)
self.form = UpdateContactForm(self)
self.btn_new = tk.Button(self, text="Agregar Contacto")
self.list.pack(side=tk.LEFT, padx=10, pady=10)
self.form.pack(padx=10, pady=10)
self.btn_new.pack(side=tk.BOTTOM, pady=5)
def setControlador(self, ctrl):
#vincula la vista con el controlador
self.btn_new.config(command=ctrl.crearContacto)
self.list.bind_doble_click(ctrl.seleccionarContacto)
self.form.bind_save(ctrl.modificarContacto)
self.form.bind_delete(ctrl.borrarContacto)
def agregarContacto(self, contacto):
self.list.insertar(contacto)
def modificarContacto(self, contacto, index):
self.list.modificar(contacto, index)
def borrarContacto(self, index):
self.form.limpiar()
self.list.borrar(index)
#obtiene los valores del formulario y crea un nuevo contacto
def obtenerDetalles(self):
return self.form.crearContactoDesdeFormulario()
#Ver estado de Contacto en formulario de contactos
def verContactoEnForm(self, contacto):
self.form.mostrarEstadoContactoEnFormulario(contacto)
51
El Controlador (I)
from vistaContactos import ContactsView, NewContact def start(self):
from claseManejadorContactos import ManejadorContactos for c in self.contactos:
class ControladorContactos(object): self.vista.agregarContacto(c)
def __init__(self, repo, vista): self.vista.mainloop()
self.repo = repo def salirGrabarDatos(self):
self.vista = vista self.repo.grabarDatos()
self.seleccion = -1
self.contactos = list(repo.obtenerListaContactos())
# comandos que se ejecutan a través de la vista
def crearContacto(self):
nuevoContacto = NewContact(self.vista).show() # PROGRAMA PRINCIPAL
if nuevoContacto: from claseRepositorioContactosJSON import
contacto = self.repo.agregarContacto(nuevoContacto) RespositorioContactos
self.contactos.append(contacto) from vistaContactos import ContactsView
self.vista.agregarContacto(contacto) from claseControladorContactos import ControladorContactos
def seleccionarContacto(self, index): from claseObjectEncoder import ObjectEncoder
self.seleccion = index def main():
contacto = self.contactos[index] conn=ObjectEncoder('contactos.json')
self.vista.verContactoEnForm(contacto) repo=RespositorioContactos(conn)
def modificarContacto(self): vista=ContactsView()
if self.seleccion==-1: ctrl=ControladorContactos(repo, vista)
return vista.setControlador(ctrl)
rowid = self.contactos[self.seleccion].rowid ctrl.start()
detallesContacto = self.vista.obtenerDetalles() ctrl.salirGrabarDatos()
detallesContacto.rowid = rowid if __name__ == "__main__":
contacto = self.repo.modificarContacto(detallesContacto) main()
self.contactos[self.seleccion] = contacto
self.vista.modificarContacto(contacto, self.seleccion)
self.seleccion=-1
def borrarContacto(self):
if self.seleccion==-1:
return
contacto = self.contactos[self.seleccion]
self.repo.borrarContacto(contacto)
self.contactos.pop(self.seleccion)
self.vista.borrarContacto(self.seleccion)
self.seleccion=-1
52