如何从启动页面在不同的 tkinter 画布之间切换并返回子画布中的启动页面

Posted

技术标签:

【中文标题】如何从启动页面在不同的 tkinter 画布之间切换并返回子画布中的启动页面【英文标题】:How to switch between different tkinter canvases from a start-up page and return back to start-up page in sub-canvas 【发布时间】:2020-11-10 23:52:02 【问题描述】:

我创建了一个start-up canvas,其中包含可调整到其他两个子画布的按钮。此外,在这两个子画布中,创建了返回启动画布的按钮。但是,在我输入sub-canvas 后,我无法返回到启动画布。也就是说,当我点击按钮返回启动画布时,它会在子画布旁边创建一个启动画布,而不是关闭子画布并切换到启动画布。有没有办法在画布之间切换并返回主画布?谢谢!

import tkinter as tk
from tkinter import ttk
import re


class SampleApp(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self._Canvas= None
        self.switch_Canvas(StartUpPage)

    def switch_Canvas(self, Canvas_class):
        new_Canvas = Canvas_class(self)
        if self._Canvas is not None:
                self._Canvas.destroy()
        self._Canvas = new_Canvas
        self._Canvas.pack()

class StartUpPage(tk.Canvas):
    def __init__(self, master, *args, **kwargs):
        tk.Canvas.__init__(self, master, *args, **kwargs)
        tk.Frame(self)
        tk.Label(self, text="Example").grid(column = 0, row = 0)
        tk.Button(self, text="Canvas1",
              command=lambda: master.switch_Canvas(PageOne)).grid(column = 0, row = 1)
        tk.Button(self, text="Canvas2",
              command=lambda: master.switch_Canvas(PageTwo)).grid(column = 0, row = 2)


class PageOne(tk.Canvas, tk.Tk):
    def __init__(self, master, *args, **kwargs):
        root = tk.Canvas.__init__(self, *args, **kwargs)
        self.frame1  = tk.Frame(root, width=430)
        self.frame1.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
        tk.Label(self.frame1, text="First Canvas").pack(side="top", fill="x", pady=5)
        tk.Button(self.frame1, text="Back to start-up page",
              command=lambda: master.switch_Canvas(StartUpPage)).pack()

class PageTwo(tk.Canvas, tk.Tk):
    def __init__(self, master, *args, **kwargs):
        root = tk.Canvas.__init__(self, *args, **kwargs)
        self.frame2  = tk.Frame(root, width=430)
        self.frame2.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
        tk.Label(self.frame2, text="Second Canvas").pack(side="top", fill="x", pady=5)
        tk.Button(self.frame2, text="Back to start-up page",
              command=lambda: master.switch_Canvas(StartUpPage)).pack()


if __name__ == "__main__":
    app = SampleApp()
    app.mainloop()

【问题讨论】:

“我无法返回启动画布”是什么意思?它是如何失败的?程序会崩溃吗?它会切换到错误的画布吗?它会抛出错误吗?另外,您发布的代码中存在缩进错误,请您修复它吗? Indent on self._Canvas.destroy() 运行代码后,我猜你想在画布之间切换(并首先创建它们),但代码实际上是在每次按钮调用时创建新画布,我猜对了吗? 谢谢!我已经修复了缩进错误。我的问题就像 Space 提到的那样。我无法返回到启动画布。相反,我将继续在我的子画布旁边创建启动画布。 解决方案似乎很明显:不要在函数中创建画布类的新实例。相反,在函数之外创建一次。你试过吗? 谢谢@BryanOakley!我真的不知道如何将画布类放在函数之外。你能给我解释一下吗?谢谢!我在每个子画布中都内置了几个框架。我想在启动画布的不同子画布之间切换。 【参考方案1】:

所以,有几件事:

第一:在PageOnePageTwo中有错误的父/主关系

def __init__(self, master, *args, **kwargs):

由于您是从 tk.Canvas 继承的,因此该类的新实例具有父 master,它本身就是从中传递的

SampleApp's switchCanvas as Canvas_class(self) 意思是 master. 或(主/根)窗口。

但是(我们仍然在 PageOne 内部)你正在使 root = tk.Canvas.__init__(self, *args, **kwargs) 变成 None,因为初始化程序返回 None

然后您将创建一个新的Frame,其父级为root,即None

总之

PageOnePageTwo 是(也是)Canvas 的新实例,其父窗口是主窗口,或者是由 SampleApp 创建的实例。 root 又名Frame 的父级是None(当你将它定位为self.frame1.pack(fill=tk.BOTH, side=tk.LEFT, expand=True) 时,我什至不知道它是如何影响它的)

因此,我假设您正在尝试创建 FramePageOnePageTwo 实例,并在其中放入 Canvas

这是我对这两个类所做的一些更改:它们的实例现在都属于(类型)tk.Frame,其中包含其他小部件。

class PageTwo(tk.Frame): # Sub-lcassing tk.Frame
    def __init__(self, master, *args, **kwargs):

        # self is now an istance of tk.Frame
        tk.Frame.__init__(self,master, *args, **kwargs)

        # make a new Canvas whose parent is self.
        self.canvas = tk.Canvas(self,bg='yellow', width=430)
        self.label = tk.Label(self, text="Second Canvas").pack(side="top", fill="x", pady=5) 
        self.button = tk.Button(self, text="Back to start-up page",
              command=lambda: master.switch_Canvas(StartUpPage))
        
        self.button.pack()

        # pack the canvas inside the self (frame).
        self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True) 

        #print('is instance',isinstance(self,tk.Frame))

