python-3.x Tkinter OOP -具有多个顶级窗口的类示例管理

9o685dep  于 2023-02-01  发布在  Python
关注(0)|答案(2)|浏览(103)

我最初的方法基于以下示例:
Best way to structure a tkinter application?
我正在尝试创建一个更复杂的应用程序,其中包含多个子应用程序,每个子应用程序都显示在一个单独的顶层窗口中。所有子应用程序都需要能够在彼此之间交换信息。因此,我打算在主应用程序中使用类变量来实现这一需求。
此刻我被困在一点上:
我不知道如何在创建子应用程序类示例后正确地实现对子应用程序类示例的销毁。
“app_one”窗口只能打开一次,然后可以正确关闭。当然,如果窗口第二次打开,则会出现错误,因为只有“Toplevel”窗口示例被破坏,而子应用程序类示例本身未被破坏。
如何在关闭“顶级”窗口时正确实现子应用程序类示例的销毁?
除此之外,我也非常感谢任何其他关于如何改进我的模板结构的建议如下“Python”以及“tkinter”最佳实践范例:

import tkinter
    import tkinter.filedialog
    
    
    class main_app(tkinter.Tk):
    
        root = None
        var_one = None
        sub_app_one_instance = None
        sub_app_two_instance = None
        sub_app_thr_instance = None
    
        def __init__(self):
    
            super().__init__()
            main_app.root = self  # <<< in order to be able to refer to root window in other classes
            main_app.var_one = 99  # <<< class variables in order to exchange data between main app and sub apps
            main_app.sub_app_one_instance = None
            main_app.sub_app_two_instance = None
            main_app.sub_app_thr_instance = None
    
            self.create_tkinter_interface_main_app()
    
        def create_tkinter_button(self, button_destination, button_text, button_width):
            tkinter_button = tkinter.Button(button_destination, text=button_text, width=button_width)
            return tkinter_button
    
        def create_tkinter_label(self, label_destination, label_text):
            tkinter_label = tkinter.Label(label_destination, text=label_text)
            return tkinter_label
    
        def create_tkinter_interface_main_app(self):
    
            frame_main = tkinter.Frame(self)
            frame_sub_one = tkinter.Frame(frame_main, bg="red")
            frame_sub_two = tkinter.Frame(frame_main, bg="green")
            frame_sub_thr = tkinter.Frame(frame_main, bg="blue")
    
            label_app = self.create_tkinter_label(frame_main, "application")
            label_one = self.create_tkinter_label(frame_sub_one, "menu one")
            label_two = self.create_tkinter_label(frame_sub_two, "menu two")
            label_thr = self.create_tkinter_label(frame_sub_thr, "menu thr")
    
            button_one = self.create_tkinter_button(frame_sub_one, "app_one", 20)
            button_one.configure(command=self.sub_app_one_open)
    
            button_two = self.create_tkinter_button(frame_sub_one, "app_two", 20)
    
            label_app.pack(side=tkinter.TOP, fill=tkinter.X, expand=tkinter.NO)
            frame_sub_one.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=tkinter.YES)
            frame_sub_two.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=tkinter.YES)
            frame_sub_thr.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=tkinter.YES)
            label_one.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
            button_one.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
            button_two.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
            label_two.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
            label_thr.pack(side=tkinter.TOP, fill=tkinter.NONE, expand=tkinter.NO, padx=10, pady=10)
            frame_main.pack(fill=tkinter.BOTH, expand=tkinter.TRUE)
    
        def sub_app_one_open(self):
            if not main_app.sub_app_one_instance == None:
                main_app.sub_app_one_instance.sub_app_one_move_to_front()
                return None
            else:
                main_app.sub_app_one_instance = sub_app_one()
    
    
    class sub_app_one(main_app):  # <<< inherit from main application in order to be able to access class variables
        def __init__(self):
            self.sub_app_one_toplevel_instance = tkinter.Toplevel(main_app.root)
            self.sub_app_one_toplevel_instance.protocol("WM_DELETE_WINDOW", self.sub_app_one_close_toplevel)
            print(main_app.var_one)  # <<< access information from main app
    
        def sub_app_one_move_to_front(self):
            self.sub_app_one_toplevel_instance.attributes("-topmost", 0x01)  # <<< set window state
            self.sub_app_one_toplevel_instance.attributes("-topmost", 0x00)  # <<< reset window state
    
        def sub_app_one_close_toplevel(self):
            self.sub_app_one_toplevel_instance.destroy()
            self.sub_app_one_toplevel_instance = None
    
    
    if __name__ == "__main__":
        main_app_instance = main_app()
        main_app_instance.mainloop()
