tkinter:如何在不移动其他窗口小部件的情况下创建可以在另一个窗口小部件上显示和消失的列表框?

时间:2017-01-23 17:15:04

标签: python tkinter listbox

问题:我想创建一个列表框,它会在label1下面出现并消失,但是当点击label1时它不应该影响label2的位置。怎么办呢?

后台:我已经编写了一个测试代码(见下文)来显示我的问题。在第一次单击鼠标时,会出现列表框但向下推动label2。在第二次鼠标单击时,列表框隐藏但label2保留在新位置并且不返回到原始位置。我尝试过使用.grid_forget()但行为相同。重复鼠标单击只显示并隐藏列表框。

我认为我无法获得所需的窗口小部件行为,因为列表框与其他窗口小部件位于同一平面/窗口上。是这样,我认为顶级窗口是允许平面外窗口小部件存在的另一个窗口小部件?如果是这样,我该怎么做?如何将它放在label1下面?此外,顶级窗口有一个窗口标题栏。我不想要那个。如何删除?得到我想要的东西,我的思维过程是否正确?

#!/usr/bin/python3
# -*- coding: utf-8 -*-

try:
    import tkinter as tk # Python 3 tkinter modules
except ImportError:
    import Tkinter as tk # Python 2 tkinter modules

class App(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.grid(row=0, column=0, sticky='nsew')

        self.toggle = True

        self.label1 = tk.Label(self, text = 'Click me to see Names of Famous Folks')
        self.lbframe = tk.Frame(self)
        self.label2 = tk.Label(self, text = 'The World of Famous Folks! Welcome!')

        self.label1.grid(row=0, column=0, sticky='nsew', pady=10)
        self.lbframe.grid(row=1, column=0, sticky='nsew')
        self.label2.grid(row=2, column=0, sticky='nsew')

        nlist1 = ['Peter', 'Scotty', 'Walter', 'Scott', 'Mary']
        self.lbframe.list_values = tk.StringVar() 
        self.lbframe.list_values.set(nlist1) 
        self.lbframe.list= tk.Listbox(self.lbframe, height=5, width= 10,
                                      listvariable=self.lbframe.list_values)
        self.lbframe.list.grid(row=0, column=0, sticky='nsew')
        self.lbframe.list.grid_remove()

        self.label1.bind("<Button-1>", self.ShowHideListbox)


    def ShowHideListbox(self, event):
        if self.toggle: # Show
            self.toggle=False
            self.lbframe.list.grid()
            self.lbframe.list.lift(aboveThis=None)
        else: # Hide
            self.toggle=True
            self.lbframe.list.grid_remove()

if __name__ == '__main__':
    root = tk.Tk()
    app = App(root)
    app.mainloop()

1 个答案:

答案 0 :(得分:2)

如果要定位窗口小部件以使其不影响任何其他窗口小部件,则应使用place。关于place的一个好处是,它允许您相对于其他小部件放置小部件,并相对于其他小部件设置宽度和高度。

例如,要将列表框窗口(包括其包含框架)放置在触发它的窗口小部件的正下方,您可以使用place,如下所示:

self.lbframe.place(in_=event.widget, x=0, rely=1, relwidth=1.0, anchor="nw")

relwidth选项表示我们希望包含列表框的框架与条目窗口小部件一样宽。这是可选的,但它说明了place的控件类型。 in_参数指定宽度相对于哪个小部件。

由于place不会影响同一父级的其他子级,因此必须确保父窗口足够大以显示列表。由于list是嵌入式窗口小部件,因此它不会浮动在窗口上方。在您的情况下,由于您的App实例未填满整个窗口,因此不会发生这种情况。它之所以没有,是因为你忽略了在根窗口中给任何行或列任何权重。

grid非常擅长它的功能,但有时它却是错误的工具。例如,由于App是根窗口中唯一的窗口小部件,因此使用pack意味着您可以使用单个命令来填充窗口。使用grid至少需要三个(其中两个你忘了打电话)。因此,我建议您使用packApp置于root内。这不是让place工作所必需的,它只是让您的代码更容易理解。

我还建议App负责将自己置于其父级。你可以,而且它不会影响place的工作方式,但它会让你的代码更容易理解,因为你没有一个函数(App.__init__)负责布局主要窗户及其子女。

考虑到所有这些,以下是基于代码的工作解决方案。注意:如何将值分配给列表框也存在一个错误,但修复它超出了此问题的范围。

#!/usr/bin/python3
# -*- coding: utf-8 -*-

try:
    import tkinter as tk # Python 3 tkinter modules
except ImportError:
    import Tkinter as tk # Python 2 tkinter modules

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

        self.toggle = True

        self.label1 = tk.Label(self, text = 'Click me to see Names of Famous Folks')
        self.label2 = tk.Label(self, text = 'The World of Famous Folks! Welcome!')

        self.label1.grid(row=0, column=0, sticky='nsew', pady=10)
        self.label2.grid(row=2, column=0, sticky='nsew')

        nlist1 = ['Peter', 'Scotty', 'Walter', 'Scott', 'Mary']

        self.lbframe = tk.Frame(self)
        self.lbframe.list_values = tk.StringVar() 
        self.lbframe.list_values.set(nlist1) 
        self.lbframe.list= tk.Listbox(self.lbframe, height=5, width= 10,
                                      listvariable=self.lbframe.list_values)
        self.lbframe.list.pack(fill="both", expand=True)

        self.label1.bind("<Button-1>", self.ShowHideListbox)


    def ShowHideListbox(self, event):
        if self.toggle: # Show
            self.toggle=False
            self.lbframe.place(in_=event.widget, x=0, rely=1, relwidth=1.0, anchor="nw")
        else: # Hide
            self.toggle=True
            self.lbframe.place_forget()

if __name__ == '__main__':
    root = tk.Tk()
    root.geometry("400x200")
    app = App(root)
    app.pack(fill="both", expand=True)
    app.mainloop()