1

¡Buenas!

Estoy tratando de obtener información de un entry, para que me la imprima por consola, pero me da el siguiente error:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Program Files (x86)\Python37-32\lib\tkinter\__init__.py", line 1705, in __call__
    return self.func(*args)
  File ".\Cliente.py", line 68, in <lambda>
    botonNick = tk.Button(frame, text="Listo!", command=lambda:ObtenerNick())
  File ".\Cliente.py", line 48, in ObtenerNick
    insertNick.get()
NameError: name 'insertNick' is not defined

aquí os dejo la porcion del código también donde me ubica el fallo:

def ObtenerNick():
    print(insertNick.get())

    root.destroy()

# --------------------------------------------------------------#

def NickGui():
    root = tk.Tk()
    root.title("Nick")

    nick = tk.StringVar()   

    frame = tk.Frame(root).grid(row=0, column=0, padx=5, pady=5)

    labelNick = tk.Label(frame, text="Inserte su nick y pulse en Listo!")
    labelNick.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="n")

    insertNick = tk.Entry(frame, width=30, textvariable=nick)
    insertNick.grid(row=2, column=0, sticky="w", padx=5, pady=5)

    botonNick = tk.Button(frame, text="Listo!", command=lambda:ObtenerNick
    botonNick.grid(row=2, column=1, sticky="e", padx=5, pady=5)

    root.mainloop()

Mi pregunta es ¿Cual es la forma correcta de obtener la información de un Entry con un get()?. Tengo entendido que hay que hacer una función, y desde ahí, usando get al Entry en cuestión obtenerla, pero me da ese error.

He probado a usar nick.get() en vez de insertNick.get() pero tampoco funciona. también he probado a hacer el get desde la propia función NickGui, pero también me da un error.

¡Gracias por la ayuda!

RuDaHee
  • 612
  • 1
  • 4
  • 20

1 Answers1

1

Por defecto, una variable definida en una función es de ámbito local a ésta, es decir, no es accesible desde fuera de ésta, dejando de existir en cuanto la función retorne.

Tanto root como insertNick son variables definidas en la función NickGui y por tanto no pueden ser usadas en ObtenerNick.

Tienes otro error importante:

frame = tk.Frame(root).grid(row=0, column=0, padx=5, pady=5)

al hacer esto, llamar al método grid/place/pack en la misma línea en la que instancias el widget, frame es el retorno de grid, que es None y no una instancia de tkinter.Frame. Esto hace que a los widget hijos les pases None como padre:

frame = tk.Frame(root)
frame.grid(row=0, column=0, padx=5, pady=5)

Para solucionar el problema del ámbito de la variables, lo más simple es que definas la callback dentro de NickGui. De esta forma la función y las variables que usa pertenecen al mismo espacio de nombres y tiene acceso a las mismas:

import tkinter as tk


def nick_gui():

    def obtener_nick():
        print(nick.get())
        root.destroy()

    root = tk.Tk()
    root.title("Nick")

    nick = tk.StringVar()

    frame = tk.Frame(root)
    frame.grid(row=0, column=0, padx=5, pady=5)

    label_nick = tk.Label(frame, text="Inserte su nick y pulse en Listo!")
    label_nick.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="n")

    insert_nick = tk.Entry(frame, width=30, textvariable=nick)
    insert_nick.grid(row=2, column=0, sticky="w", padx=5, pady=5)

    boton_nick = tk.Button(frame, text="Listo!", command=obtener_nick)
    boton_nick.grid(row=2, column=1, sticky="e", padx=5, pady=5)

    root.mainloop()


if __name__ == "__main__":
    nick_gui()

No tienes (y no debes) que usar una función anónima si no necesitas pasar parámetros a la función:

Otra posibilidad es pasar las variables como argumento:

import tkinter as tk


def obtener_nick(root, var):
    print(var.get())
    root.destroy()