第二:跟踪创建了哪些实例/对象,而不是不断产生新的实例/对象。我决定保持简单并创建一个内部dictionary,其键是StratUpPagePageOnePageTwo 的类,这意味着每个类都有一个可以切换的实例到。

def switch_Canvas(self, Canvas_class):

        # Unless the dictionary is empty, hide the current Frame (_mainCanvas is a frame)
        if self._mainCanvas:
            self._mainCanvas.pack_forget()

        # Modification 2: is the Class type passed is a one we have seen before?
        canvas = self._allCanvases.get(Canvas_class, False)

        # if Canvas_class is a new class type, canvas is False
        if not canvas:
            # Instantiate the new class
            canvas = Canvas_class(self)
            # Store it's type in the dictionary
            self._allCanvases[Canvas_class] = canvas

        # Pack the canvas or self._mainCanvas (these are all frames)
        canvas.pack(pady = 60)
        # and make it the 'default' or current one.
        self._mainCanvas = canvas

完整代码

import tkinter as tk
from tkinter import ttk
import re

class SampleApp(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self._mainCanvas= None
        # The dictionary to hold the class type to switch to
        # Each new class passed here, will only have instance or object associated with it (i.e the result of the Key)
        self._allCanvases = dict()
        # Switch (and create) the single instance of StartUpPage
        self.switch_Canvas(StartUpPage)

    def switch_Canvas(self, Canvas_class):

        # Unless the dictionary is empty, hide the current Frame (_mainCanvas is a frame)
        if self._mainCanvas:
            self._mainCanvas.pack_forget()

        # is the Class type passed one we have seen before?
        canvas = self._allCanvases.get(Canvas_class, False)

        # if Canvas_class is a new class type, canvas is False
        if not canvas:
            # Instantiate the new class
            canvas = Canvas_class(self)
            # Store it's type in the dictionary
            self._allCanvases[Canvas_class] = canvas

        # Pack the canvas or self._mainCanvas (these are all frames)
        canvas.pack(pady = 60)
        # and make it the 'default' or current one.
        self._mainCanvas = canvas

class StartUpPage(tk.Canvas):
    def __init__(self, master, *args, **kwargs):
        tk.Canvas.__init__(self, master, *args, **kwargs)
        tk.Frame(self) # Here the parent of the frame is the self instance of type tk.Canvas
        tk.Label(self, text="Example").grid(column = 0, row = 0)
        tk.Button(self, text="Canvas1",
              command=lambda: master.switch_Canvas(PageOne)).grid(column = 0, row = 1)
        tk.Button(self, text="Canvas2",
              command=lambda: master.switch_Canvas(PageTwo)).grid(column = 0, row = 2)


class PageOne(tk.Frame):
    def __init__(self, master, *args, **kwargs):
        tk.Frame.__init__(self,master, *args, **kwargs)
        self.canvas = tk.Canvas(self,bg='blue', width=430)
        print('got',self,master,args,kwargs)
        tk.Label(self, text="First Canvas").pack(side="top", fill="x", pady=5)
        tk.Button(self, text="Back to start-up page",
              command=lambda: master.switch_Canvas(StartUpPage)).pack()

        self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)

class PageTwo(tk.Frame): # Sub-lcassing tk.Frame
    def __init__(self, master, *args, **kwargs):
        # self is now an istance of tk.Frame
        tk.Frame.__init__(self,master, *args, **kwargs)
        # make a new Canvas whose parent is self.
        self.canvas = tk.Canvas(self,bg='yellow', width=430)
        self.label = tk.Label(self, text="Second Canvas").pack(side="top", fill="x", pady=5)
        self.button = tk.Button(self, text="Back to start-up page",
              command=lambda: master.switch_Canvas(StartUpPage))

        self.button.pack()
        # pack the canvas inside the self (frame).
        self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
        #print('is instance',isinstance(self,tk.Frame))

if __name__ == "__main__":
    app = SampleApp()
    app.mainloop()

编辑视觉演示

【讨论】:

以上是关于如何从启动页面在不同的 tkinter 画布之间切换并返回子画布中的启动页面的主要内容,如果未能解决你的问题,请参考以下文章

如何从锚点旋转 tkinter 画布小部件?

如何设置 Python tkinter 画布元素的 z 索引?

Python Tkinter:如何让画布每帧更新多边形坐标? [解决了]

Python,Tkinter:如何在可滚动画布上获取坐标

tkinter - 加权画布不填充空白空间

你如何在 tkinter 画布上创建一个按钮?