nszi6y05

nszi6y051#

〈〈〈从主应用程序继承,以便能够访问类变量

这不是继承的工作方式。如果sub_app_one需要访问主应用程序中的变量,它应该通过main_app的示例获得它们。你绝对不应该从main_app继承。
当你使用继承的时候,你是说子类 * 是一个 * 主类。意思是,你最终得到了两个主类。一个是原来的,一个和原来的完全一样,只是做了一些修改。这意味着你最终得到了tk.Tk的两个示例,这不是tkinter的设计使用方式。你可以做到,但是行为并不直观。也很少是正确的选择。
你应该做的是在创建main_app的示例时将其传递到你的sub_app_one中,它看起来像这样:

class main_app(tkinter.Tk):
    ...
    def sub_app_one_open(self):
        ...
        main_app.sub_app_one_instance = sub_app_one(main_app=self)
        ...

class sub_app_one():
    def __init__(self, main_app):
        self.main_app = main_app
        ...

至于删除窗口的处理,最简单的解决方案是将子类设为tk.Toplevel的子类,这样,就可以使用tkinter内置的事件处理来了解小部件何时被销毁。
下面是一个工作示例:

import tkinter as tk

class Main(tk.Tk):
    def __init__(self):
        super().__init__()
        self.sub_app_one = None
        b1 = tk.Button(self, text="Open Sub App One", command=self.sub_app_one_open)
        b1.pack(padx=20, pady=20)

    def sub_app_one_open(self):
        if self.sub_app_one is None:
            self.sub_app_one = Sub_app_one(self)
        else:
            self.sub_app_one.deiconify()
        self.sub_app_one.bind("<Destroy>", self._child_destroyed)

    def _child_destroyed(self, event):
        if event.widget == self.sub_app_one:
            print("sub_app_one has been destroyed")
            self.sub_app_one = None

class Sub_app_one(tk.Toplevel):
    def __init__(self, main_app):
        self.main_app = main_app
        super().__init__(main_app)
        label = tk.Label(self, text="This is sub app one")
        label.pack(padx=50, pady=50)

main = Main()
main.mainloop()
bvjxkvbb

bvjxkvbb2#

在过去的几周里,我一直在处理这样的应用程序,下面是我可以给出的一些建议。我将把我的答案分为两部分:
1.我自己的代码中的一些示例
1.如果这是一个长期项目,建议将很有帮助

    • 我的代码中的示例**

1.管理GUI
我建议你有一个主类来管理你的应用程序。这个类把你的应用程序的所有框架/页面放在彼此的顶部,并把你需要的提升到顶部。我为每个框架都有一个单独的文件,因为每个框架的代码都可能很长。这是'Main.py'文件:

import tkinter as tk
    #importing frames that we will create soon!
    from Frame1 import Frame1
    frome Frame2 import Frame2
    frome Frame3 import Frame3

    class Main(tk.Tk):
        def __init__(self):
            tk.Tk.__init__(self) 

            #Sets the screen size of the GUI
            self.width= self.winfo_screenwidth() 
            self.height= self.winfo_screenheight()
            self.geometry(str(self.width) + 'x' + str(self.height) + '+0+0')

            self.title('project_title')

            #The container is a frame that contains the projects's frames
            container = tk.Frame(self, 
                                 height=self.height, 
                                 width=self.width)

            #Pack the container to the root
            container.pack(side="top", fill="both", expand=True)

            #Fixes pack location of the container using grid
            container.grid_rowconfigure(0, weight=1)
            container.grid_columnconfigure(0, weight=1)

            #Create empty dictionary of frames
            self.frames = {}

            #Use container as master frame for imported frames 
            for Frame in (Frame1, Frame2, Frame3):
                self.frames[Frame] = Frame(container)
                self.frames[Frame].grid(row=0, column=0, sticky="nsew")  

            #Define buttons to switch GUI pages
            self.frame1_btn = tk.Button(self, 
                                           text='Frame1', 
                                           width=15, 
                                           font=('Arial', 10),
                                           command=self.show_frame1)
            self.frame1_btn.place(relx=0.75, 
                                  rely=0.025, 
                                  anchor='center')

            self.frame2_btn = tk.Button(self, 
                                       text='Frame2', 
                                       width=15, 
                                       font=('Arial', 10),
                                       command=self.show_frame2)
            self.frame2_btn.place(relx=0.85, 
                                  rely=0.025, 
                                  anchor='center')

            self.frame3_btn = tk.Button(self, 
                                       text='Frame3', 
                                       width=15, 
                                       font=('Arial', 10),
                                       command=self.show_frame3)
            self.frame3_btn.place(relx=0.95, 
                                  rely=0.025, 
                                  anchor='center')

            #The start page is raised to the top at the beginning.
            self.show_frame1()

        #Raises the selected frame to the top.
        def show_frame1(self):
            frame = self.frames[Frame1]
            frame.tkraise()

        def show_frame2(self):
            frame = self.frames[Frame2]
            frame.tkraise()

        def show_frame3(self):
            frame = self.frames[Frame3]
            frame.tkraise()

    def main():
        app = Main()
        app.mainloop()

    if __name__ == '__main__':
        main()