def nick_gui():

    root = tk.Tk()
    root.title("Nick")

    nick = tk.StringVar()

    frame = tk.Frame(root)
    frame.grid(row=0, column=0, padx=5, pady=5)

    label_nick = tk.Label(frame, text="Inserte su nick y pulse en Listo!")
    label_nick.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="n")

    insert_nick = tk.Entry(frame, width=30, textvariable=nick)
    insert_nick.grid(row=2, column=0, sticky="w", padx=5, pady=5)

    boton_nick = tk.Button(
        frame,
        text="Listo!",
        command=lambda:obtener_nick(root, nick)
        )

    boton_nick.grid(row=2, column=1, sticky="e", padx=5, pady=5)

    root.mainloop()


if __name__ == "__main__":
    nick_gui()

Te recomiendo no usar mayúsculas y CamelCase para nombrar variables o funciones. Esto se reserva para las clases por convención (ver Guia de estilo para código Python - PEP 8)

Obviamente existen más formas de solucionar el problema, pero tendríamos que modificar la estructura general de tu código. Por ejemplo, usando programación orientada a objetos:

import tkinter as tk



class NickGui(tk.Frame):

    def __init__(self, root, *args, **kwargs):
        super().__init__(root, args, **kwargs)
        self.root = root
        self.root.title("Nick")

        self.nick = tk.StringVar()
        frame = tk.Frame(root)
        frame.grid(row=0, column=0, padx=5, pady=5)

        label_nick = tk.Label(frame, text="Inserte su nick y pulse en Listo!")
        label_nick.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="n")

        insert_nick = tk.Entry(frame, width=30, textvariable=self.nick)
        insert_nick.grid(row=2, column=0, sticky="w", padx=5, pady=5)

        boton_nick = tk.Button(frame, text="Listo!", command=self.obtener_nick)
        boton_nick.grid(row=2, column=1, sticky="e", padx=5, pady=5)

    def obtener_nick(self):
        print(self.nick.get())
        self.root.destroy()


if __name__ == "__main__":
    root = tk.Tk()
    NickGui(root).place(relwidth=1, relheight=1)
    root.mainloop()
FJSevilla
  • 55,603
  • 7
  • 35
  • 58
  • Guay, no sabia que podía hacer una función dentro de otra funciona. Pruebo las correcciones y te evaluo la respuesta. Por cierto, muchisimas gracias por todas las respuestas que me has dado estos días. – RuDaHee Aug 10 '19 at 09:50
  • Hay varias cosas que no entiendo de este ultimo ejemplo, como ¿porque haces un ´place(relwidth=1,relheight=1)´ otra duda es el ¿por qué o para que sirve eso? ```python def __init__(self, root, *args, **kwargs):` super().__init__(root, args, **kwargs) ``` sobre todo la parte de *args, *kwargs – RuDaHee Aug 10 '19 at 10:18
  • NickGui es una instancia de `Frame`, esto hace que puedas reutilizar la clase fácilmente. place solo se usa para posicionar el `Frame` en la ventana principal y que ocupe todo el espacio disponible . [`__init__` es el inicializador de la clase](https://es.stackoverflow.com/a/63451/15089). Por otro lado [`super` permite llamar al inicializador (`__init__`) de la clase padre (tkinter.Frame)](https://es.stackoverflow.com/a/130252/15089). Mira a ver si los enlaces te ayudan, es un tema complejo para explicar en comentarios. – FJSevilla Aug 10 '19 at 10:40
  • En cuanto a `*args` permite que una función acepte cualquier número de parámetros posicionales mientras que `**kwargs` permite que acepte cualquier número de parámetros de tipo keyword (`foo(a=8)`). En este caso `NickGui(root)` no recibe ningún parámetro aparte de la ventana principal, pero imagina que queremos pasar un argumento extra al instanciar, por ejemplo queremos que el color de fondo sea rojo. Para ello simplemente hacemos: `NickGui(root, background="red")`. El argumento `background` se pasa al `__init__` de NinckGui mediante `**kwargs` y de este al padre mediante `super`. – FJSevilla Aug 10 '19 at 10:50
  • Me ha quedado bastante mas claro, a partir de aqui voy a tratar de modificar el programa usando Programación Orientada a Objetos, tratando de entender cada una de las cosas que me explicas tanto en las respuestas como en los enlaces de los comentarios. La POO en python me lia mucho mas que en Java o C#, donde lo veo todo mucho mas claro y sencillo :l – RuDaHee Aug 10 '19 at 10:57