【问题标题】:how to remove a circular dependency in tkinter gui如何在 tkinter gui 中删除循环依赖
【发布时间】:2026-01-30 17:55:02
【问题描述】:

我正处于制作 GUI 的早期阶段。现在我有登录视图(LogInWind class)和注册视图(SignUpWind class)。当我只有一个视图时一切都很好,但现在我对这两个对象有循环依赖。这发生在按钮的命令中,因为在登录视图中有一个按钮可以将您带到注册视图,反之亦然。我想我可以为注册视图制作一个弹出窗口。但是我认为我切换窗口的方式是一种不好的做法,以后可能会带来更大的问题。

我的项目结构如下。

服务包将在 gui 和数据库之间建立连接。目前它什么也不做。

我很高兴收到有关所有内容的反馈。如果需要进一步解释,我很乐意这样做。

root.py

import tkinter as tk
from .log_in_window import LogInWind as LIWind
from .sign_up_window import SignUpWind as SuWind
from services.messenger import Messenger


class Root(tk.Tk):
    def __init__(self, *args,**kwargs):
        super().__init__(*args,**kwargs)
        
        # General variables.
        self.frames = {}
        self.width_window
        self.height_window
        self.container = tk.Frame(self,relief="groove")

        # General app configurations.
        self.title("SECOM")
        self.iconbitmap("C:/Users/joshu/Documents/VSC/python/secom/icons/icon.ico")
        self.configure(bg="#E0E0E0")
        
        # Container setup.
        self.container.pack(side="top", fill="both", expand=True)
        self.container.grid_rowconfigure(0, weight=1)
        self.container.grid_columnconfigure(0, weight=1)


    def createFrame(self, page_name):
        """
        INPUT: view object. 
        OUTPUT: None

        Description: creates frame for the view `page_name`.
        """
        # Setup `newFrame.`
        newFrame = page_name(parent=self.container, controller=self)
        newFrame.configure(bg="#E0E0E0")
        newFrame.grid(row=0, column=0, sticky=tk.NSEW)

        # Add `newFrame` to catalog of frames.
        self.__frames[page_name] = newFrame
        

    def showFrame(self, page_name):
        """
        INPUT: view object.
        OUTPUT: None.

        Calls the view `page_name` up front for display.
        """

        try:
            # Bings requested view to the front.
            self.frames[page_name].tkraise()
        except KeyError:
            # Creates view and displays it.
            self.createFrame(page_name)
            self.frames[page_name].tkraise()


    def setLocation(self):
        """
        INPUT: None
        OUTPUT: None

        Description: Sets window in the middle of the screen.
        """
        self.widthWindow = 400
        self.heightWindow = 225
        widthScreen = self.winfo_screenwidth()
        hightScreen = self.winfo_screenheight()
        x = (widthScreen / 2) - (self.widthWindow / 2)
        y = (hightScreen / 2) - (self.heightWindow / 2)

        self.geometry("%dx%d+%d+%d" % (self.widthWindow, self.heightWindow, x, y))

log_in_window.py

import tkinter as tk
import tkinter.font as tkf
from .sign_up_window import SignUpWind as SUWind  # <-- circular dependency


class LogInWind(tk.Frame):
    def __init__(self, parent, controller):
        super().__init__(parent, controller)
        
        # Creation of labels.
        self.titleLbl = tk.Label(self,
                                 text="Iniciar Secion",
                                 font=tkf.Font(family="Helvetica", size=15, weight="bold"),
                                 bg="#E0E0E0")
        self.userLbl = tk.Label(self,
                                text="Usuario:",
                                font=tkf.Font(family="Helvetica", size=10, weight="bold"),
                                bg="#E0E0E0")
        self.pswdLbl = tk.Label(self,
                                text="Contraseña:",
                                font=tkf.Font(family="Helvetica", size=10, weight="bold"),
                                bg="#E0E0E0")
        # Creation of entries.
        self.userEty = tk.Entry(self)   
        self.pswdEty = tk.Entry(self, show="*")
        # Creation of buttons.
        self.logInBtn = tk.Button(self,
                                  width=15,
                                  text="Iniciar Sesion",
                                  font=tkf.Font(family="Helvetica", size=10, weight="bold"),
                                  bg="#0080FF",
                                  ################################### ADD COMMAND
                                  fg="#fff")
        self.signUpBtn  = tk.Button(self,
                                    width=15,
                                    text="Crear cuenta",
                                    font=tkf.Font(family="Helvetica", size=10, weight="bold"),
                                    bg="#0080FF",
                                    command=lambda: controller.showFrame(SUWind),  # <---- use object HERE.
                                    fg="#fff")

        # Places labels.
        self.titleLbl.grid(row=0,
                           column=0,
                           padx=400 / 2 - 60,
                           pady=10,sticky=tk.SW)
        self.userLbl.grid(row=1,
                          column=0,
                          padx=parent.width_window / 2 - 60,
                          sticky=tk.SW)
        self.pswdLbl.grid(row=3,
                          column=0,
                          padx=parent.width_window / 2 - 60,
                          sticky=tk.W)
        # Places entries (string inputs).
        self.userEty.grid(row=2,
                          column=0,
                          padx=parent.width_window / 2 - 60,
                          pady=5)
        self.pswdEty.grid(row=4,
                          column=0,
                          padx=parent.width_window / 2 - 60,
                          pady=5)
        # # Places buttons
        self.logInBtn.grid(row=5,column=0, sticky=tk.N)
        self.signUpBtn.grid(row=6, column=0, sticky=tk.S, pady=10)

