Calificación:
  • 0 voto(s) - 0 Media
  • 1
  • 2
  • 3
  • 4
  • 5
[tkinter] Importar al programa principal las funciones de otro modulo
#1
Hola otra vez!  Big Grin

Pues cuando creia que era sencillo, no logro resolver esta situacion...

A medida que creamos una GUI con tkinter, es normal que usemos funciones propias que son llamadas por los propios command de los widgets creados.
Pero claro, el programa principal se empieza a llenar de esas funciones y widgets. Creia que era tan sencillo como crear esas funciones en otro modulo y que fueran importada por el programa principal. Pero me da errores. He intentado, incluso, la importacion de tkinter y algunas de sus clases en los modulos que se importan. Pero no logro solucionarlo. Pongo un ejemplo muy simple para ver si asi se entiende mejor:

Modulo principal:

Código:
from tkinter import Tk, Button, Label
from funciones import saludo


# PROGRAMA PRINCIPAL
ventana = Tk()
ventana.geometry('300x300')

btn_saludo = Button(ventana, text = 'Saluda', command = saludo)
btn_saludo.place(x = 10, y = 50)



ventana.mainloop()

Modulo que se importa con la funcion:

Código:
from tkinter import Label


# MODULO FUNCIONES
def saludo():
   lbl_saludo = Label(ventana, text = 'Hola')
   lbl_saludo.place(x = 10, y = 10)

Y claro, me da el tipico error:

Exception in Tkinter callback
Traceback (most recent call last): 
    lbl_saludo = Label(ventana, text = 'Hola')
NameError: name 'ventana' is not defined


Puedo entender por donde va la cosa. Pero no se como configurar los modulos para no sobrecargar de codigo el programa principal. Entiendo que 'ventana' no esta en el modulo que importamos. Pero si se encuentra creada en el modulo principal. Al hacer la importacion del modulo funciones es cuando se da cuenta que no existe ventana en dicho modulo.

Muchas gracias por adelantado!
Responder
#2
Hola, puedes resolverlo pasando como argumento la ventana a las funciones de tu módulo, a través de functools.partial():

  1. from functools import partial
  2.  
  3. btn_saludo = Button(ventana, text='Saluda', command=partial(saludo, ventana))


Luego tu función debe aceptar ese argumento:

  1. def saludo(ventana):
  2. lbl_saludo = Label(ventana, text = 'Hola')
  3. lbl_saludo.place(x = 10, y = 10)


Saludos!
Responder
#3
Lo primero de todo ¡Feliz año nuevo!  Smile

Pues una vez mas, gracias! 

Funciona a la perfeccion. Lo que me extraña que esto no este mas detallado o explicado en los manuales o guias de tkinter. Y mas cuando suelen recomendar modular en varios scripts los codigos mas extensos. Creo que nunca hubiera dado con la solucion. Incluso en tu blog hay una entrada que explica este modulo a la perfeccion. Pero creo, yo no hubiera imaginado su uso para la duda que tenia.

Esta es la entrada de tu blog:

https://recursospython.com/guias-y-manuales/functools/

Ahora me tocara investigar cuando la funcion que importamos al modulo principal admite argumentos. Supongo que habra que recurrir a lambda para ese fin. 

¡Un saludo y feliz 2019!
Responder
#4
Me alegro que te haya servido! Un muy feliz año para vos también Wink

Saludos
Responder
#5
Buenas:

Ya que veo que el tema está ya hecho pues continuo con el aquí.

Yo también necesito importar funciones dentro de una app principal (clase).

He probado lo que aquí pone, pero no lo he conseguido. (Si pongo todo el código dentro de la misma app funciona sin problemas pero claro dado que voy a necesitar muchísimas funciones (Que en realidad son Toplevels que a su vez interactuan con la ventana principal), pero me sigue saliendo el error de que el nombre no está definido). Estoy teniendo muchos problemas con el sistema de imports y tal vez se deba a eso pero no consigo hacer que esto funcione.

