在 Python 3 中,如何使框架(如数据输入表单或状态栏)始终出现在其他 tkinter 框架之上?

How to make a frame (like a data entry form or a status bar) always appear on top of other tkinter frames in Python 3?

所以,我有一个小型 Python 3 项目,它应该有一个 window 由 3 个主要区域组成:

我也在尝试添加一个快速数据输入表单,该表单应按用户要求显示在状态栏的正上方。我想我已经能够使用 pack() 为此 window 创建主要结构。当用户单击适当的按钮时,数据输入表单也会显示并消失。但是有几件事我不知道如何解决。第一个是当用户调整 window 的大小时,使其变小,状态栏和数据输入表单都会在 table/treeview 下消失。我怎样才能让他们总是在最前面?

这是我的代码:

#!/usr/local/bin/python3
# encoding: utf-8

from tkinter import *
from tkinter import ttk

entryform_visible = 0


class mainWindow:
    def __init__(self, master):
        self.master = master
        master.title("AppTittle")
        master.minsize(width=700, height=200)           

        def show_entryform():
            global entryform_visible
            if entryform_visible == 1:
                hide_entryform()
                return
            else:
                entryform_visible = 1
                # Form for data entry (bottom of main window)
                status_txt.set("Introducing new data...")
                self.bottomframe.pack(side=BOTTOM, fill=X)

        def hide_entryform(): 
            global entryform_visible
            self.bottomframe.pack_forget()
            entryform_visible = 0
            status_txt.set("Introduction of data was cancelled.")

        def add_register():
            hide_entryform()
            status_txt.set("New register added!")                  

        # Tool bar (buttons)
        self.topframe = ttk.Frame(root, padding="3 3 12 12")
        self.topframe.pack(side=TOP, fill=X)
        btn_add = ttk.Button(self.topframe, text="Button1").pack(side=LEFT)
        btn_add = ttk.Button(self.topframe, text="+", width=2, command=show_entryform).pack(side=RIGHT)
        btn_add = ttk.Button(self.topframe, text="Button2").pack(side=RIGHT)

        # Data table (center area of window)
        self.mainframe = ttk.Frame(root)
        self.mainframe.pack(side=TOP, fill=X)

        tree = ttk.Treeview(self.mainframe, height=25, selectmode='extended')
        tree['columns'] = ('id', 'name', 'descr')
        tree.pack(side=TOP, fill=BOTH)
        tree.column('#0', anchor=W, minwidth=0, stretch=0, width=0)
        tree.column('id', anchor=W, minwidth=30, stretch=0, width=40)
        tree.column('name', minwidth=30, stretch=1, width=30)
        tree.column('descr', minwidth=100, stretch=1, width=200)
        tree.heading('id', text="ID")
        tree.heading('name', text="Name")
        tree.heading('descr', text="Description")

        # Data entry form
        self.bottomframe = ttk.Frame(root, padding="3 3 12 12")
        ttk.Label(self.bottomframe, text="Field 1").pack(side=LEFT)
        text_input_obj = ttk.Entry(self.bottomframe, width=13)
        text_input_obj.pack(side=LEFT)
        text_input_obj.focus_set()
        btn_add = ttk.Button(self.bottomframe, text="Add", command=add_register).pack(side=RIGHT)
        # We are going to pack() this later (when the user clicks the '+' button)

        # Status bar
        self.statusframe = ttk.Frame(root, padding="2 2 2 2")
        self.statusframe.pack(side=BOTTOM, fill=X)
        status_txt = StringVar()
        self.statusbar = ttk.Label(self.statusframe, textvariable=status_txt).pack(side=LEFT)


root = Tk()
appwindow = mainWindow(root)
root.mainloop()

The first one is that when the user resizes the window, making it smaller, the status bar and the data entry form both disappear under the table/treeview. How can I make them be always on top?

这与加壳器的工作方式有关。当可用的 space 少于所需的 space 时,它将开始砍掉小部件。它以与小部件的打包方式相反的顺序执行此操作。

在这种情况下,因为底部框架是最后打包的,所以当您将 window 缩小到太小无法容纳所有内容时,首先打包会缩小任何 windows 可能已经扩展到大于它们的默认大小,然后开始从最后一个打包的小部件开始切掉小部件。在这种情况下,self.statusframe 被切碎,因为它是最后一个被打包的。

有两种方法可以解决这个问题。一种是简单地颠倒将所有内容打包到根 window 中的顺序。另一种是最初将树形部件制作得非常小,然后让它长满space。由于 treeview 的自然大小很小,tkinter 会在开始从视图中删除小部件之前缩小它。如果你使用第二种技术,你可以使用 wm_geometry 使 window 在开始时变大(否则树将没有任何 space 可以长成)

我的建议是始终将 packgrid 语句分组。它使这样的更改变得非常容易,并且使您的代码可视化变得更加容易。例如,使用第一种技术,我会将主要区域的打包移动到函数的底部:

# layout the main GUI
self.topframe.pack(side=TOP, fill=X)
self.statusframe.pack(side=BOTTOM, fill=X)
self.mainframe.pack(side=TOP, fill=BOTH, expand=True)

有了这个,当 space 变得紧凑时,就会开始砍树。但是因为这棵树很大,所以在它开始消失之前有很多东西要砍。

当您这样做时,您将在布局中暴露出另一个问题,那就是您没有扩展树视图以在 mainframe 中填充额外的 space。你需要这样打包:

tree.pack(side=TOP, fill=BOTH, expand=True)

这带来了另一个问题。即,您的弹出窗口 bottomframe 可能会隐藏弹出。那是因为 pack 的设计不适用于这种情况。同样,那是因为 pack 将开始以与添加小部件相反的顺序切断小部件。由于您最后添加它,因此它是第一个被砍掉的小部件。如果用户将 window 的大小调整得更小,将其打包在底部可能会导致它永远不会出现。

在这种特定情况下更好的解决方案是对这个小部件使用 pack。相反,由于您希望它成为 "pop up",您可以使用 placeplace 非常适合在其他小部件之上弹出小部件。您唯一需要做的另一件事是确保此帧的堆叠顺序高于状态栏,您可以使用 lift:

def show_entryform():
    ...
    self.bottomframe.place(relx=.5, rely=1, anchor="s", relwidth=1.0)
    self.bottomframe.lift()

当然,使用 place_forget 而不是 pack_forget:

def hide_entryform(): 
    ...
    self.bottomframe.place_forget()
    ...