sing_up_window.py

import tkinter as tk
import tkinter.font as tkf
from .log_in_window import LogInWind as LIWind # <-- circular dependency


class SignUpWind(tk.Frame):
    def __init__(self, parent, controller): 
        super().__init__(parent, controller)
        # Setup imge for back button.
        self.img = tk.PhotoImage(file="C:/Users/joshu/Documents/VSC/python/secom/icons/return.png")
        self.img = self.img.subsample(4,4)
        # Create label.
        self.titleLbl = tk.Label(self,
                                 text="Crear cuenta nueva",
                                 font=tkf.Font(family="Helvetica", size=15, weight="bold"),
                                 bg="#E0E0E0")
        self.userLbl = tk.Label(self,
                                text="Usuario:",
                                font=tkf.Font(family="Helvetica", size=10, weight="bold"),
                                bg="#E0E0E0")
        self.pswdLbl = tk.Label(self,
                                text="Contraseña:",
                                font=tkf.Font(family="Helvetica", size=10, weight="bold"),
                                bg="#E0E0E0")
        self.pswdConfirmLbl = tk.Label(self,
                                 text="Cormirma Contraseña:",
                                 font=tkf.Font(family="Helvetica", size=10, weight="bold"),
                                 bg="#E0E0E0")
        # Creation of entries.
        self.userEty = tk.Entry(self)
        self.pswdEty = tk.Entry(self, show="*")
        self.pswdConfirmEty = tk.Entry(self, show="*")                                 
        # Creation of button.
        self.backBtn = tk.Button(self,
                                 image=self.img,
                                 command=lambda: controller.showFrame(LIWind))   <----- use object HERE.
        self.CreateBtn = tk.Button(self,
                                   width=8,
                                   text="Crear",
                                   font=tkf.Font(family="Helvetica", size=10, weight="bold"),
                                   bg="#0080FF",
                                   ############################ ADD COMAND
                                   fg="#fff")


        # Places labels.
        self.titleLbl.grid(row=0, column=0, padx=400 / 2 - 90, sticky=tk.SW)
        self.userLbl.grid(row=1, column=0, padx=parent.width_window / 2 - 60, sticky=tk.W)
        self.pswdLbl.grid(row=3, column=0, padx=parent.width_window / 2 - 60, sticky=tk.W)
        self.pswdConfirmLbl.grid(row=5, column=0, padx=parent.width_window / 2 - 60, sticky=tk.W)
        # Places entries (string inputs).
        self.userEty.grid(row=2, column=0, padx=parent.width_window / 2 - 60, pady=5, sticky=tk.W)
        self.pswdEty.grid(row=4, column=0, padx=parent.width_window / 2 - 60, pady=5, sticky=tk.W)
        self.pswdConfirmEty.grid(row=6, column=0, padx=parent.width_window / 2 - 60, pady=5, sticky=tk.W)
        # Places buttons
        self.backBtn.grid(row=0, column=0,sticky=tk.W)
        self.CreateBtn.grid(row=7, column=0, padx=100,sticky=tk.N)

ma​​in.py

from views.root import Root
from views.log_in_window import LogInWind as LIWind


if __name__ == '__main__':
    root = Root()

    root.showFrame(LIWind)
    
    root.mainloop()

【问题讨论】:

  • 为什么 sing_up_window.py 从 .root 导入任何东西?它没有使用该文件中的任何内容。如果这是循环依赖的原因,只需将其删除。
  • 谢谢我改了。那不应该在那里。我将导入更改为应有的样子。

标签: python-3.x user-interface tkinter object-oriented-analysis


【解决方案1】:

如果您将代码设计为将页面名称作为字符串而不是作为类传递,那么您将不会遇到此问题。

例如,在 root.py 中你可以这样开始:

pages = {
    "log in": LIWind,
    "sign up": SuWind,
}

接下来,在createFrame 中,您将执行以下操作:

def createFrame(self, page_name):
    ...
    cls = pages[page_name]
    newFrame = cls(parent=self.container, controller=self)
    ...
    self.__frames[page_name] = newFrame

然后,在任何需要窗口的地方都可以传入页面名称,如:

self.backBtn = tk.Button(..., command=lambda: controller.showFrame("log in"))

【讨论】:

  • 我虽然打算这样做,但是我怎样才能在 createFrame(page_name) 中使用字符串的对象名称创建对象?
  • @MrRobot:您可以在 root.py 中创建一个映射,将页面名称映射到类。