Calificación:
  • 0 voto(s) - 0 Media
  • 1
  • 2
  • 3
  • 4
  • 5
Esquema de clases para python/tkinter
#1
Buenas aquí sigo con mi app. Esto ya esta muy muy avanzado y claro van surgiendo otras necesidades y otros problemas.

Estoy "empezado" con las clases y tengo este código(Es una prueba, no hagáis caso de los import ya que son para la app):

  1. import tkinter as tk
  2. from tkinter import ttk
  3. from datetime import *
  4. from tkinter import messagebox
  5. import time
  6. import calendar
  7. import psycopg2
  8. import threading
  9. from threading import Timer
  10. import os
  11. import sys
  12. import tkinter.font as tkfont
  13. from functools import partial
  14. import subprocess
  15. import smtplib
  16. from email.mime.multipart import MIMEMultipart
  17. from email.mime.text import MIMEText
  18. from email.mime.base import MIMEBase
  19. from email import encoders
  20. import Pmw
  21.  
  22.  
  23.  
  24. class Phone:
  25.  
  26.      def __init__(self):
  27.  
  28. ######## MAIN WINDOW #########
  29.  
  30.          self.win01 = tk.Tk()
  31.          self.win01.title('Prueba Botón Llamada')
  32.          self.win01.geometry('600x400+0+0')
  33.  
  34. ########## GRAL VARIABLES ###########
  35.  
  36.          self.fila = 0
  37.          self.columna = 0
  38.  
  39.  
  40. ######## GRAL FUNTIONS ##########
  41.  
  42.          def addbot():
  43.  
  44.              self.fila += 1
  45.  
  46.              self.botgr = ttk.Button(self.win01, text='925' + str(self.fila))
  47.              self.botgr.grid(row=self.fila, column = 0)
  48.  
  49.  
  50. # Entry para imprimir números
  51.  
  52.          self.ent01 = ttk.Entry(self.win01, justify= tk.CENTER, font=("DejaVu Sans Condensed",20,'bold'))
  53.          self.ent01.grid(row = 0, column=1)
  54.  
  55.  
  56. # Botón para añadir botones.
  57.  
  58.          self.bot01 = ttk.Button(self. win01, command=addbot, text='ADD')
  59.          self.bot01.grid(row=0, column = 0, padx=5, pady=5)
  60.  
  61.          self.win01.mainloop()
  62.  
  63.  
  64.  
  65. def main():
  66.  
  67. myapp = Phone()
  68.  
  69. if __name__ == '__main__':
  70.     main()


