【问题标题】:app structuring / re importing modules with tkinter python3使用 tkinter python3 构建应用程序/重新导入模块
【发布时间】:2020-03-27 14:30:47
【问题描述】:

我正在重组这个问题:我觉得最初的问题很啰嗦,对其他人没有特别的帮助。

我有一个直接运行的主应用程序(运行为__main__)。 我有一个只希望用作导入的模块(simple_module.py)。 我意识到如果需要(通过 if__name__)我可以独立运行它,并将其包含在模块代码中,仅用于演示。

当用户按下 main.py 的“开始”按钮时,它应该会打开一个新的顶层窗口,其中包含来自 simple_module 的所有类和小部件,它们都在一个名为 Page 的类中。 (同时主应用程序窗口保持打开状态。)

我希望每次按下按钮时都重新导入(或等效)模块。当按下模块“关闭”按钮或 X 时,我希望它关闭该窗口。 main.py 的窗口在整个过程中保持打开状态,按下按钮需要重新打开该模块窗口,就像循环一样无限。

我添加了 if name == 'ma​​in' 只是为了强调我了解它的作用(通常在我的所有 main.py 应用程序中)以及为什么我仍然无法得到我想要的结果。据我所见,它并没有改变任何东西,我现在只导入类,但仍然无法识别“新”。这与前面的示例中的问题相同。

我有 main.py

import tkinter as tk

# audio module works as expected
import audio_module as am

# I want this window to open and close on command
import simple_module as sm



class GUI(tk.Frame):

    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)

        #self.new = tk.Toplevel(self) # auto loads a second, unwanted window

        self.session_counter = 0
        self.start_btn = tk.Button(root, text="start", command=self.start)
        self.start_btn.grid(row=4,column=0,sticky="nsew",pady=30, padx=30, ipady=18)


    def start(self):
        am.spell() # these audio imports work like a charm, every btn press - single functions call OK

        self.session_counter += 1
        print(self.session_counter)

        #import simple_module - if used here, my usual 'illegal' import style (works great, once only,
        # unless in same script as __main__ in which case all re-imports work fine)

        # Import attempts
        #import simple_module as sm
        #page = Page(new) # Page not defined
        #sm.Page() #missing parent arg (new)

        # error: 'new' not defined
        #sm.Page(new)


if __name__ == '__main__':
    print('running as __main__')
    root = tk.Tk()
    #sm.Page = tk.Toplevel(new) # a desperate attempt NO
    #page = sm.Page(tk.TopLevel) NO
    # qualify and USE module here! sm is not required if you use 'from simple_module import Page' !!
    page = sm.Page(root)
    #page.pack(fill='both', expand=True)
    page.grid(row=0,column=0,sticky='nsew')
    main = GUI(root)
    root.mainloop()

最后,我们有了 simple_module.py:

import tkinter as tk
import audio_module as am

# this module works exactly as expected IF run directly...

class Page(tk.Frame):

    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        # super().__init__(*args, **kwargs)

        self.back_btn = tk.Button(parent, text="close", command=self.back)
        self.back_btn.grid(row=4,column=0,sticky="nsew",pady=30, padx=30, ipady=18)


    def back(self):
        am.click()
        # close this page BUT have it ready to re-open IF user re-presses button.
        new.destroy()


if __name__ == "__main__":
    print('running as __main__ directly')
    new = tk.Tk()
    #new = tk.Toplevel() # this loads an unwanted additional blank window. IF run directly.
    page = Page(new)
    # the missing line to self contain module!
    #page.pack(fill='both', expand=True)
    page.grid(row=0,column=0,sticky='nsew')
    new.mainloop()

else:
    print('running as import with __name__ ==',__name__)

感谢您的耐心和回复。我重新研究了您提供链接的 if ma​​in 指南,它再次确认了我已经相信我知道的内容。当我只想打开一个框架并在它们之间切换时,这是一个有用的示例,但在这种情况下,我希望主窗口在调用模块窗口时保持打开状态。

【问题讨论】:

    标签: python-3.x oop tkinter


    【解决方案1】:

    您面临的问题是您的 Page 类不是可重用的。它依赖于全局变量和关于调用它的代码的知识。为了可重用,Page 类需要是自包含的。

    简而言之,这意味着Page 创建的每个小部件都必须在Page 类中,而不是在根窗口中。此外,创建Page 实例的代码需要负责在实例上调用pack、place 或grid。

    所以,第一步是修改Page,使其可以重用。关键是要求调用者传入父窗口小部件。您正在这样做,但您没有使用传入的值。

    Page 应该类似于以下代码。请注意,它将parent 明确声明为第一个位置参数,并将其传递给tk.Frame.__init__:

    class Page(tk.Frame):
    
        def __init__(self, parent, *args, **kwargs):
            tk.Frame.__init__(self, parent, *args, **kwargs)
            self.back_btn = tk.Button(parent, text="close", command=self.back)
            ...
    

    注意后退按钮是parent 的子代,而不是某个全局变量的子代。这是您缺少的重要步骤,这就是允许 Page 独立而不与创建它的代码紧密耦合的原因。

    一旦Page 正确独立,创建Page 实例的代码就有责任在实例上调用pack、place 或grid。

    例如,simple_module.py 可能在末尾有这个块:

    if __name__ == "__main__":
        new = tk.Tk()
        page = Page(new)
        page.pack(fill="both", expand=True)
        new.mainloop()
    

    在main.py中,由于是整体导入simple_module,所以需要完全限定Page的使用:

    import simple_module as sm
    ...
    root = tk.Tk()
    page = sm.Page(root)
    page.pack(fill="both", expand=True)
    root.mainloop()
    

    或者,您可以只导入 Page 并省略 sm.:

    from simple_module import Page
    ...
    page = Page(root)
    ...
    

    请注意一个文件可以使用root,一个文件可以使用new,但是您的代码在这两种情况下都可以工作,因为它不依赖于全局变量。在页面内部,无论调用者如何称呼它,它始终是 parent。

    顺便说一句,您不必在start 中导入simple_module -- 您只需在程序开始时导入一次。

    【讨论】:

    • 感谢奥克利先生。我一直试图解决这个问题,但没有成功,所以我重新写了这篇文章以更有针对性。我希望你能提供进一步的帮助。
    • 好的,有趣的发展。我不能使用 .pack(),因为它受 .grid() 的奴役。我无法在开始按钮上加载第二个窗口?我尝试将其从包更改为网格,但该模块窗口不会加载。如果 page.pack() 或 .grid() 在外部 if 语句中 - 那么它为什么会被奴役?快到了,只需要打开第二个窗口。我以为 TopLevel 也会参与其中,但你没有包含它。
    • @glitchyGhost:pack 没有被网格“奴役”。我什至不知道那是什么意思。它们相互独立。
    • 回溯(最近一次调用最后):文件“import_simple.py”,第 76 行,在 page.pack(fill='both', expand=True) 文件“/Library/Frameworks /Python.framework/Versions/3.8/lib/python3.8/tkinter/__init__.py",第 2387 行,在 pack_configure self.tk.call( _tkinter.TclError: cannot use geometry manager pack inside . 已经有从属管理网格
    • @glitchyGhost:是的,当你误用 grid 和 pack 时会发生这种情况。这通常意味着你做错了什么。听起来您没有遵循此答案中的建议并使每个页面自成一体。对于具有相同父级的小部件,您不能同时使用 pack 和 grid。它与类的使用无关。无论有没有类,你都可以得到那个错误。
    猜你喜欢
    • 2015-01-20
    • 1970-01-01
    • 2013-01-13
    • 2023-02-09
    • 2014-07-20
    • 2014-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多