Cómo crear una ventana secundaria y comunicarse con el padre en TkInter

Estoy creando algunos cuadros de diálogo con TkInter y necesito poder abrir una subventana secundaria (modal o sin modelo) al hacer clic en un botón en el padre. El niño permitiría entonces que se creara un registro de datos y estos datos (ya sea el registro o si se canceló la operación) deben comunicarse de nuevo a la ventana principal. Hasta ahora tengo:

import sel_company_dlg from Tkinter import Tk def main(): root = Tk() myCmp = sel_company_dlg.SelCompanyDlg(root) root.mainloop() if __name__ == '__main__': main() 

Esto invoca el diálogo de nivel superior que permite al usuario seleccionar una empresa. El diálogo de selección de empresa se ve así:

 class SelCompanyDlg(Frame): def __init__(self, parent): Frame.__init__(self, parent) self.parent_ = parent self.frame_ = Frame( self.parent_ ) // .. more init stuff .. self.btNew_ = Button( self.frame_, text="New ...", command=self.onNew ) def onNew(self): root = Toplevel() myCmp = company_dlg.CompanyDlg(root) 

Al hacer clic en el botón Nuevo … , se muestra un cuadro de diálogo Crear empresa que permite al usuario completar los detalles de la empresa y hacer clic en crear o cancelar. Aquí está la parte inicial de eso:

 class CompanyDlg(Frame): def __init__(self, parent): Frame.__init__(self, parent) // etc. 

Estoy luchando con la mejor manera de invocar el diálogo infantil en onNew() : lo que tengo funciona pero no estoy convencido de que sea el mejor enfoque y, además, no puedo ver cómo comunicar los detalles al niño diálogo.

He intentado buscar tutoriales / referencias en línea, pero lo que he encontrado es demasiado simplista o se centra en cosas como tkMessageBox.showinfo() que no es lo que quiero.

Hay al menos un par de maneras de resolver su problema. Su diálogo puede enviar información directamente a la aplicación principal, o su diálogo puede generar un evento que le dice a la aplicación principal que los datos realmente se deben extraer del diálogo. Si el cuadro de diálogo simplemente cambia la apariencia de algo (por ejemplo, un cuadro de diálogo de fuente), generalmente genero un evento. Si el cuadro de diálogo crea o elimina datos, normalmente tengo que enviar información a la aplicación.

Normalmente tengo un objeto de aplicación que actúa como el controlador para la GUI en su conjunto. A menudo, esta es la misma clase que la ventana principal, o puede ser una clase separada o incluso definida como una mezcla. Este objeto de aplicación tiene métodos a los que los diálogos pueden llamar para enviar datos a la aplicación.

Por ejemplo:

 class ChildDialog(tk.Toplevel): def __init__(self, parent, app, ...) self.app = app ... self.ok_button = tk.Button(parent, ..., command=self.on_ok) ... def on_ok(self): # send the data to the parent self.app.new_data(... data from this dialog ...) class MainApplication(tk.Tk): ... def on_show_dialog(self): dialog = ChildDialog(self) dialog.show() def new_data(self, data): ... process data that was passed in from a dialog ... 

Al crear el cuadro de diálogo, se pasa una referencia al objeto de la aplicación. Entonces, el cuadro de diálogo sabe que debe llamar a un método específico en este objeto para enviar datos a la aplicación.

Si no estás en el tema completo del modelo / vista / controlador, puedes pasar fácilmente una función en lugar de un objeto, diciéndole al cuadro de diálogo “llamar a esta función cuando quieras proporcionarme datos”.

En uno de mis proyectos intentaba verificar dentro de una ventana secundaria tk.Toplevel (child1) de mi ventana raíz (self), si el usuario creó una ventana tk.Toplevel (child2) desde la ventana raíz, y si Esta ventana (child2) está presente en la pantalla de los usuarios en este momento.

Si este no fuera el caso, la nueva ventana tk.Toplevel debería ser creada por la ventana secundaria (child1) de la ventana raíz, en lugar de la propia ventana raíz. Y si ya fue creado por la ventana raíz y actualmente está presente en la pantalla de los usuarios, debería obtener focus () en lugar de reinicializarse con “child1”.

La ventana raíz se ajustó dentro de una clase llamada App () y ambas ventanas “secundarias” se crearon mediante métodos dentro de la clase raíz App ().

Tuve que inicializar “child2” en modo silencioso si un argumento dado al método era Verdadero. Supongo que ese fue el error enredado. El problema ocurrió en Windows 7 de 64 bits, si eso es significativo.

Probé esto (ejemplo):

 import tkinter as tk from tkinter import ttk class App(tk.Tk): def __init__(self): tk.Tk.__init__(self) top = self.winfo_toplevel() self.menuBar = tk.Menu(top) top['menu'] = self.menuBar self.menuBar.add_command(label='Child1', command=self.__create_child1) self.menuBar.add_command(label='Child2', command=lambda: self.__create_child2(True)) self.TestLabel = ttk.Label(self, text='Use the buttons from the toplevel menu.') self.TestLabel.pack() self.__create_child2(False) def __create_child1(self): self.Child1Window = tk.Toplevel(master=self, width=100, height=100) self.Child1WindowButton = ttk.Button(self.Child1Window, text='Focus Child2 window else create Child2 window', command=self.CheckForChild2) self.Child1WindowButton.pack() def __create_child2(self, givenarg): self.Child2Window = tk.Toplevel(master=self, width=100, height=100) if givenarg == False: self.Child2Window.withdraw() # Init some vars or widgets self.Child2Window = None else: self.Child2Window.TestLabel = ttk.Label(self.Child2Window, text='This is Child 2') self.Child2Window.TestLabel.pack() def CheckForChild2(self): if self.Child2Window: if self.Child2Window.winfo_exists(): self.Child2Window.focus() else: self.__create_child2(True) else: self.__create_child2(True) if __name__ == '__main__': App().mainloop() 

Aquí viene el problema: no pude comprobar si “child2” ya está presente. Se produjo un error: _tkinter.TclError: nombre incorrecto de la ruta de la ventana

Solución:

La única forma de obtener el ‘nombre de la ruta de la ventana’ correcta fue, en lugar de llamar al método winfo_exists () directamente en la ventana “child2”, llamando al maestro de la ventana “child1” y agregando los atributos correspondientes seguidos de los atributos de ventana maestra que desea utilizar.

Ejemplo (edición del método CheckForChild2):

  def CheckForChild2(self): if self.Child2Window: if self.Child1Window.master.Child2Window.winfo_exists(): self.Child1Window.master.Child2Window.focus() else: self.__create_child2(True) else: self.__create_child2(True)