Tkinter Button 使用 pack 后为何出现在左上角
结论
按钮出现在左上角,是因为 room_selected 的父容器 room_container 没有为第 0 行和第 0 列设置 weight。即使使用了 sticky="nsew",Battle frame 也只能保持最小尺寸,停在容器左上角。
pack(anchor="center") 只能让按钮在 pack 分配的区域内居中,并不会让这个区域自动填满父容器。要把按钮放在房间区域中央,需要做三处调整:
- 让
room_selected填满room_container。 - 设置
expand=True,让pack使用父容器的剩余空间。 - 将
label、房间区域和日志区域放在不同的行中,避免多个控件占用同一个网格单元。
最小修改方案
创建 room_container 后,为它的网格设置伸缩权重:
room_container = tk.Frame(main_container, background="green")
room_container.grid(row=0, column=0, sticky="nsew")
room_container.rowconfigure(0, weight=1)
room_container.columnconfigure(0, weight=1)
再修改按钮的 pack 配置:
def add_button(self, text, command):
b = tk.Button(
self,
text=text,
command=command,
activebackground="black",
activeforeground="white",
padx=30,
pady=20,
anchor="center",
)
b.pack(expand=True)
self.buttons.append(b)
room_selected 原来的配置可以保留:
room_selected = Battle(room_container)
room_selected.grid(row=0, column=0, sticky="nsew")
room_container.rowconfigure() 和 columnconfigure() 会让 Battle frame 扩展到整个可用区域。b.pack(expand=True) 则会让按钮的布局区域取得剩余空间,按钮默认显示在这块区域的中央。
如果只写:
b.pack(anchor="center")
按钮通常只能水平居中,垂直方向仍靠近父容器顶部。这是因为 pack 默认使用 side="top",又没有扩展按钮的布局区域。
推荐的容器结构
当前代码把 main_container 和 label 都放在 root 的第 0 行、第 0 列,两个控件占用了同一个网格单元,容易发生重叠和层叠顺序问题。可以改为由 main_container 管理标题和房间区域,root 只管理主区域与日志区域:
root = create_root()
root.rowconfigure(0, weight=1)
root.rowconfigure(1, weight=0)
root.columnconfigure(0, weight=1)
main_container = tk.Frame(root, background="red")
main_container.grid(row=0, column=0, sticky="nsew")
main_container.rowconfigure(0, weight=0) # label
main_container.rowconfigure(1, weight=1) # room
main_container.columnconfigure(0, weight=1)
label = tk.Label(
main_container,
text=(
"You make your way towards the cave entrance...\n"
"but who are you anyways?"
),
fg="black",
bg="grey",
font=("Papyrus", 30),
)
label.grid(row=0, column=0, sticky="ew")
room_container = tk.Frame(main_container, background="green")
room_container.grid(row=1, column=0, sticky="nsew")
room_container.rowconfigure(0, weight=1)
room_container.columnconfigure(0, weight=1)
room_selected = Battle(room_container)
room_selected.grid(row=0, column=0, sticky="nsew")
logbox_frame = tk.Frame(root, background="red")
logbox_frame.grid(row=1, column=0, sticky="ew")
logbox_frame.rowconfigure(0, weight=1)
logbox_frame.columnconfigure(0, weight=1)
logbox = Logbox(logbox_frame)
logbox.grid(row=0, column=0, sticky="nsew")
调整后的布局关系如下:
root
├── main_container
│ ├── label
│ └── room_container
│ └── Battle
│ └── Button
└── logbox_frame
└── Logbox
按钮会显示在 label 下方、Logbox 上方,并在 Battle 区域内居中。
两种 anchor 的区别
代码中的两处 anchor 含义不同:
tk.Button(..., anchor="center")
这里的 anchor 决定按钮文字在按钮内部的位置,不会改变按钮本身的位置。
b.pack(anchor="center")
这里的 anchor 决定按钮在 pack 分配区域中的位置。如果没有设置 expand=True,这块区域通常只有按钮所需的高度,所以按钮无法在垂直方向居中。
其他注意事项
可以用 grid 管理 Battle frame,再在 Battle 内部用 pack 管理按钮,因为这两个布局管理器作用于不同的父容器。同一个父容器中的子控件不要同时交给 pack 和 grid 管理。
Logbox 最好不要在自己的 __init__() 中调用:
self.grid(...)
由创建 Logbox 的代码决定它的位置,后续会更容易维护:
logbox = Logbox(logbox_frame)
logbox.grid(row=0, column=0, sticky="nsew")
self.textframe.grid(...) 也不必放在 for 循环中。循环只需要配置各行,完成后调用一次 grid:
for i in range(6):
self.textframe.rowconfigure(i, weight=1 if i == 5 else 0)
self.textframe.grid(row=0, column=0, sticky="nsew")
备注:内容仅供参考。