Lo intenté hacer mediante clases con la ayuda de Francisco de este foro pero dado que las clases debían interactuar con atributos de la principal me fue imposible lograr que funcionase correctamente y recurrí a meter todo mediante funciones dentro de la misma app que funciona muy bien pero cuando la cosa vaya a más será un auténtico caos de código. (Por eso quiero dividir las distintas funciones (Toplevels) en módulos y así que sea más legible.

No se como mandar el código entero porque es bastante grande. Si puedo mandar el inicio de ambos módulos.


Clase principal, (La app):

from tkinter import *
from tkinter import ttk
from datetime import *
from tkinter import messagebox
import time
import calendar
import threading
from threading import Timer
import psycopg2
import os
import sys
from functools import partial
sys.path.append('/home/juan/Templates/Python/AMBULANCIAS/ALBACETE')
from SAB01 import *

class Ccl:
    
    def __init__(self):
        
        
        # MAIN WINDOW (With Scrollbar)
        
        self.root = Tk()
        self.root.title("CENTRO DE CONTROL LOGÍSTICO")
        self.root.geometry('1680x1050')
        
        
        
        self.frame = ttk.Frame(self.root, width=1680, height=1050)
        self.frame.grid(row=0, column=0)
        
        self.scroll = ttk.Scrollbar(self.frame, orient='vertical')
        self.scroll.grid(row=0, column=1, sticky=N+S)
        
        self.canvas = Canvas(self.frame,  scrollregion=(0,0,1250,1250), background='#1A1A1F', width=1660, height=985, yscrollcommand=self.scroll.set)
        self.canvas.grid(row=0, column=0, sticky=N+S+E+W)
        
        self.scroll.configure(command=self.canvas.yview)
        
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))
        
        self.frame_can = ttk.Frame(self.canvas, style="BG.TFrame")
        
        self.canvas.create_window(0,0, window=self.frame_can, anchor='nw')



Las funciones que quiero que pueda ejecutar dicha app desde otro módulo:

from tkinter import *
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
sys.path.append('/home/juan/Templates/Python/APP112')
from CCLprobando import *




def openab01():
    
    self.AB01.configure(state=["disabled"])  ### ESTO ES UN ATRIBUTO DE LA CLASE PRINCIPAL, UN BOTÓN PARA SER EXACTOS QUE SE DEBE DESHABILITAR CUANDO SE ABRA EL TOPLEVEL A CONTINUACIÓN.
            
    self.topab01 = Toplevel()
    self.topab01.title("CENTRO DE CONTROL DE DATOS LOGÍSTICOS")
    self.topab01.geometry("1680x1050")
            
    # PANEL 1
            
    # FRAMES PANEL 1
            
    self.box0 = ttk.Frame(self.topab01)
    self.box0.grid(row=0, column=0)
            
    self.box1 = ttk.Frame(self.topab01)
    self.box1.grid(row=0, column=1)
            
    self.box2 = ttk.Frame(self.topab01)
    self.box2.grid(row=0, column=2)
            
    self.box3 = ttk.Frame(self.topab01)
    self.box3.grid(row=1, column=0)
            
    self.box4 = ttk.Frame(self.topab01)
    self.box4.grid(row=1, column=1)
        
    self.box5 = ttk.Frame(self.topab01)
    self.box5.grid(row=1, column=2)

Olvide poner el error que me da:

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 "/home/juan/Templates/Python/AMBULANCIAS/ALBACETE/SAB01.py", line 19, in openab01
    self.AB01.configure(state=["disabled"])
NameError: name 'self' is not defined
Responder
#6
Hola. Primero deberías modificar la declaración de tu función para que acepte el argumento self:

  1. def openab01(self):
  2. # ...


Luego simplemente recuerda pasar ese argumento cuando llames a la función desde la clase principal.

Saludos
Responder
#7
(18-02-2019, 11:49 AM)Francisco escribió: Hola. Primero deberías modificar la declaración de tu función para que acepte el argumento self:

  1. def openab01(self):
  2.    # ...


Luego simplemente recuerda pasar ese argumento cuando llames a la función desde la clase principal.

Saludos
Muchas gracias. Ahora sí funciona perfectamente.
Responder


Salto de foro:


Usuarios navegando en este tema: 1 invitado(s)