You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tkinter Grid中Rowspan设置失效问题排查

修复UI布局异常问题

问题原因

  1. 切换按钮位置偏移:MainFrame未配置行权重,且切换按钮未设置sticky属性,导致row 0未占据顶部空间,按钮被挤到内容区域中间。
  2. 内容超出屏幕:MapFrame中地图固定高度570,加上内外边距后超出窗口总高度(600);同时各级框架未设置sticky属性让组件填充可用空间,行权重配置不合理导致布局挤压。

修复方案

  1. 给MainFrame的行设置权重,让row 1占据窗口剩余空间,固定切换按钮在顶部。
  2. 调整地图高度为自适应容器,或降低固定高度避免超出窗口。
  3. 给所有grid布局的组件添加合适的sticky属性,让组件填充所在单元格空间。
  4. 优化TaskInputFrame中标签和输入框的对齐方式,避免布局混乱。

修改后的代码

import tkinter as tk
from tkinter import ttk
import tkintermapview

class GeneralFrame(ttk.Frame):
    # 补全缺失的基类
    def __init__(self, parent=None, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)

class TaskInputFrame(GeneralFrame):
    def __init__(self, parent=None, *args):
        super().__init__(parent, *args)

        self.enter_task_button = ttk.Button(self, text="Enter Task", command=self.enter_task)

        self.turnpoint1_label = ttk.Label(self, text="Turnpoint 1:")
        self.turnpoint2_label = ttk.Label(self, text="Turnpoint 2:")
        self.turnpoint3_label = ttk.Label(self, text="Turnpoint 3:")
        self.turnpoint4_label = ttk.Label(self, text="Turnpoint 4:")
        self.turnpoint5_label = ttk.Label(self, text="Turnpoint 5:")
        self.pilot_mass_label = ttk.Label(self, text="Pilot Weight:")
        self.desired_wing_loading_label = ttk.Label(self, text="Desired Wing Loading:")

        self.turnpoint1_entry = ttk.Entry(self)
        self.turnpoint2_entry = ttk.Entry(self)
        self.turnpoint3_entry = ttk.Entry(self)
        self.turnpoint4_entry = ttk.Entry(self)
        self.turnpoint5_entry = ttk.Entry(self)
        self.pilot_mass_entry = ttk.Entry(self)
        self.desired_wing_loading_entry = ttk.Entry(self)

    def place_widgets(self):
        self.enter_task_button.grid(column=0, row=7, sticky=tk.W, pady=3)

        # 输入框设置sticky让其填充列空间
        self.turnpoint1_entry.grid(column=1, row=0, sticky=tk.EW, pady=3, padx=(5,0))
        self.turnpoint2_entry.grid(column=1, row=1, sticky=tk.EW, pady=3, padx=(5,0))
        self.turnpoint3_entry.grid(column=1, row=2, sticky=tk.EW, pady=3, padx=(5,0))
        self.turnpoint4_entry.grid(column=1, row=3, sticky=tk.EW, pady=3, padx=(5,0))
        self.turnpoint5_entry.grid(column=1, row=4, sticky=tk.EW, pady=3, padx=(5,0))
        self.pilot_mass_entry.grid(column=1, row=5, sticky=tk.EW, pady=3, padx=(5,0))
        self.desired_wing_loading_entry.grid(column=1, row=6, sticky=tk.EW, pady=3, padx=(5,0))

        # 标签右对齐保持布局整齐
        self.turnpoint1_label.grid(column=0, row=0, sticky=tk.E, pady=3, padx=1)
        self.turnpoint2_label.grid(column=0, row=1, sticky=tk.E, pady=3, padx=1)
        self.turnpoint3_label.grid(column=0, row=2, sticky=tk.E, pady=3, padx=1)
        self.turnpoint4_label.grid(column=0, row=3, sticky=tk.E, pady=3, padx=1)
        self.turnpoint5_label.grid(column=0, row=4, sticky=tk.E, pady=3, padx=1)
        self.pilot_mass_label.grid(column=0, row=5, sticky=tk.E, pady=3, padx=1)
        self.desired_wing_loading_label.grid(column=0, row=6, sticky=tk.E, pady=3)

        # 配置列权重,让输入框列自适应宽度
        self.columnconfigure(1, weight=1)

    def forget_widgets(self):
        self.enter_task_button.grid_forget()

        self.turnpoint1_entry.grid_forget()
        self.turnpoint2_entry.grid_forget()
        self.turnpoint3_entry.grid_forget()
        self.turnpoint4_entry.grid_forget()
        self.turnpoint5_entry.grid_forget()
        self.pilot_mass_entry.grid_forget()
        self.desired_wing_loading_entry.grid_forget()

        self.turnpoint1_label.grid_forget()
        self.turnpoint2_label.grid_forget()
        self.turnpoint3_label.grid_forget()
        self.turnpoint4_label.grid_forget()
        self.turnpoint5_label.grid_forget()
        self.pilot_mass_label.grid_forget()
        self.desired_wing_loading_label.grid_forget()

    def enter_task(self):
        pass