您有一个管理GUI的"main"文件。现在您必须创建以后将成为应用程序页面的框架。最佳做法: 将它们分别保存在单独的文件中,然后导入www.example.com!以下是www.example.com文件示例:Main.py! This is an example Frame.py file:

import tkinter as tk

class Frame1(tk.Frame):
    def __init__(self, ParentFrame):
        tk.Frame.__init__(self, ParentFrame)

        #The label acts as a title for each main frame.
        self.label = tk.Label(self, 
                              text='App_Name: Frame1',
                              font=('Arial', 20))

        self.label.pack(padx=10, pady=10, anchor='w')

根据需要对任意多的帧重复此操作。然后,您的目录中应包含以下文件:
Main.py, Frame1.py, Frame2.py, Frame3.py, etc.
尝试运行www.example.com文件! GUI现在看起来仍然很无聊,但您将用自己的代码填充每个框架。Main.py file! The GUI still looks boring right now, but you will fill each frame with your own code.
1.破坏框架
假设你有一个只想使用一次的起始页,它在使用后应该被销毁。让我们的'Frame1.py'文件作为起始页。它的代码应该如下所示:

import tkinter as tk

class Frame1(tk.Frame):
    def __init__(self, ParentFrame):
        tk.Frame.__init__(self, ParentFrame)

        #The label acts as a title for each main frame.
        self.label = tk.Label(self, 
                              text='App_Name: Frame1',
                              font=('Arial', 20))

        self.label.pack(padx=10, pady=10, anchor='w')

        self.selfdestruct_btn = tk.Button(self, 
                                          text='selfdestruct',
                                          command=self.selfdestruct)
        
        self.selfdestruct_btn.pack(pady=20)

    def selfdestruct(self):
        self.destroy()

如果单击起始页上的"自毁"按钮,此框架将被正确销毁。但是,您仍然可以看到在"Main.py"中定义的"框架1"按钮,即使单击它没有产生任何结果。返回到"Main.py"文件并删除定义此按钮的部分。无论如何,您不需要它。因为当您启动应用程序时,起始页将被行'self.show_frame1()'提升到顶部。
我希望这对你的问题有所帮助。下面是我想给你的一些其他建议:

    • 进一步建议**

1.文件结构
一个好的文件结构可以帮助你对你的代码有一个总体的了解,并且会使修复错误变得更加容易。你可能需要一个类似于下面的目录:

  • Main.py file
  • 帧1的文件夹
  • 帧2的文件夹
  • 框架3的文件夹
  • 可能是数据库
  • 应用程序的任何其他主要组件

为什么要为应用程序的每个框架使用文件夹?当你为每个框架编写代码时,你可能会开始用子框架填充每个框架。每个子框架都有自己的功能,可能需要大量的代码。因此,我会为你使用的每个major子框架定义一个类,用大量的代码来描述它。如果你有多个这样的子框架类,你甚至可能想要在frame文件夹中有一个"subframe"文件夹。2你也可以把那些只被这个frame使用的文件放在frame文件夹中。3假设你必须执行一些复杂的计算并定义一个类来完成它。4你可以把这个类保存在frame文件夹中的一个单独的文件中。
1.快速GUI加载
试着不要让你的电脑在启动程序时加载一堆不必要的东西。最好的情况是它只需要打包你所有的页面/框架和它们的按钮。不要让它加载巨大的图片文件或任何类似的东西。让它通过点击按钮来完成这些事情。
1.漂亮不会得奖的
......至少这里的情况可能不是这样。我不会花太多时间在继续之前完美地格式化每个按钮,因为你可能会一直移动东西。一旦你把所有需要的元素打包到框架中,你就可以考虑格式化了。
这就是我现在要分享的。如果你有任何后续问题,请随时提问!
贝斯特安塞尔

相关问题