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

Código:
from functools import partial

btn_saludo = Button(ventana, text='Saluda', command=partial(saludo, ventana))

Luego tu función debe aceptar ese argumento:

Código:
def saludo(ventana):
   lbl_saludo = Label(ventana, text = 'Hola')
   lbl_saludo.place(x = 10, y = 10)

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

Código:
def openab01(self):
    # ...

Luego simplemente recuerda pasar ese argumento cuando llames a la función desde 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
#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:

Código:
def openab01(self):
   # ...

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

Saludos
Muchas gracias. Ahora sí funciona perfectamente.
Responder
#8
(19-02-2019, 10:18 AM)Myszowor escribió:
(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:

Código:
def openab01(self):
   # ...

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

Saludos
Muchas gracias. Ahora sí funciona perfectamente.
Buenas, me ha surgido un problemilla con este tema al variar la app que estoy haciendo. La app es una class y los distintos modulos son funciones que abren otras ventanas. Os pongo el ejemplo:

Código:
# La app que es la class:

class app112:
    
    def __init__(self):
        
        self.prinlog = Tk()
        self.prinlog.title("BIENVENID@S A PEREGRINO")
        self.prinlog.geometry('800x600+500+200')
        self.prinlog.resizable(False, False)
        
        # VARIABLES LOGIN
        
        self.CheckMAP = BooleanVar()
        self.CheckMAP.set(True)
        self.CheckCCD = BooleanVar()
        self.CheckCCD.set(True)
        self.CheckCCL = BooleanVar()
        self.CheckCCL.set(True)
        self.CheckCCC = BooleanVar()
        self.CheckCCC.set(True)

            if self.CheckCCL.get() == True:                   # con esto consigo que se abra sin problemas la función openCCL que está en otro módulo.
                AB01 = partial(openCCL, self)
                AB01()    
            
            elif self.CheckCCL.get() == False:
                pass


def openCCL(self):                                                 # Esta es la función que se abre desde la class app
        
        
    # 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')

  
    self.AB01 = ttk.Button(self.frame_can1, text="SVB ALB1",style="OPB.TButton", cursor='hand2', command=partial(openab01, self))
    self.AB01.grid(row=1, column=0)


# Aquí llega el problema: Cuando le doy al botón self.AB01 me dice que el nombre openab01 no existe. #Dicha función está en otro módulo.

def openab01(self):
    
    self.AB01.configure(state=["disabled"])
            
    self.topab01 = Toplevel()
    self.topab01.title("PARTE DE CONTROL LOGÍSTICO")
    self.topab01.geometry("1680x1050")
            
    # PANEL 1
            
    # FRAMES PANEL 1
            
    self.box0ab1 = ttk.Frame(self.topab01)
    self.box0ab1.grid(row=0, column=0)
            
    self.box1ab1 = ttk.Frame(self.topab01)
    self.box1ab1.grid(row=0, column=1)
            
    self.box2ab1 = ttk.Frame(self.topab01)
    self.box2ab1.grid(row=0, column=2)
            
    self.box3ab1 = ttk.Frame(self.topab01)
    self.box3ab1.grid(row=1, column=0)
            
    self.box4ab1 = ttk.Frame(self.topab01)
    self.box4ab1.grid(row=1, column=1)
        
    self.box5ab1 = ttk.Frame(self.topab01)
    self.box5ab1.grid(row=1, column=2)

Es decir en resumen:
Tengo 3 módulos: Uno de ellos es una class y los otros dos son dos funciones. El problema es que las funciones(ventanas) tienen que interactuar la unas con la otras pero no consigo que esto se realice. ¿Alguna idea?.

P.D. ¿Se pueden tener dos ventanas Tk() abiertas en la misma app? No parece que ese sea el problema pero lo pregunto por si acaso. ¿Qué ocurre si la app primero crea una ventana Tk() (Un login por ejemplo) y luego la destruye abriendo otra ventana Tk() como la principal?. (Puede dar eso problemas de lo que me está ocurriendo?.
Muchas Gracias.
Responder
#9
Hola.

En primer lugar, este código:

Código:
if self.CheckCCL.get() == True:
    AB01 = partial(openCCL, self)
    AB01()
es un tanto innecesario, porque no estás pasando el objeto AB01 a ninguna otra función, así que se puede prescindir de partial() y simplificar la condición:

Código:
if self.CheckCCL.get():
    openCCL(self)
En cuanto a lo segundo, si al clickear el botón te dice que openab01() no está definido entonces no lo importaste o no lo estás escribiendo correctamente (¿está dentro de una clase o algo parecido?).

Saludos!

P.D. Te dejo acá el explicativo sobre cómo pegar el código Python en el foro para que se lea mejor: https://foro.recursospython.com/showthre...d=59#pid59.
¡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
#10
(12-07-2019, 07:38 PM)Francisco escribió: Hola.

En primer lugar, este código:

Código:
if self.CheckCCL.get() == True:
   AB01 = partial(openCCL, self)
   AB01()    
es un tanto innecesario, porque no estás pasando el objeto AB01 a ninguna otra función, así que se puede prescindir de partial() y simplificar la condición:

Código:
if self.CheckCCL.get():
   openCCL(self)
En cuanto a lo segundo, si al clickear el botón te dice que openab01() no está definido entonces no lo importaste o no lo estás escribiendo correctamente (¿está dentro de una clase o algo parecido?).

Saludos!

P.D. Te dejo acá el explicativo sobre cómo pegar el código Python en el foro para que se lea mejor: https://foro.recursospython.com/showthread.php?tid=18&pid=59#pid59.
Muchas Gracias.

Creo que va a ser tema de sistema operativo e instalación de python y los import. De hecho los import me están dando problemas desde que instalé Debian (Buster/testing). Como puse en otro post algo no funciona bien porque en el portátil dónde tengo ubuntu instalado, trabajando con treeviews me sale todo correctamente y cuando paso exactamente el mismo código al pc de sobremesa no da error pero no funciona correctamente (Sobre todo el tema de Style().)

En cuanto a los import he sido incapaz (Veremos ahora que ya ha salido la versión final de Debian 10 e instalaré en breve aunque me da mucha pereza) de importar directorios completos. De hecho en mis import he tenido que hacer "triquiñuelas". Por ejemplo: para importar un solo módulo tuve que realizar esto:

sys.path.append('/home/juan/Templates/Python/APP112')
from Ccl import *

Y eso solo me valió para importar el módulo Ccl, cuando en esa misma carpeta/directorio hay distintos módulos pero con la instrucción
from APP112 import * no me importa ninguno. (Cuando todo el mundo me decía que importar módulos era sencillísimo. Solo tienes que poner:
from directorio import * y eso debería importar todos los módulos de ese directorio pero en mi caso no fue así.

Muchas gracias de nuevo. Voy a seguir intentándolo.
Responder


Salto de foro:


Usuarios navegando en este tema: 1 invitado(s)