有比.grid()更好的Tkinter布局管理器吗?

7

我的投诉

我目前正在深入研究Tkinter GUI,发现.grid()几何管理器有几个不足之处:

  1. 图形是基于它们内部最大的部件 - 这种相对性会导致不准确。

  2. 在Windows 7上,Python 2.7.3中,该程序似乎不遵循我的行号,而是更喜欢使用部件的顺序。

我的代码

我目前正在开发一个非常基本的文本编辑器,并希望在框架顶部有多个按钮。但我无法实现这一点,因为我的部件要么放置在屏幕中心占主导地位的巨大文本框的最左边,要么放置在最右边。

========Class __init__ Stuff============
def widget(self):#Place widgets here

    #Save Button
    self.saveButton = Button (self, text = "Save", command = self.saveMe)
    self.saveButton.grid(column = 0, row = 0, sticky = W)

    #Open Button
    self.openButton = Button (self, text = "Open", command = self.openMe)
    self.openButton.grid(column = 0, row = 1, sticky = W)
    #Area where you write 
    self.text = Text (self, width = (root.winfo_screenwidth() - 20),
                      height = (root.winfo_screenheight() - 10))
    self.text.grid(row = 2)
==============Mainloop/Command Stuff============

我的问题

有没有其他更准确的方式来使用 .grid() 几何管理器,或者我应该完全使用另一个函数?

谢谢!


你关于窗口不“听从你的行号”的评论是不正确的。几十年来,网格几何管理器已被证明在所有平台上都是100%功能正常的。我并不是说你的代码没有问题,只是它出现问题的原因不是你认为的那样。 - Bryan Oakley
4个回答

16

在Python Tkinter中,你有三种可用的几何管理器——gridpackplace。其中第三种是最通用的,但也非常难以使用。我更喜欢使用grid。请注意,你可以将小部件放置在其他小部件内,或者指定columnspan。因此,如果你想要如下布局:

  -------------------------
  |    Button1  | Button2 |
  -------------------------
  |     Big Widget        |
  -------------------------

使用 .grid 有两种标准的方法来实现它。第一种方法是使用 columnspan

import Tkinter as Tk
root = Tk.Tk()
b1 = Tk.Button(root,text="Button1")
b1.grid(row=0,column=0)
b2 = Tk.Button(root,text="Button2")
b2.grid(row=0,column=1)
big_widget = Tk.Canvas(root)
big_widget.grid(row=1,column=0,columnspan=2)

*请注意,还有一个完全类似的rowspan选项。
第二种方法是使用一个Frame来容纳你的按钮:
import Tkinter as Tk
root = Tk.Tk()
f = Tk.Frame(root)
f.grid(row=0,column=0)
#place buttons on the *frame*
b1 = Tk.Button(f,text="Button1")
b1.grid(row=0,column=0)
b2 = Tk.Button(f,text="Button2")
b2.grid(row=0,column=1)

big_widget = Tk.Canvas(root)
big_widget.grid(row=1,column=0)  #don't need columnspan any more.

这种方法非常有用,可以创建复杂的布局 - 如果不使用像这样的Frame对象,我不知道怎么能创建出复杂的布局...


好的.... 所以你正在使用 Tk.Canvas 而不是 Tk.Text,对吗?另外,你如何使用 .place() 并且为什么它是最通用/准确的(意思对我来说含糊不清)。 - xxmbabanexx
1
使用 .place,您可以指定要放置小部件的精确坐标。就我而言,那将是一个维护噩梦,几乎不值得一提。 - 是的,我说 "Canvas" 而不是 "Text" - 我只是需要选择一个通常比 "Button" 小部件占用更多空间的小部件 ;-) - mgilson
1
在我使用过许多不同的GUI工具包和几何管理器的经验中,使用一个非常简单的布局管理器并嵌套不同的组是最好的选择。所有那些更复杂的几何结构(我指的是你Swing和你所有不同的管理器)更难使用,而且并没有真正给你任何你不能通过嵌套方法得到的东西。 - Voo
@Voo -- 我不知道其他GUI工具包的情况 -- 我只了解Tkinter -- 但这一直是我的得力助手,我看不出有更好的方法来完成它。 - mgilson
@mgilson 在您离开之前还有一个(几个)问题。我如何使用OOP创建多个框架?我需要初始化一个新类还是一个新方法?最后,对于这个新框架中的小部件,我应该如何将它们放置在一个框架内 - 而不是另一个框架内?最后,在您的意见中,Tkinter是否足够强大,可以用于更大的项目,还是仅仅是像我这样的初学者的学习工具?您一直非常有帮助 - 非常感谢! :) - xxmbabanexx
2
关于如何使用OOP,我不知道。我并不过分纠结于使用OOP或不使用它。我只是尽力让代码按照我理解的方式运行,并努力清理界面。坐下来仔细思考你想要这些部件如何交互真的很有帮助——一旦你弄清楚了数据在程序中应该如何传递,通常设计GUI就会更容易。我认为你可以在大型项目中使用Tkinter。我知道Bryan Oakley会同意这一点——但其他人可能会持不同意见。例如,大型项目通常使用gtk/wxWidgets。 - mgilson

