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

9

我正在使用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个回答

14

解决你的问题至少有两种方法。一种是对话框可以直接将信息发送给主应用程序,另一种是对话框生成一个事件,告诉主应用程序数据需要从对话框中获取。如果对话框只是改变某些东西(例如字体对话框),我通常会生成一个事件。如果对话框创建或删除数据,我通常会让它将信息推送回应用程序。

我通常会有一个应用程序对象,作为整个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 ...
创建对话框时,您需要传递应用程序对象的引用。然后,对话框知道在该对象上调用特定方法以将数据发送回应用程序。
如果您不喜欢整个模型/视图/控制器的方式,您也可以传递函数而不是对象,有效地告诉对话框“在您要向我提供数据时调用此函数”。

#bryan-oakley 对这些技术的总结很好。您是否推荐有关父窗口和子窗口之间数据交换的更详细参考资料(互联网或书籍资源)?
  • 事件生成返回到父级 - 将数据推回到父级 - ...
- AJN

2
在我的一个项目中,我试图在根窗口(self)的子tk.Toplevel窗口(child1)中检查用户是否从根窗口内创建了一个tk.Toplevel窗口(child2),并且如果此窗口(child2)当前存在于用户屏幕上。
如果不是这种情况,新的tk.Toplevel窗口应该由根窗口的子窗口(child1)创建,而不是由根窗口本身创建。如果它已经由根窗口创建并且当前存在于用户屏幕上,则应通过“child1”获取焦点()而不是重新初始化。
根窗口包含在名为App()的类中,并且两个“子”窗口都是由根类App()内的方法创建的。
如果给定方法的参数为True,则必须以安静模式初始化“child2”。我想这就是混乱的错误。如果有意义的话,问题发生在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: bad window path name

解决方案:

获取正确的“窗口路径名”的唯一方法是,不要直接在“child2”窗口上调用winfo_exists()方法,而是调用“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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接