面向对象的 Tkinter - 居中多个帧

Posted

技术标签:

【中文标题】面向对象的 Tkinter - 居中多个帧【英文标题】:Object Oriented Tkinter - Centering Multiple Frames 【发布时间】:2016-03-17 14:09:10 【问题描述】:

所以我正在用 Python 编写一个面向对象的程序并使用 Tkinter 构建一个 GUI。我是面向对象和 Tkinter 的新手,所以我一直在 youtube、effbot.org、*** 和 zetcode.com 上使用 Sentdex 视频来帮助我。

这是我的程序,我对其进行了很多精简,使其更短且更易于理解。它以全屏模式开始并将框架居中。然后我单击登录按钮,下一帧被提升,然而,尽管它位于网格的中间,但它并没有居中,而网格的中心被权重 = 1 的单元格包围。

import tkinter as tk
from tkinter import ttk

class Program(tk.Tk):        
    def __init__(self, *args, **kwargs):   
        tk.Tk.__init__(self, *args, **kwargs)

        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)
        container.grid_rowconfigure(2, weight=1)
        container.grid_columnconfigure(2, weight=1)

        self.frames = 
        frame = Login(container, self)
        self.frames[Login] = frame

        frame.grid(row=1, column=1, sticky="nsew")

        for F in (Login, AdminHome):

            frame = F(container, self)
            self.frames[F] = frame
            frame.grid(row = 1, column = 1, sticky = "nsew")

        self.show_frame(Login)

    def show_frame(self,cont):
        frame = self.frames[cont]
        frame.tkraise()

