I\'m creating some dialogs using TkInter and need to be able to open a child sub-window (modal or modeless) on clicking a button in the parent. The child would then allow a
In one of my projects I was trying to check within a child tk.Toplevel window (child1) of my root window (self), if a tk.Toplevel window (child2) was created by the user from within the root window, and if this window (child2) is present at the users screen at the moment.
If this wouldn't be the case, the new tk.Toplevel window should gets created by the child window (child1) of the root window, instead of the root window itself. And if it was already created by the root window and is currently present at the users screen, it should get focus() instead of getting reinitialized by "child1".
The root window was wrapped inside a class called App() and both "children" windows were created by methods inside the root class App().
I had to initialize "child2" in a quiet mode if an argument given to the method was True. I suppose that was the entangled mistake. The problem occurred on Windows 7 64 bit, if that's significant.
I tried this (example):
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()
Here comes the problem: I wasn't able to check if "child2" is already present. Got error: _tkinter.TclError: bad window path name
Solution:
The only way to get the right 'window path name' was, instead of calling the winfo_exists() method directly onto the "child2" window, calling the master of the "child1" window and adding the according attributes followed by the attributes of the master window you want to use.
Example (edit of the method 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)
There are at least a couple ways to solve your problem. Either your dialog can directly send information to the main application, or your dialog can generate an event that tells the main application that data is really to be pulled from the dialog. If the dialog simply changes the appearance of something (for example, a font dialog) I usually generate an event. If the dialog creates or deletes data I typically have it push information back to the application.
I typically have an application object that acts as the controller for the GUI as a whole. Often this is the same class as the main window, or it can be a separate class or even defined as a mixin. This application object has methods that dialogs can call to feed data to the application.
For example:
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 ...
When creating the dialog, you pass in a reference to the application object. The dialog then knows to call a specific method on this object to send data back to the application.
If you're not into the whole model/view/controller thing you can just as easily pass in a function rather than an object, effectively telling the dialog "call this function when you want to give me data".