如何在TkInter中创建子窗口并与父进行通信

时间:2012-05-23 10:27:37

标签: python tkinter

我正在使用TkInter创建一些对话框,并且需要能够在单击父级中的按钮时打开子子窗口(模态或无模式)。然后,子项将允许创建数据记录,并且需要将此数据(记录或操作被取消)传送回父窗口。到目前为止,我有:

import sel_company_dlg

from Tkinter import Tk

def main():
    root = Tk()
    myCmp = sel_company_dlg.SelCompanyDlg(root)
    root.mainloop()

if __name__ == '__main__':
    main()

这会调用顶级对话框,允许用户选择公司。公司选择对话框如下所示:

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)

单击新建... 按钮后,将显示“创建公司”对话框,允许用户填写公司详细信息并单击“创建”或“取消”。这是开头的一点:

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

我正在努力调用onNew()中调用子对话框的最佳方法 - 我的工作方式但是我不相信这是最好的方法,而且我也看不清楚如何沟通细节往返于儿童对话框。

我已经尝试过查看在线教程/参考资料,但我发现它过于简单化或者专注于像tkMessageBox.showinfo()这样的东西,而不是我想要的东西。

2 个答案:

答案 0 :(得分:10)

至少有几种方法可以解决您的问题。您的对话框可以直接将信息发送到主应用程序,或者您的对话框可以生成一个事件,告诉主应用程序确实要从对话框中提取数据。如果对话框只是改变了某些东西的外观(例如,字体对话框),我通常会生成一个事件。如果对话框创建或删除数据,我通常会将信息推送回应用程序。

我通常有一个应用程序对象作为整个GUI的控制器。通常这是与主窗口相同的类,或者它可以是单独的类,甚至可以定义为mixin。此应用程序对象具有对话框可以调用以将数据提供给应用程序的方法。

例如:

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 ...

创建对话框时,传入对应用程序对象的引用。然后,对话框知道调用此对象上的特定方法将数据发送回应用程序。

如果你没有进入整个模型/视图/控制器的东西,你可以轻松地传入一个函数而不是一个对象,有效地告诉对话框“当你想给我数据时调用这个函数”。

答案 1 :(得分:2)

在我的一个项目中,如果用户在根窗口中创建了tk.Toplevel窗口(child2),我试图检查我的根窗口(self)的子tk.Toplevel窗口(child1)。 ,如果此窗口(child2)此时出现在用户屏幕上。

如果不是这种情况,则新的tk.Toplevel窗口应该由根窗口的子窗口(child1)创建,而不是根窗口本身。如果它已经由根窗口创建并且当前出现在用户屏幕上,那么它应该得到focus()而不是被" child1"重新初始化。

根窗口包含在一个名为App()的类中,并且包含在" children"窗口是由根类App()中的方法创建的。

我必须初始化" child2"如果给出方法的参数为True,则在安静模式下。我想那是纠结的错误。在Windows 7 64位上出现问题,如果这很重要的话。

我试过这个(例子):

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

问题出现了: 我无法检查" child2"已经存在了。收到错误:_tkinter.TclError:错误的窗口路径名称

解决方案:

获得正确的窗口路径名称的唯一方法是'是的,而不是直接调用winfo_exists()方法到" child2"窗口,调用" child1"窗口并添加相应的属性,后跟要使用的主窗口的属性。

示例(编辑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)