如何在 Tkinter 中将 Toplevel 作为 master 传入 Game 类
问题根源
Game是继承自tkinter.Frame的容器组件,实例化时没有传入指定父容器,tkinter会默认将其挂载到程序第一个创建的根主窗口(即欢迎窗口root)上,因此游戏内容全部显示在主窗口底部,新建的Toplevel子窗口只有一个测试按钮,没有游戏内容。mains函数中额外添加了无意义的测试按钮,必须点击按钮才会创建游戏实例,不符合点击「New Game」直接启动游戏的需求。Game类内部错误调用了self.mainloop():Tk程序全程只需要一个全局事件循环,主窗口已经启动了root.mainloop(),子窗口/组件内重复调用会造成事件循环嵌套,引发逻辑异常。- Tkinter中同一个组件不能同时调用
pack()和grid()两种布局方法,否则会导致程序卡死。
修改步骤
仅需修改game_2048.py的代码,主欢迎窗口的核心逻辑无需改动。
1. 修改Game类构造函数
增加master参数接收外部传入的父窗口,将其传给父类Frame的初始化方法,删除多余的mainloop()调用。
原代码中的self.grid()是组件自行布局的逻辑,两种方案二选一即可:
方案A(保留原有布局,改动最小)
保留self.grid(),游戏帧会自动把自己添加到传入的父窗口中:
class Game(tkinter.Frame): def __init__(self, master): # 传入父容器给父类初始化 super().__init__(master) # 保留原有的自动布局逻辑 self.grid() self.master.title('2048') self.main_grid = tkinter.Frame( self, bg=c.GRID_COLOR, bd=3, width=400, height=400) self.main_grid.grid(pady=(80, 0)) self.make_GUI() self.start_game() self.master.bind("<Left>", self.left) self.master.bind("<Right>", self.right) self.master.bind("<Up>", self.up) self.master.bind("<Down>", self.down) # 必须删除原有的 self.mainloop()
方案B(解耦布局,更规范)
删掉类内部的self.grid(),布局逻辑交给外部调用方控制:
class Game(tkinter.Frame): def __init__(self, master): super().__init__(master) self.master.title('2048') self.main_grid = tkinter.Frame( self, bg=c.GRID_COLOR, bd=3, width=400, height=400) self.main_grid.grid(pady=(80, 0)) self.make_GUI() self.start_game() self.master.bind("<Left>", self.left) self.master.bind("<Right>", self.right) self.master.bind("<Up>", self.up) self.master.bind("<Down>", self.down)
2. 重写mains函数
创建Toplevel子窗口后,将其作为父容器传入Game即可。如果使用方案B,需要手动添加游戏帧的布局代码:
# 如果文件内使用 import tkinter 导入,Toplevel要写为 tkinter.Toplevel def mains(root): # 创建游戏子窗口 game_window = Toplevel(root) # 方案A直接实例化即可,无需额外布局 Game(game_window) # 方案B需要手动布局游戏帧,撑满子窗口 # game = Game(game_window) # game.pack(fill=BOTH, expand=True)
3. 修正单文件运行入口
如果需要直接运行game_2048.py启动游戏(不经过欢迎窗口),修改底部入口代码,自行创建根窗口并启动唯一的全局事件循环:
if __name__ == "__main__": root = tkinter.Tk() # 隐藏空白的默认根窗口,仅显示游戏子窗口 root.withdraw() mains(root) # 全程序唯一的事件循环 root.mainloop()
可选优化:禁止重复开启多个窗口
默认多次点击「New Game」会弹出多个游戏窗口,可以在主窗口中添加判断,游戏窗口打开时禁用按钮,关闭后恢复:
def start_new_game(): # 禁用按钮防止重复点击 button1.config(state=DISABLED) game_window = Toplevel(root) def on_game_close(): game_window.destroy() # 关闭游戏后恢复按钮 button1.config(state=NORMAL) # 绑定窗口关闭事件 game_window.protocol("WM_DELETE_WINDOW", on_game_close) game_2048.Game(game_window) # 将button1的command替换为start_new_game button1 = Button(root, text="New Game", fg="black", bg="#ddf0d0", padx=3, pady=3, font=('Helvetica', '12', 'bold'), activebackground="#94d3c3", command=start_new_game )
内容的提问来源于stack exchange,提问作者BigPy
相关产品推荐
相关产品推荐