7
“最好的”几何管理器取决于您想要做什么,没有一个几何管理器适用于所有情况。对于您在此特定情况下要做的事情,pack可能是更好的选择。同时请注意,在应用程序中可能会有多个“最佳”选项。在应用程序的不同部分中使用grid和pack是非常正常的。我很少创建界面,其中不使用grid和pack。并且很少使用place。 pack擅长将物品放置在单个行和单个列中。例如,工具栏是使用pack的完美案例,因为您希望所有按钮都靠左对齐。根据其名称,grid在需要固定行和列时表现最佳。当既不能使用grid也不能使用pack时,place是有用的,因为它允许您在精确的固定或相对位置上放置小部件。
我的建议是将应用程序小部件分组,并针对每个组使用适当的几何管理器。在您的情况下,您有两个逻辑组:顶部的工具栏和底部的文本小部件和滚动条组合。因此,从两个框架开始。由于工具栏位于顶部,文本小部件在下方,因此pack效果最佳。
toolbar = tk.Frame(...)
main = tk.Frame(...)
toolbar.pack(side="top", fill="x", expand=False)
main.pack(side="bottom", fill="both", expand=True)

上述内容现在为您提供了两个易于独立修改的区域。

对于工具栏,pack仍然是最自然的选择。但在这种情况下,您希望按钮靠左而不是靠上或靠下:

b1 = tk.Button(toolbar, ...)
b2 = tk.Button(toolbar, ...)
b1.pack(side="left")
b2.pack(side="left")
...

最后是底部区域。您的示例代码没有显示滚动条,但我认为在某个时候您会想要添加它们。如果您同时使用水平和垂直滚动条,grid会很好地工作。我会这样布局:

hsb = tk.Scrollbar(main, ..., orient="horizontal", ...)
vsb = tk.Scrollbar(main, ..., orient="vertical", ...)
text = tk.Text(main, ...)
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
text.grid(row=0, column=0, sticky="nsew")
main.grid_rowconfigure(0, weight=1)
main.grid_columnconfigure(0, weight=1)

最后这一部分非常重要--你总是需要给至少一行和一列赋予权重。这告诉网格哪些行和列应该在窗口调整大小时增长和缩小。


1
我认为 place 管理器是三种管理器中最直观和准确的,因为它可以指定位置和大小的绝对和相对长度的组合。
mywidget.place(x=..., y=..., width=..., height=...) # absolute
mywidget.place(relx=..., rely=..., relwidth=..., relheight=...) # relative

# combination
pad_left=5
pad_right=5
pad_top=5
pad_bottom=5
mywidget.place(relx=0, x=pad_left, rely=0, y=pad_top,\
       relwidth=1, width=-pad_left-pad_right,\
       relheight=1, height=-pad_top-pad_bottom, anchor="e") # use the whole space except some padding

由于某种原因,.place 管理器似乎不太受欢迎,但我发现它在定义窗口小部件在父窗口调整大小时如何移动和调整大小方面表现出色。
为了简化复杂窗口的构建,我有时使用函数(类似于模拟网格行为)来辅助垂直布局,例如:
def offset_y(line_no):
    if line_no == 0:
        return 0
    else:
        return height_label + (line_no - 1)*height_button

mylabel.place(..., y=offset_y(0))
mybtn1.place(..., y=offset_y(1))
etc.

将一个标签和一系列按钮垂直地打包起来。(当然,可以在这种情况下使用 .pack 管理器,但我更喜欢使用 .place 管理器,因为这样可以更准确地理解窗口调整大小时的移动情况)。


我不理解你关于准确性的评论。例如,当我使用pack和grid时,我知道它们将如何行事。在这方面,place有什么更准确的地方吗?至于为什么大多数人不使用place,可能是因为它更加复杂:它要求你自己进行大量的数学计算。pack和grid都会为您处理大部分数学计算。 - Bryan Oakley
2
我认为我喜欢place的原因很简单,那就是它的坐标系统。@Bryan Oakley:在这个意义上非常准确:我知道我把我的小部件放在哪里,并给它绝对或相对于框架的坐标系中的哪个大小。我可以像这样添加所有小部件。我不特别喜欢pack的原因是需要一组隐形的Frame层级来分组小部件。我不特别喜欢grid的原因是必须使用'缓冲'(隐形)行/列来提供可扩展的'填充',当窗口调整大小时应该移动小部件但不修改其大小。但是也许我错过了什么? - armando.sano

-2

你可以使用坐标代替行、列等。

这可能比其他方法更容易。

示例:

from Tkinter import*
root=Tk()
Button(root,text="HI").grid(padx=100,pady=50)
root.mainloop()

尝试自己更改padx和pady值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接