class TaskOutputFrame(GeneralFrame):
    def __init__(self, parent=None, *args):
        super().__init__(parent, *args)

        self.task_duration_label = ttk.Label(self, text="Task Duration:")
        self.optimal_launch_time_label = ttk.Label(self, text="Optimal Launch Time:")
        self.finish_time_label = ttk.Label(self, text="Finish Time:")
        self.optimal_wing_loading_label = ttk.Label(self, text="Optimal Wing Loading:")
    
    def place_widgets(self):
        self.task_duration_label.grid(column=0, row=0, sticky=tk.E, pady=3)
        self.optimal_launch_time_label.grid(column=0, row=1, sticky=tk.E, pady=3)
        self.finish_time_label.grid(column=0, row=2, sticky=tk.E, pady=3)
        self.optimal_wing_loading_label.grid(column=0, row=3, sticky=tk.E, pady=3)

    def forget_widgets(self):
        self.task_duration_label.grid_forget()
        self.optimal_launch_time_label.grid_forget()
        self.finish_time_label.grid_forget()
        self.optimal_wing_loading_label.grid_forget()

class MapFrame(GeneralFrame):
    def __init__(self, parent=None, *args):
        super().__init__(parent, *args)

        # 调整地图高度避免超出窗口,同时支持自适应
        self.map = tkintermapview.TkinterMapView(width=600, height=480, corner_radius=3)

        self.map.set_position(50.853060, -3.269220)

        self.turnpoint1 = self.map.set_marker(50.853060, -3.269220, text="NHL")
        self.turnpoint2 = self.map.set_marker(50.991271, -3.491076, text="BAM")
        self.turnpoint3 = self.map.set_marker(50.897219, -2.642515, text="YEO")
        self.turnpoint4 = self.map.set_marker(50.853060, -3.269220, text="NHL")

        self.task = self.map.set_path([self.turnpoint1.position, self.turnpoint2.position, self.turnpoint3.position, self.turnpoint4.position])

    def place_widgets(self):
        # 让地图填充整个单元格
        self.map.grid(column=0, row=0, padx=15, pady=15, sticky=tk.NSEW)
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)

    def forget_widgets(self):
        self.map.grid_forget()
    
class TaskFrame(GeneralFrame):
    def __init__(self, parent=None, *args):
        super().__init__(parent, *args)

        # 配置行、列权重,让布局自适应
        self.rowconfigure(0, weight=2)
        self.rowconfigure(1, weight=1)
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=2)

        self.TaskInputFrame = TaskInputFrame(self)
        self.TaskOutputFrame = TaskOutputFrame(self)
        self.MapFrame = MapFrame(self)

        # 设置sticky让子框架填充单元格
        self.TaskInputFrame.grid(column=0, row=0, sticky=tk.NSEW, padx=5, pady=5)
        self.TaskOutputFrame.grid(column=0, row=1, sticky=tk.NSEW, padx=5, pady=5)
        self.MapFrame.grid(column=1, row=0, rowspan=2, sticky=tk.NSEW, padx=5, pady=5)
    
    def place_widgets(self):
        self.TaskInputFrame.place_widgets()
        self.TaskOutputFrame.place_widgets()
        self.MapFrame.place_widgets()

    def forget_widgets(self):
        self.TaskInputFrame.forget_widgets()
        self.TaskOutputFrame.forget_widgets()
        self.MapFrame.forget_widgets()

# 补全缺失的GliderFrame类
class GliderFrame(GeneralFrame):
    def __init__(self, parent=None, *args):
        super().__init__(parent, *args)
        self.test_label = ttk.Label(self, text="Glider Frame Content")
    
    def place_widgets(self):
        self.test_label.grid(column=0, row=0, padx=20, pady=20)
    
    def forget_widgets(self):
        self.test_label.grid_forget()

class MainFrame(GeneralFrame):
    def __init__(self, parent=None, *args):
        super().__init__(parent, *args)

        self.switch_frames_button = ttk.Button(self, text="Switch Frame", command=self.change_screens)
        # 固定按钮在顶部左侧
        self.switch_frames_button.grid(column=0, row=0, sticky=tk.W, padx=10, pady=10)

        self.TaskFrame = TaskFrame(self)
        self.GliderFrame = GliderFrame(self)
        # 让内容框架填充剩余空间
        self.TaskFrame.grid(column=0, row=1, sticky=tk.NSEW)
        self.TaskFrame.place_widgets()
        self.current_frame = 1

        # 配置行权重,让row1占据所有剩余空间
        self.rowconfigure(1, weight=1)
        self.columnconfigure(0, weight=1)

    def change_screens(self):
        if self.current_frame == 1:     
            self.TaskFrame.grid_forget()
            self.TaskFrame.forget_widgets()
            self.GliderFrame.grid(column=0, row=1, sticky=tk.NSEW)
            self.GliderFrame.place_widgets()
            self.current_frame = 2
        else:
            self.GliderFrame.grid_forget()
            self.GliderFrame.forget_widgets()
            self.TaskFrame.grid(column=0, row=1, sticky=tk.NSEW)
            self.TaskFrame.place_widgets()
            self.current_frame = 1

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.geometry("1200x600")
        self.title("Gliding Task Analysis")
        self.main_frame = MainFrame(self)
        # 让主框架填充整个窗口
        self.main_frame.grid(column=0, row=0, sticky=tk.NSEW)
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)

App().mainloop()

关键修改点说明

  • 补全了缺失的GeneralFrame和GliderFrame类,确保代码可运行。
  • 给各级框架添加columnconfigure和rowconfigure设置权重,让布局自适应窗口大小。
  • 所有grid组件添加sticky属性,让组件填充所在单元格空间。
  • 调整地图高度为480,避免超出窗口总高度,同时支持容器自适应。
  • 给MainFrame的row 1设置权重,固定切换按钮在顶部区域。

内容的提问来源于stack exchange,提问作者Connor Williams

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 10:42:01