class Login(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        name = tk.Label(self, text = "Program")
        name.grid(row=0, columnspan=5, sticky="W"+"E")

        username = tk.Label(self, text="Username: ")
        username.grid(row=1, columnspan=2, sticky="W")

        user = ttk.Entry(self, text ="", width=45)
        user.grid(row=1, column=2 ,columnspan=3, sticky="w")

        password = tk.Label(self, text="Password: ")
        password.grid(row=2, columnspan=2, sticky="W")

        passentry = ttk.Entry(self, text ="", width=45)
        passentry.grid(row=2, column=2 ,columnspan=3, sticky="W")

        loginb = ttk.Button(self, text = "Login", command = lambda: controller.show_frame(AdminHome))
        loginb.grid(row=3, columnspan=5, sticky="W"+"E")

class AdminHome(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        name = tk.Label(self, text = "Admin Home")
        name.grid(row=0, sticky="W")

        name = tk.Label(self, text = "FirstName + LastName")
        name.grid(row=0, column=5 ,sticky="E")

        update = ttk.Button(self, text = "Update Online", command = lambda: controller.show_frame(AdminHome))
        update.grid(row=1, columnspan=6, sticky="W"+"E")

        fetch = ttk.Button(self, text = "Fetch Data", command = lambda: controller.show_frame(AdminHome))
        fetch.grid(row=2, columnspan=6, sticky="W"+"E")

        a = ttk.Button(self, text = "a", command = lambda: controller.show_frame(AdminHome))
        a.grid(row=3, columnspan=2, sticky="W"+"E")

        b = ttk.Button(self, text = "b", command = lambda: controller.show_frame(AdminHome))
        b.grid(row=3, column=2, columnspan=2, sticky="W"+"E")

        c = ttk.Button(self, text = "c", command = lambda: controller.show_frame(AdminHome))
        c.grid(row=3, column=4, columnspan=2, sticky="W"+"E")

        edit = ttk.Button(self, text = "Edit Details", command = lambda: controller.show_frame(AdminHome))
        edit.grid(row=4, columnspan=6, sticky="W"+"E")

app = Program()
app.state('zoomed')
app.mainloop()

Login Screen before progress bars are added

当我将以下代码添加到 AdminHome 类时,问题反过来了,登录屏幕不再居中,但管理员主屏幕居中。

    uip = tk.Label(self, text="Updating in Progress ")
    uip.grid(row=5, sticky="W")

    updatebar = ttk.Progressbar(self,orient ="horizontal",length = 200, mode ="determinate")
    updatebar.grid(row=5, column=1, sticky="W"+"E")
    updatebar["maximum"] = 100
    updatebar["value"] = 50


    fip = tk.Label(self, text="Fetching in Progress ")
    fip.grid(row=5, column=2, sticky="W")

    fetchbar = ttk.Progressbar(self,orient ="horizontal",length = 200, mode ="determinate")
    fetchbar.grid(row=5, column=3, sticky="W"+"E")
    fetchbar["maximum"] = 100
    fetchbar["value"] = 50

Login Screen after progress bars are added

谁能告诉我为什么会发生这种情况以及如何解决它。如果我更改这些框架中的小部件的大小以及我添加到程序中的所有其他框架,我需要该解决方案也能正常工作。

【问题讨论】:

您创建了两次Login 框架。这是故意的吗? 不,这不是故意的,我该如何解决这个问题? 【参考方案1】:

问题不在于框架没有在容器中居中,而是管理页面小部件没有在管理框架中居中。通过为AdminHome 框架赋予独特的颜色,您可以很容易地看到这一点。执行此操作时,您会看到 AdminHome 框架保持在窗口的中心。

您复制的代码是专门为填充它们所在单元格的单个“页面”对象而设计的。代码只能这样工作,因为小部件都堆叠在一起。它们的大小必须相同,否则您会看到较大的框架位于较小的框架后面。

由于您不太了解代码的工作原理,因此我的建议是不要尝试更改它。与其试图使页面居中,不如专注于使页面中的数据居中。每个页面都被设计成完全独立的,因此您在该框架内所做的任何事情都不会影响任何其他小部件。

使每个页面中的内容居中的最简单解决方案是使用另一个框架。将所有小部件放在该框架中,然后将该框架居中。您实际上不需要这样做,您也可以使用您之前尝试使用的相同技巧,通过在页面内的所有小部件周围添加“加权”行和列。

以下是一个完整的工作示例。这不是解决问题的唯一方法,甚至可能不是最好的方法。请注意,由于使用了place,您必须给整个窗口一个大小(或像您正在做的那样设置zoomed 属性)。这是因为place 的使用不会强制窗口适应其内容。

要寻找的重要一点是在每个页面中使用innerFrame。这就是中心化的东西。

import tkinter as tk
from tkinter import ttk

class Program(tk.Tk):        
    def __init__(self, *args, **kwargs):   
        tk.Tk.__init__(self, *args, **kwargs)

        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = 
        for F in (Login, AdminHome):

            frame = F(container, self)
            self.frames[F] = frame
            frame.grid(row = 0, column = 0, sticky = "nsew")

        self.show_frame(Login)

    def show_frame(self,cont):
        frame = self.frames[cont]
        frame.tkraise()

class Login(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        innerFrame = tk.Frame(self)
        innerFrame.place(relx=.5, rely=.5, anchor="c")

        name = tk.Label(innerFrame, text = "Program")
        name.grid(row=0, columnspan=5, sticky="W"+"E")

        username = tk.Label(innerFrame, text="Username: ")
        username.grid(row=1, columnspan=2, sticky="W")

        user = ttk.Entry(innerFrame, text ="", width=45)
        user.grid(row=1, column=2 ,columnspan=3, sticky="w")

        password = tk.Label(innerFrame, text="Password: ")
        password.grid(row=2, columnspan=2, sticky="W")

        passentry = ttk.Entry(innerFrame, text ="", width=45)
        passentry.grid(row=2, column=2 ,columnspan=3, sticky="W")

        loginb = ttk.Button(innerFrame, text = "Login", command = lambda: controller.show_frame(AdminHome))
        loginb.grid(row=3, columnspan=5, sticky="W"+"E")

class AdminHome(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        innerFrame = tk.Frame(self)
        innerFrame.place(relx=.5, rely=.5, anchor="c")

        name = tk.Label(innerFrame, text = "Admin Home")
        name.grid(row=0, sticky="W")

        name = tk.Label(innerFrame, text = "FirstName + LastName")
        name.grid(row=0, column=5 ,sticky="E")

        update = ttk.Button(innerFrame, text = "Update Online", command = lambda: controller.show_frame(AdminHome))
        update.grid(row=1, columnspan=6, sticky="W"+"E")

        fetch = ttk.Button(innerFrame, text = "Fetch Data", command = lambda: controller.show_frame(AdminHome))
        fetch.grid(row=2, columnspan=6, sticky="W"+"E")

        a = ttk.Button(innerFrame, text = "a", command = lambda: controller.show_frame(AdminHome))
        a.grid(row=3, columnspan=2, sticky="W"+"E")

        b = ttk.Button(innerFrame, text = "b", command = lambda: controller.show_frame(AdminHome))
        b.grid(row=3, column=2, columnspan=2, sticky="W"+"E")

        c = ttk.Button(innerFrame, text = "c", command = lambda: controller.show_frame(AdminHome))
        c.grid(row=3, column=4, columnspan=2, sticky="W"+"E")

        edit = ttk.Button(innerFrame, text = "Edit Details", command = lambda: controller.show_frame(AdminHome))
        edit.grid(row=4, columnspan=6, sticky="W"+"E")

app = Program()
app.state('zoomed')
app.mainloop()

【讨论】:

我尝试使用此解决方案,但没有列重,您可以看到当前升起的框架下方的框架,有没有办法克服这个问题?如果不是,为什么在使用网格时 AdminHome 框架中有额外的空间,我将如何使用网格解决问题? “此解决方案但没有列权重” - 我不知道这是什么意思。什么是“这个解决方案”?是的,当然有解决方案。你的问题在这一点上有点不清楚。我们已经确定内部框架正确居中,所以您是在问如何将管理框架内的东西居中?还是您在询问登录屏幕上的进度条? 所以我使用了 place 和 'relx' 和 'rely' 值设置为 0.5,正如你所建议的,但是你可以在凸起的登录框架下方看到 AdminHome 框架。我的问题是,我将如何防止看到任何未提出的框架?如果没有办法做到这一点,如何在使用网格时将 AdminHome 框架和登录框架中的内容居中?

以上是关于面向对象的 Tkinter - 居中多个帧的主要内容,如果未能解决你的问题,请参考以下文章

通过 Tkinter 类传递数据帧

实现面向对象的计算器

面向对象简易计算器

课堂笔记 ---- 面向对象压缩软件

理清那么多个OO(面向对象)

课堂笔记 ---- 面向对象计算器