Bien, todo funciona perfectamente. Con el botón ADD vas añadiendo botones los cuales muetran un número que se va incrementando. Ahora quiero que cada botón una vez pulsado me imprima en el entry el número reflejado en dicho botón.(He pensado que lo mejor y más "pythoniano" para no repetirse es añadir una clase con un método que me permita esto heredando dicho método al pulsar cada  botón(No se si me explico bien)

1º Duda: No tengo muy claro dónde tengo que poner la nueva clase (class). Fuera de la principal o dentro.(Lo he probado y no me ha funcionado ninguna. Algo estoy haciendo mal).
Responder
#2
Hola. Podrías usar algo así:

  1. from tkinter import messagebox, ttk
  2. import tkinter as tk
  3.  
  4.  
  5. class MyButton(ttk.Button):
  6.  
  7. def __init__(self, *args, **kwargs):
  8. if "command" not in kwargs:
  9. kwargs["command"] = self.default_command
  10. super().__init__(*args, **kwargs)
  11.  
  12. def default_command(self):
  13. messagebox.showinfo(title="Número", message=self["text"])
  14.  
  15.  
  16. root = tk.Tk()
  17. root.config(width=400, height=300)
  18.  
  19. button1 = MyButton(text="1234")
  20. button1.place(x=30, y=70)
  21.  
  22. root.mainloop()


En tu caso la definición de la clase iría fuera de la clase principal.

Saludos!
Responder
#3
Muchísimas Gracias Francisco.



El problema era que al añadir la clase lo hacía con el __init__ y no sé por qué no funcionaba. Me salía un error de que determinada clase no tenía el atributo(método).



Si creo la clase dónde solo meto el método(un método simple como print algo) si no añado el __init__ sí que funciona y no sé por qué. Tengo un poco atragantado esto de las clases.



Vamos a ir poco a poco (También necesitaré un poco de explicación de los argumentos *args y **kwargs, que los veo en montones de sitios y he leído algunas explicaciones pero no me queda claro su uso.



Os pongo el ejemplo sencillo que funciona.



  1. import tkinter as tk
  2.  
  3. from tkinter import ttk
  4.  
  5. from datetime import *
  6.  
  7. from tkinter import messagebox
  8.  
  9. import time
  10.  
  11. import calendar
  12.  
  13. import psycopg2
  14.  
  15. import threading
  16.  
  17. from threading import Timer
  18.  
  19. import os
  20.  
  21. import sys
  22.  
  23. import tkinter.font as tkfont
  24.  
  25. from functools import partial
  26.  
  27. import subprocess
  28.  
  29. import smtplib
  30.  
  31. from email.mime.multipart import MIMEMultipart
  32.  
  33. from email.mime.text import MIMEText
  34.  
  35. from email.mime.base import MIMEBase
  36.  
  37. from email import encoders
  38.  
  39. import Pmw
  40.  
  41.  
  42.  
  43. class Button():
  44.  
  45.  
  46.  
  47.      def hola():
  48.  
  49.  
  50.  
  51.          print("HOLA MUNDO")
  52.          self.ent01.insert(0, "HOLA MUNDO")
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59. class Phone:
  60.  
  61.  
  62.  
  63.      def __init__(self):
  64.  
  65.  
  66.  
  67. ######## MAIN WINDOW #########
  68.  
  69.  
  70.  
  71.          self.win01 = tk.Tk()
  72.  
  73.          self.win01.title('Prueba Botón Llamada')
  74.  
  75.          self.win01.geometry('600x400+0+0')
  76.  
  77.  
  78.  
  79. ########## GRAL VARIABLES ###########
  80.  
  81.  
  82.  
  83.          self.fila = 0
  84.  
  85.          self.columna = 0
  86.  
  87.  
  88.  
  89.  
  90.  
  91. ######## GRAL FUNTIONS ##########
  92.  
  93.  
  94.  
  95.          def addbot():
  96.  
  97.  
  98.  
  99.              self.fila += 1
  100.  
  101.  
  102.  
  103.              self.botgr = ttk.Button(self.win01, text='925' + str(self.fila), command=Button.hola)
  104.  
  105.              self.botgr.grid(row=self.fila, column = 0)
  106.  
  107.  
  108.  
  109.  
  110.  
  111. # Entry para imprimir números
  112.  
  113.  
  114.  
  115.          self.ent01 = ttk.Entry(self.win01, justify= tk.CENTER, font=("DejaVu Sans Condensed",20,'bold'))
  116.  
  117.          self.ent01.grid(row = 0, column=1)
  118.  
  119.  
  120.  
  121.  
  122.  
  123. # Botón para añadir botones.
  124.  
  125.  
  126.  
  127.          self.bot01 = ttk.Button(self. win01, command=addbot, text='ADD')
  128.  
  129.          self.bot01.grid(row=0, column = 0, padx=5, pady=5)
  130.  
  131.  
  132.  
  133.          self.win01.mainloop()
  134.  
  135.  
  136.  
  137.  
  138.  
  139.  
  140.  
  141. def main():
  142.  
  143.  
  144.  
  145. myapp = Phone()
  146.  
  147.  
  148.  
  149. if __name__ == '__main__':
  150.  
  151.     main()


Bien, aquí funciona excepto al intentar insertar algo en el Entry que me sale el siguiente error:

Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/lib/python3.7/tkinter/__init__.py", line 1705, in __call__
    return self.func(*args)
  File "pythonbuttonclass.py", line 27, in hola
    self.ent01.insert(0, "HOLA MUNDO")
NameError: name 'self' is not defined

Creo que tiene que ver con la herencia de una clase a otra.
Responder
#4
Hola. Tal vez te faltaría un poco más de información sobre la orientación a objetos. Te paso este artículo que escribí hace unos años (del cual no me siento muy orgulloso, así que sentite libre de buscar algún otro también): https://recursospython.com/guias-y-manua...a-objetos/.

Yendo a tu ejemplo:

  1. class Button():
  2.  
  3. def hola():
  4. print("HOLA MUNDO")
  5. self.ent01.insert(0, "HOLA MUNDO")


Acá no hay ninguna diferencia con definir hola() fuera de la clase. Solo que en este caso accedes a la función vía Button.hola(). Es natural que al llamar a la función Python arroje un error diciendo que "self" no existe, ya que ese objeto está dentro de la función Phone.__init__(). Las funciones tienen ámbitos privados para sus variables; una variable definida dentro de una función no puede ser accedida fuera de ella. Si querés que hola() tenga acceso al self de la otra función, tenés que pasárselo como argumento, aquí probablemente con un partial(). Pero en ese caso no veo la necesidad de usar clases, con una función ya basta.

Sobre *args y **kwargs te dejo este artículo: https://recursospython.com/guias-y-manua...gs-kwargs/.

Saludos!
Responder
#5
Muchas Gracias Francisco!!!!!. Me había leído el artículo je je je.(Me he leído casi todos aunque el de *args y **kwargs no lo había visto ¡Está genial!).



Bueno en cuanto a mi problema en realidad no es tan grave puesto que aunque los puristas pythonianos inciden mucho en aquello de Don't you repeat, simple better than complex etc etc de momento me conformo con que la aplicación funciona y poco a poco lo estoy consiguiendo.



El tema de liarme ahora con las clases es para avanzar un poco y controlar todos los aspectos de python pero bueno no llevo prisa.



Creo que incluso lo que quiero hacer se podría hacer simplemente con una función.



Te pongo un ejemplo muy sencillito y te digo que es lo que pretendo:





  1. import tkinter as tk
  2.  
  3. from tkinter import ttk
  4.  
  5.  
  6.  
  7. class App:
  8.  
  9.  
  10.  
  11.      def __init__(self):
  12.  
  13.  
  14.  
  15.  
  16.  
  17.          self.root = tk.Tk()
  18.  
  19.          self.root.title("PRUEBAS")
  20.  
  21.          self.root.geometry('300x200')
  22.  
  23.  
  24.  
  25.          def mostrar():
  26.  
  27.  
  28.  
  29.              self.ent.delete(0, tk.END)
  30.  
  31.              self.ent.insert(0, self.boton.cget('text'))
  32.  
  33.  
  34.  
  35.          def mostrar1():
  36.  
  37.  
  38.  
  39.              self.ent.delete(0, tk.END)
  40.  
  41.              self.ent.insert(0, self.boton1.cget('text'))
  42.  
  43.  
  44.  
  45.  
  46.  
  47.          self.boton = ttk.Button(self.root, text='925', command=mostrar)
  48.  
  49.          self.boton.grid(row=0, column=0, pady=5)
  50.  
  51.  
  52.  
  53.          self.boton1 = ttk.Button(self.root, text='926', command=mostrar1)
  54.  
  55.          self.boton1.grid(row=1, column=0, pady=5)
  56.  
  57.  
  58.  
  59.          self.boton2 = ttk.Button(self.root, text='927')
  60.  
  61.          self.boton2.grid(row=2, column=0, pady=5)
  62.  
  63.  
  64.  
  65.          self.ent = ttk.Entry(self.root, width=5)
  66.  
  67.          self.ent.grid(row=0, column=1, padx=10)
  68.  
  69.  
  70.  
  71.          self.root.mainloop()
  72.  
  73.  
  74.  
  75. def main():
  76.  
  77. my_app = App()
  78.  
  79.  
  80.  
  81. if __name__ == '__main__':
  82.  
  83. main()


Bien en el ejemplo tenemos un app con tres botones. La idea es que cada botón al ser clickeado inserte en el entry el texto que tiene. Como pongo en el ejemplo se puede conseguir de manera fácil creando una función para cada botón. La cuestión es que me gustaría saber como crear una única función para los tres botones y no tener que crear tres que son exactamente iguales y no repetirme.(Solo cambia la variable que define el botón en cada caso). Penśe que tendría que ser una clase pero pienso que a lo mejor con una simple función se puede hacer y es mucho más sencillo.

P.D. Vuelvo a decir que poniéndo las tres funciones(Una para cada botón) funciona todo perfectamente y es muy sencillo. Pero me estoy obsesinando con el no te repitas y a lo mejor es un error).

Gracias de nuevo por todo.
Responder
#6
¡De nada! Está perfecto que busques optimizar tu código evitando la repetición de código, y efectivamente las clases cumplen en gran medida ese propósito.

Con respecto a este último ejemplo, se podría solucionar creando una función genérica y usando functools.partial():

  1. import tkinter as tk
  2. from tkinter import ttk
  3. from functools import partial
  4.  
  5.  
  6. class App:
  7.  
  8. def __init__(self):
  9. self.root = tk.Tk()
  10. self.root.title("PRUEBAS")
  11. self.root.geometry('300x200')
  12.  
  13. def mostrar_generico(boton):
  14. self.ent.delete(0, tk.END)
  15. self.ent.insert(0, boton.cget('text'))
  16.  
  17. self.boton = ttk.Button(self.root, text='925')
  18. self.boton["command"] = partial(mostrar_generico, self.boton)
  19. self.boton.grid(row=0, column=0, pady=5)
  20.  
  21. self.boton1 = ttk.Button(self.root, text='926')
  22. self.boton1["command"] = partial(mostrar_generico, self.boton1)
  23. self.boton1.grid(row=1, column=0, pady=5)
  24.  
  25. self.boton2 = ttk.Button(self.root, text='927')
  26. self.boton2.grid(row=2, column=0, pady=5)
  27.  
  28. self.ent = ttk.Entry(self.root, width=5)
  29. self.ent.grid(row=0, column=1, padx=10)
  30.  
  31. self.root.mainloop()
  32.  
  33.  
  34. def main():
  35. my_app = App()
  36.  
  37. if __name__ == '__main__':
  38. main()


(Necesariamente el command lo tengo que indicar una línea despúes de crear el botón, para poder tener la referencia).

Pero también se puede solucionar creando una nueva clase para esos botones, que herede toda la funcionalidad de ttk.Button, como el código que te pasé antes:

  1. import tkinter as tk
  2. from tkinter import ttk
  3.  
  4.  
  5. class MyButton(ttk.Button):
  6.  
  7. def __init__(self, *args, entry=None, **kwargs):
  8. if "command" not in kwargs:
  9. kwargs["command"] = self.default_command
  10. self.entry = entry
  11. super().__init__(*args, **kwargs)
  12.  
  13. def default_command(self):
  14. self.entry.delete(0, tk.END)
  15. self.entry.insert(0, self.cget('text'))
  16.  
  17.  
  18. class App:
  19.  
  20. def __init__(self):
  21. self.root = tk.Tk()
  22. self.root.title("PRUEBAS")
  23. self.root.geometry('300x200')
  24.  
  25. self.ent = ttk.Entry(self.root, width=5)
  26. self.ent.grid(row=0, column=1, padx=10)
  27.  
  28. self.boton = MyButton(self.root, text='925', entry=self.ent)
  29. self.boton.grid(row=0, column=0, pady=5)
  30.  
  31. self.boton1 = MyButton(self.root, text='926', entry=self.ent)
  32. self.boton1.grid(row=1, column=0, pady=5)
  33.  
  34. self.boton2 = ttk.Button(self.root, text='927')
  35. self.boton2.grid(row=2, column=0, pady=5)
  36.  
  37. self.root.mainloop()
  38.  
  39.  
  40. def main():
  41. my_app = App()
  42.  
  43. if __name__ == '__main__':
  44. main()


La clave acá está en que creo una nueva clase MyButton que hereda de ttk.Button, de modo que al crear un MyButton, puedo pasarle los mismos argumentos que a un ttk.Button (de ahí el uso de *args y **kwargs), con el agregado de "entry", que es un argumento propio de MyButton e indica la caja de texto en la cual el botón mostrará el texto al ser presionado.

Saludos
Responder
#7
¡Muchas Gracias!!. Esto tengo que estudiarlo que veo que me va a hacer falta.

GRACIAS DE NUEVO!!!!
Responder


Salto de foro:


Usuarios navegando en este tema: 1 invitado(s)