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):

Código:
import tkinter as tk
from tkinter import ttk
from datetime import *
from tkinter import messagebox
import time
import calendar
import psycopg2
import threading
from threading import Timer
import os
import sys
import tkinter.font as tkfont
from functools import partial
import subprocess
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
import Pmw



class Phone:

     def __init__(self):

######## MAIN WINDOW #########

         self.win01 = tk.Tk()
         self.win01.title('Prueba Botón Llamada')
         self.win01.geometry('600x400+0+0')

########## GRAL VARIABLES ###########

         self.fila = 0
         self.columna = 0


######## GRAL FUNTIONS ##########

         def addbot():

             self.fila += 1

             self.botgr = ttk.Button(self.win01, text='925' + str(self.fila))
             self.botgr.grid(row=self.fila, column = 0)


# Entry para imprimir números

         self.ent01 = ttk.Entry(self.win01, justify= tk.CENTER, font=("DejaVu Sans Condensed",20,'bold'))
         self.ent01.grid(row = 0, column=1)


# Botón para añadir botones.

         self.bot01 = ttk.Button(self. win01, command=addbot, text='ADD')
         self.bot01.grid(row=0, column = 0, padx=5, pady=5)

         self.win01.mainloop()



def main():

myapp = Phone()

if __name__ == '__main__':
    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í:

Código:
from tkinter import messagebox, ttk
import tkinter as tk


class MyButton(ttk.Button):
    
    def __init__(self, *args, **kwargs):
        if "command" not in kwargs:
            kwargs["command"] = self.default_command
        super().__init__(*args, **kwargs)
    
    def default_command(self):
        messagebox.showinfo(title="Número", message=self["text"])


root = tk.Tk()
root.config(width=400, height=300)

button1 = MyButton(text="1234")
button1.place(x=30, y=70)

root.mainloop()

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

Saludos!
¡No te pierdas nuestro curso oficial en Udemy para aprender Python, bases de datos SQL, orientación a objetos, tkinter y mucho más!

También ofrecemos consultoría profesional de desarrollo en Python para personas y empresas.
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.



Código:
import tkinter as tk

from tkinter import ttk

from datetime import *

from tkinter import messagebox

import time

import calendar

import psycopg2

import threading

from threading import Timer

import os

import sys

import tkinter.font as tkfont

from functools import partial

import subprocess

import smtplib

from email.mime.multipart import MIMEMultipart

from email.mime.text import MIMEText

from email.mime.base import MIMEBase

from email import encoders

import Pmw



class Button():



     def hola():



         print("HOLA MUNDO")
         self.ent01.insert(0, "HOLA MUNDO")






class Phone:



     def __init__(self):



######## MAIN WINDOW #########



         self.win01 = tk.Tk()

         self.win01.title('Prueba Botón Llamada')

         self.win01.geometry('600x400+0+0')



########## GRAL VARIABLES ###########



         self.fila = 0

         self.columna = 0





######## GRAL FUNTIONS ##########



         def addbot():



             self.fila += 1



             self.botgr = ttk.Button(self.win01, text='925' + str(self.fila), command=Button.hola)

             self.botgr.grid(row=self.fila, column = 0)





# Entry para imprimir números



         self.ent01 = ttk.Entry(self.win01, justify= tk.CENTER, font=("DejaVu Sans Condensed",20,'bold'))

         self.ent01.grid(row = 0, column=1)





# Botón para añadir botones.



         self.bot01 = ttk.Button(self. win01, command=addbot, text='ADD')

         self.bot01.grid(row=0, column = 0, padx=5, pady=5)



         self.win01.mainloop()







def main():



myapp = Phone()



if __name__ == '__main__':

    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:

Código:
class Button():

     def hola():
         print("HOLA MUNDO")
         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!
¡No te pierdas nuestro curso oficial en Udemy para aprender Python, bases de datos SQL, orientación a objetos, tkinter y mucho más!

También ofrecemos consultoría profesional de desarrollo en Python para personas y empresas.
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:





Código:
import tkinter as tk

from tkinter import ttk



class App:



     def __init__(self):





         self.root = tk.Tk()

         self.root.title("PRUEBAS")

         self.root.geometry('300x200')



         def mostrar():



             self.ent.delete(0, tk.END)

             self.ent.insert(0, self.boton.cget('text'))



         def mostrar1():



             self.ent.delete(0, tk.END)

             self.ent.insert(0, self.boton1.cget('text'))





         self.boton = ttk.Button(self.root, text='925', command=mostrar)

         self.boton.grid(row=0, column=0, pady=5)



         self.boton1 = ttk.Button(self.root, text='926', command=mostrar1)

         self.boton1.grid(row=1, column=0, pady=5)



         self.boton2 = ttk.Button(self.root, text='927')

         self.boton2.grid(row=2, column=0, pady=5)



         self.ent = ttk.Entry(self.root, width=5)

         self.ent.grid(row=0, column=1, padx=10)



         self.root.mainloop()



def main():

my_app = App()



if __name__ == '__main__':

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():

Código:
import tkinter as tk
from tkinter import ttk
from functools import partial


class App:

    def __init__(self):
        self.root = tk.Tk()
        self.root.title("PRUEBAS")
        self.root.geometry('300x200')
        
        def mostrar_generico(boton):
            self.ent.delete(0, tk.END)
            self.ent.insert(0, boton.cget('text'))

        self.boton = ttk.Button(self.root, text='925')
        self.boton["command"] = partial(mostrar_generico, self.boton)
        self.boton.grid(row=0, column=0, pady=5)

        self.boton1 = ttk.Button(self.root, text='926')
        self.boton1["command"] = partial(mostrar_generico, self.boton1)
        self.boton1.grid(row=1, column=0, pady=5)

        self.boton2 = ttk.Button(self.root, text='927')
        self.boton2.grid(row=2, column=0, pady=5)

        self.ent = ttk.Entry(self.root, width=5)
        self.ent.grid(row=0, column=1, padx=10)

        self.root.mainloop()


def main():
    my_app = App()

if __name__ == '__main__':
    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:

Código:
import tkinter as tk
from tkinter import ttk


class MyButton(ttk.Button):

    def __init__(self, *args, entry=None, **kwargs):
        if "command" not in kwargs:
            kwargs["command"] = self.default_command
        self.entry = entry
        super().__init__(*args, **kwargs)

    def default_command(self):
        self.entry.delete(0, tk.END)
        self.entry.insert(0, self.cget('text'))


class App:

    def __init__(self):
        self.root = tk.Tk()
        self.root.title("PRUEBAS")
        self.root.geometry('300x200')

        self.ent = ttk.Entry(self.root, width=5)
        self.ent.grid(row=0, column=1, padx=10)

        self.boton = MyButton(self.root, text='925', entry=self.ent)
        self.boton.grid(row=0, column=0, pady=5)

        self.boton1 = MyButton(self.root, text='926', entry=self.ent)
        self.boton1.grid(row=1, column=0, pady=5)

        self.boton2 = ttk.Button(self.root, text='927')
        self.boton2.grid(row=2, column=0, pady=5)

        self.root.mainloop()


def main():
    my_app = App()

if __name__ == '__main__':
    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
¡No te pierdas nuestro curso oficial en Udemy para aprender Python, bases de datos SQL, orientación a objetos, tkinter y mucho más!

También ofrecemos consultoría profesional de desarrollo en Python para personas y empresas.
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)