需要 tkinter GUI 的建议 - 按钮定位不一致
Posted
技术标签:
【中文标题】需要 tkinter GUI 的建议 - 按钮定位不一致【英文标题】:Need advice with tkinter GUI - inconsistent button positioning 【发布时间】:2021-09-22 09:27:31 【问题描述】:请看下面的sn-p:
import tkinter as tk
# Grid variables
width = 6
mid_left = int(width/2 - 1)
mid_right = int(width/2)
pady_5 = 5
# Main window
main_window = tk.Tk()
main_window.title('Controller')
# Top frame
top_frame = tk.Frame(main_window)
top_frame.grid(row=0, pady=pady_5)
label_0 = tk.Label(top_frame, text='Arm Movement')
label_0.grid(row=0, columnspan=width, pady=pady_5)
button_1 = tk.Button(top_frame, text='Move to Center Position')
button_1.grid(row=1, column=mid_left, sticky='e', pady=pady_5)
button_2 = tk.Button(top_frame, text='Move to Last Position')
button_2.grid(row=1, column=mid_right, sticky='w', pady=pady_5)
entry_3_1 = tk.Entry(top_frame)
entry_3_1.grid(row=2, column=mid_left, pady=pady_5)
label_3_1 = tk.Label(top_frame, text='X:')
label_3_1.grid(row=2, column=mid_left-1, sticky='e', pady=pady_5)
entry_3_2 = tk.Entry(top_frame)
entry_3_2.grid(row=2, column=mid_right+1, pady=pady_5)
label_3_2 = tk.Label(top_frame, text='Y:')
label_3_2.grid(row=2, column=mid_right, sticky='e', pady=pady_5)
button_3 = tk.Button(top_frame, text='Move!')
button_3.grid(row=2, column=mid_right+2, sticky='w', pady=pady_5)
# Center frame
center_frame = tk.Frame(main_window)
center_frame.grid(row=1, pady=pady_5)
label_0 = tk.Label(center_frame, text='Execution')
label_0.grid(row=0, columnspan=width, pady=pady_5)
button_1 = tk.Button(center_frame, text='Quick Routine')
button_1.grid(row=1, column=mid_left, sticky='e', pady=pady_5)
button_2 = tk.Button(center_frame, text='Full Routine')
button_2.grid(row=1, column=mid_right, sticky='w', pady=pady_5)
# Bottom frame
bottom_frame = tk.Frame(main_window)
bottom_frame.grid(row=2, pady=pady_5)
label_0 = tk.Label(bottom_frame, text='Reconstruction')
label_0.grid(row=0, columnspan=width, pady=pady_5)
button_1 = tk.Button(bottom_frame, text='Quick')
button_1.grid(row=1, column=mid_left, sticky='e', pady=pady_5)
button_2 = tk.Button(bottom_frame, text='Full')
button_2.grid(row=1, column=mid_right, sticky='w', pady=pady_5)
# Open main window
main_window.mainloop()
如果您运行此示例,您会看到最上面的两个按钮“移至中心位置”和“移至最后位置”不像其他按钮那样居中。此外,输入字段似乎是任意放置的,即使它们应该彼此相邻并居中。是什么导致了这些不一致,我该如何解决?
或者有没有比使用grid
方法更好的方法开始 来放置GUI 元素?我遇到了“愚蠢”的问题,例如在偶数和奇数的网格列之间“必须决定”:选择偶数列(例如上面的示例)并且您不能居中元素(因为没有中心列) 与选择奇数列,您不能将两个元素并排居中。使用 pack
方法很快就失宠了,因为它太有限了。
更新:只是想说明,无论如何调整代码,grid
方法中似乎都有一层不可见的额外代码,它可以任意移动元素。似乎不可能获得干净、可靠的定位。看看这里的按钮位置,特别是使用sticky='e'
或='w'
对按钮Move to Center Position
和Move to Last Position
的定位完全没有效果:
import tkinter as tk
# Grid variables
width = 6
mid_left = int(width/2 - 1)
mid_right = int(width/2)
padx_5 = 10
pady_5 = 5
# Main window
main_window = tk.Tk()
main_window.title('Controller')
# Top frame
top_frame = tk.Frame(main_window)
top_frame.grid(row=0, pady=pady_5)
label_0 = tk.Label(top_frame, text='Arm Movement')
label_0.grid(row=0, columnspan=width, pady=pady_5)
button_1 = tk.Button(top_frame, text='Move to Center Position')
button_1.grid(row=3, column=mid_right, padx=padx_5, pady=pady_5)
button_2 = tk.Button(top_frame, text='Move to Last Position')
button_2.grid(row=3, column=mid_left, padx=padx_5, pady=pady_5)
label_3_1 = tk.Label(top_frame, text='X [cm]:')
label_3_1.grid(row=1, column=mid_left-1, sticky='e', padx=(20, 0), pady=pady_5)
entry_3_1 = tk.Entry(top_frame)
entry_3_1.grid(row=1, column=mid_left, pady=pady_5)
label_3_2 = tk.Label(top_frame, text='Y [cm]:')
label_3_2.grid(row=2, column=mid_left-1, sticky='e', padx=(20, 0), pady=pady_5)
entry_3_2 = tk.Entry(top_frame)
entry_3_2.grid(row=2, column=mid_left, pady=pady_5)
button_3 = tk.Button(top_frame, text='Move!')
button_3.grid(row=1, rowspan=2, column=mid_left+1, sticky='w', padx=padx_5, pady=pady_5)
# Center frame
center_frame = tk.Frame(main_window)
center_frame.grid(row=1, pady=pady_5)
label_0 = tk.Label(center_frame, text='Execution')
label_0.grid(row=0, columnspan=width, pady=pady_5)
button_1 = tk.Button(center_frame, text='Quick Routine')
button_1.grid(row=1, column=mid_left, padx=padx_5, pady=pady_5)
button_2 = tk.Button(center_frame, text='Full Routine')
button_2.grid(row=1, column=mid_right, padx=padx_5, pady=pady_5)
# Bottom frame
bottom_frame = tk.Frame(main_window)
bottom_frame.grid(row=2, pady=pady_5)
label_0 = tk.Label(bottom_frame, text='Reconstruction')
label_0.grid(row=0, columnspan=width, pady=pady_5)
button_1 = tk.Button(bottom_frame, text='Quick')
button_1.grid(row=1, column=mid_left, padx=padx_5, pady=pady_5)
button_2 = tk.Button(bottom_frame, text='Full')
button_2.grid(row=1, column=mid_right, padx=padx_5, pady=pady_5)
# Open main window
main_window.mainloop()
【问题讨论】:
您可以使用.place
,它允许您将您的小部件放置在主控器中您希望的任何位置
查看columnspan
和rowspan
。
谢谢,我会调查place
...我也在玩columnspan
和rowspan
,但发现它们在这种情况下没有帮助,因为它们似乎只是在转移以一种不可预知的方式围绕着我的按钮。如果人们能够找出 tkinter
背后的机制会很有帮助,即如何根据输入参数/变量以数学方式计算位置、大小等。或者简单地说,如果事情会更一致,需要更少的巫术
我建议你使用框架,其实我也有点困惑,但是......我通常使用框架(允许在布局管理器之间切换)
好的,将尝试按框架对元素进行分组...顺便说一句,布局管理器是什么?
【参考方案1】:
由于您将两个按钮放在第 2 行小部件的同一框架 (top_frame
) 中,因此它们的位置会受到这些小部件的影响。
您可以使用另一个框架来按住两个按钮并将该框架放在top_frame
的第 1 行:
# frame for the two buttons
top_inner_frame = tk.Frame(top_frame)
top_inner_frame.grid(row=1, column=0, columnspan=width)
button_1 = tk.Button(top_inner_frame, text='Move to Center Position')
button_1.pack(size="left", pady=pady_5)
button_2 = tk.Button(top_inner_frame, text='Move to Last Position')
button_2.pack(size="left", pady=pady_5)
【讨论】:
嗨,为什么将这些按钮放在同一个top_frame
会导致任何问题? grid
在同一个孩子中的坐标不一样吗?还是应该为每组按钮/元素创建新的内框?
如果两行中的小部件数量不同,则可能难以按照您的意愿进行布局。把它们放在不同的框架中,基本上更容易排列。
是的,这是我从简单的问题中看到的,例如以两个与三个元素为中心。但是这种看似不确定/任意的行为是如何引入的呢?它让我想起了一点 LaTeX,有时你必须使用“撬棍”将东西强制到位。如果我定义一个数学网格并在其上放置元素,我希望这些元素“服从”而不是一些引入任意变化的抽象层。例如,如果我在网格上安排 matplotlib
子图,它们实际上最终会出现在网格上并且不会左右移动
另见问题中添加的示例。更干净的 GUI - 但tkinter
的行为仍然无法预测。感觉就像使用带有额外接头的螺丝刀,使末端无法控制地翻转
@michaela_karl:“但是这种看似不确定/任意的行为是如何引入的?” - 这种行为不是不确定的。网格算法在规范文档中有准确描述:The grid algorithm【参考方案2】:
除非我误解这是因为您使用的是 .grid() 函数。
网格很棒,因为它很简单,但它并不意味着精确放置。还有两个用于放置小部件的函数,.pack() 和 .place()。我怀疑您知道 .pack() 和 .grid() 并认为网格用于更具体的放置,因为 pack 几乎无法控制放置/间距。 .place() 几乎绝对是您正在寻找的方法。
.place() 代替 grid 接受三个参数,x 值、y 值和锚点位置。您甚至可以使用相对 x 和 y 值,因此无论窗口大小如何,它都将始终出现在窗口的同一位置!我真的很喜欢这种方法。
例如
your_widget.place(relx=0.5, rely=0.5, anchor=CENTER) #places widget dead center of the window, with an anchor in the middle of the widget
您也可以从小部件上的许多不同位置进行锚定,例如,在上面的代码中,anchor=NW 而不是 anchor=CENTER 将使小部件的左上角与窗口的中心对齐。
【讨论】:
好的,我已经放弃了place
,因为我想避免自己定义位置值。此外,anchor=CENTER
似乎没有计算。它要么是 anchor='center'
,要么是 CENTER 选项已被完全删除,只剩下 'n/e/w/s'
和一些组合 - 尽管尚不清楚是否需要单引号(即作为字符串或变量输入)。关闭...
无引号。全部大写只是 CENTER。您还可以使用 N、E、W、S 以及 NW、NE 等角的组合。我不确定为什么它不适合您,我已经使用了很多次,包括在 GUI 中目前接近 2 个 KLOC 的程序。
@Matt:他们导入 tkinter 的方式,他们不能使用CENTER
。它必须是 tk.CENTER
,或者只是文字字符串 "center"
也可以。
啊,有道理。我没有考虑过它们是作为 tk 进口的;通常我只看到“from tkinter import *”无论哪种方式,.place() 都应该是实现帖子目标的有效替代方案,不是吗?【参考方案3】:
您的第一个版本的代码就像这个带有嵌套表(框架)的 html 表(根)。
元素不会随机移动。它们在同一框架内相互依赖。 每个框架都是一个独立的系统,可以根据其单元格的内容进行调整。 框架相互独立,但依赖于根窗口。
两栏一帧就够了。一个系统适用于所有元素。
试试这样的:
| col 0 | col 1 |
------|----------------------------------
row 0 | Arm Movement | columnspan=2
------|----------------------------------
row 1 | Move to Center | Move to Last | sticky='ew' for both
------|----------------------------------
row 2 | X: | Y: | sticky='ew' for both
------|----------------------------------
row 3 | Entry X | Entry Y |
------|----------------------------------
row 4 | Move! | columnspan=2
------|----------------------------------
row 5 | Execution |
------|----------------------------------
row 6 | Quick Routine | Full Routine |
------|----------------------------------
row 7 | Reconstruction |
------|----------------------------------
row 8 | Quick | Full |
------|----------------------------------
Entry X: sticky='w' and padx
Entry Y: sticky='e' and padx
【讨论】:
这没有显示任何代码,而且结果似乎与 OP 想要的不同,因为条目位于其标签下方而不是它们旁边。 @BryanOakley 我不想说出问题的作者到底想要什么。但似乎在窗口中居中元素存在问题。也许这个人正试图找到一种设计,使一切看起来都很平衡。在第二个版本中,条目的排列方式已经不同。 谢谢,这里有很多有用的答案,但我认为你找到了问题的核心……不包括实际代码也没关系,有时伪代码/结构示意图更有用。将尝试这些建议 是的,尽管问题可能表明,我实际上并没有浪费几周的强迫症与巫毒风格代码......恕我直言风格应该反映用户输入的信息量。更少的信息 => 更多的对称性。有点像LyX
使用 LaTeX
时 - 您只需很少甚至没有额外调整即可投入内容,并且样式输出已经是 80-90% 或“相当不错”。人们可以投入更多精力来调整 95-99%,但该框架应反映帕累托原则,以最大限度地提高生产力
不幸的是,我可以确认sticky='ew'
和sticky='nesw
都没有将按钮/元素扩展到它们的网格单元格边框。 tkinter
中仍有巫毒代码可以任意调整元素大小【参考方案4】:
默认情况下,每列的宽度刚好足以容纳其内容。你没有做任何事情来改变它。因此,第 2 列和第 3 列不会位于 6 列框架的中心。
这是top_frame
中网格单元的可视化。注意:第 0 列不可见,因为它的宽度为 0。
您可以通过强制所有列的大小相同来轻松验证这一点。当你这样做时,按钮将完全居中。当然,您的其他一些小部件不会是您所期望的。
要将列设置为统一宽度,请为uniform
属性赋予它们相同的值。值是什么并不重要,只要它是相同的。这必须在创建 top_frame
之后完成。
虽然 tkinter 文档暗示 columnconfigure
接受单个索引,但 tcl/tk 文档清楚地表明您可以提供索引列表。由于width
表示代码中的总列数,我们可以使用以下命令将列 0、1、2、3、4 和 5 配置为 uniform
值为 1
:
top_frame.grid_columnconfigure(tuple(range(width)), uniform=1)
注意:tkinter 对此函数有两个名称:columnconfigure
和 grid_columnconfigure
。一个只是另一个的别名。
这是具有统一列宽的框架的可视化:
我猜你不希望所有的列都具有相同的宽度,因为这会迫使 UI 比它需要的更宽。如果我正在编写这段代码,我会为带有条目小部件的行使用单独的框架,这似乎是让你的设计在非常窄和非常宽的列上都被抛弃了。
文档
网格算法的规范描述以及各种网格命令(grid
、grid_columnconfigure
等)的可用选项可在
tcl/tk man pages for the grid command。关于如何将 tcl/tk 手册页翻译成 tkinter 文档的说明可以在官方 python tkinter 文档的标题为Tkinter Life Preserver 的部分中找到。
【讨论】:
嗨,谢谢。这看起来很有趣,虽然我不懂语法。你知道如何解释columnconfigure
(tcl.tk/man/tcl8.6.11/TkCmd/grid.htm#M8) 上的文档吗?它看起来像一些奇怪的命令行语法,但我需要 Python 语法。例如,它没有说我需要传递 Python 元组。此外,uniform
被描述为需要“价值”的“选项”,但它是什么意思?它是否必须评估为布尔值、整数、...?
...例如,如果我有 6 列,是否需要插入一个元组 (2, 2, 3, 3, 2, 2)
来获取宽度为 2、2、3、3、2 和 2 的列?如果是这样,为什么额外的选项uniform
?在这个例子中,宽度不是统一的 - 所以uniform=1
会抛出异常或导致巫毒行为吗?
@michaela_karl: " 你知道如何解释关于 columnconfigure 的文档吗? - 我的回答中有一个调用columnconfigure
的例子。官方 tkinter 文档在 Tkinter Life Preserver 中给出了如何将 tcl/tk 文档翻译成 python 的提示。
@michaela_karl: "例如,如果我有 6 列,是否需要插入一个元组 (2, 2, 3, 3, 2, 2) 以获得宽度的列2、2、3、3、2 和 2?” - 不。如果第一个参数是一个元组,它代表一个列号列表。它们都将获得命令中指定的相同选项。 tuple(range(width))
解析为 (0,1,2,3,4,5)
,这意味着对于 uniform
选项,第 0、1、2、3、4 和 5 列都将收到值 1。以上是关于需要 tkinter GUI 的建议 - 按钮定位不一致的主要内容,如果未能解决你的问题,请参考以下文章