首页 > 解决方案 > 使用 tkinter python3 构建应用程序/重新导入模块

问题描述

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

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

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

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

我添加了 if name == ' main ' 只是为了强调我理解它的作用(它通常在我的所有 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__)

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

标签: python-3.xooptkinter

解决方案


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

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

因此,第一步是进行修改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调用packplacegrid

例如,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在页面内部,无论调用者如何称呼它,它都将始终是。

顺便说一句,你不必在simple_module里面导入start——你只需要在程序开始时导入一次。


推荐阅读