需要 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 PositionMove 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,它允许您将您的小部件放置在主控器中您希望的任何位置 查看columnspanrowspan 谢谢,我会调查place...我也在玩columnspanrowspan,但发现它们在这种情况下没有帮助,因为它们似乎只是在转移以一种不可预知的方式围绕着我的按钮。如果人们能够找出 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 对此函数有两个名称:columnconfiguregrid_columnconfigure。一个只是另一个的别名。

这是具有统一列宽的框架的可视化:

我猜你不希望所有的列都具有相同的宽度,因为这会迫使 UI 比它需要的更宽。如果我正在编写这段代码,我会为带有条目小部件的行使用单独的框架,这似乎是让你的设计在非常窄和非常宽的列上都被抛弃了。

文档

网格算法的规范描述以及各种网格命令(gridgrid_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 的建议 - 按钮定位不一致的主要内容,如果未能解决你的问题,请参考以下文章

Python内建GUI模块Tkinter

Python GUI编程(Tkinter)

使用 Tkinter 的 GUI 应用程序 - 拖放

如何通过单击tkinter中不同窗口中的按钮来获取位于窗口中的GUI文本字段的参数? [重复]

Tkinter教程

gui - tkinter 开发