树莓派逻辑分析仪Tkinter界面:Canvas无法填充父框架求助
Tkinter逻辑分析仪界面Canvas无法填充父框架问题解决
我用树莓派开发逻辑分析仪,GUI基于Tkinter实现。添加滚动条前,界面顶部框架下方的Canvas可以占满所有可用空间;但新增包含两个Canvas的框架后,Canvas无法再填充父框架的可用空间。
界面截图描述:
顶部是包含触发按钮、通道选择下拉框的白色操作栏;下方红色父框架内,左侧是黑色窄Canvas(仅显示顶部少量空间,未垂直填充),右侧绿色框架内是黑色Canvas(仅水平拉伸,未垂直填充父框架),绿色框架底部有水平滚动条。
相关代码
import tkinter as tk from tkinter import ttk CH_NAME_WIDTH = 100 N_CHANNELS = 4 SIGNAL_WIDTH = 100 SIGNAL_HEIGHT = 200 SIGNAL_OFFSET = 5 def draw_ch_list(canvas): canvas.update() canvas.create_line(CH_NAME_WIDTH, 0, CH_NAME_WIDTH, canvas.winfo_height(), fill='white') ch_separator = int(canvas.winfo_height() / N_CHANNELS) canvas.create_text(CH_NAME_WIDTH / 2, 20, text=f'Channel{1}', fill='green', font=40) for h in range(1, N_CHANNELS): canvas.create_line(0, ch_separator * h, CH_NAME_WIDTH, ch_separator * h, fill='white') canvas.create_text(CH_NAME_WIDTH / 2, 20 + ch_separator * h, text=f'Channel{h+1}', fill='green', font=40) canvas.create_text(CH_NAME_WIDTH / 2, 20 + ch_separator * (N_CHANNELS - 1), text=f'Channel{N_CHANNELS}', fill='green', font=40) def draw_ana(canvas): ch_separator = int(canvas.winfo_height() / N_CHANNELS) for h in range(1, N_CHANNELS): canvas.create_line(0, ch_separator * h, canvas.winfo_width(), ch_separator * h, fill='white') def draw_ch(data, canvas): for count, byte in enumerate(data): if byte & 1: canvas.create_line(SIGNAL_WIDTH * (count + 1), (canvas.winfo_height() / N_CHANNELS) - SIGNAL_HEIGHT, SIGNAL_WIDTH * (count + 2), (canvas.winfo_height() / N_CHANNELS) - SIGNAL_HEIGHT, fill='red') else: canvas.create_line(SIGNAL_WIDTH * (count + 1), (canvas.winfo_height() / N_CHANNELS) - SIGNAL_OFFSET, SIGNAL_WIDTH * (count + 2), (canvas.winfo_height() / N_CHANNELS) - SIGNAL_OFFSET, fill='red') if count != 0 and data[count - 1] != data[count]: canvas.create_line(SIGNAL_WIDTH * (count + 1), (canvas.winfo_height() / N_CHANNELS) - SIGNAL_OFFSET, SIGNAL_WIDTH * (count + 1), (canvas.winfo_height() / N_CHANNELS) - SIGNAL_HEIGHT, fill='red') window = tk.Tk() window.title('PIAna') window.geometry(f'{window.winfo_screenwidth()}x{window.winfo_screenheight()}') window.grid_rowconfigure(1, weight=1) window.grid_columnconfigure(0, weight=1) # Header frm_header = tk.Frame(master=window, bg='white', relief=tk.GROOVE, borderwidth=1) lbl_triger = tk.Label(master=frm_header, text='Trigger:', font=40) btn_rising = tk.Button(master=frm_header, text='Rising') btn_falling = tk.Button(master=frm_header, text='Falling') lbl_channel = tk.Label(master=frm_header, text='Channel:', font=40) selected_channel = tk.StringVar() cb_channel = ttk.Combobox(master=frm_header, textvariable=selected_channel) cb_channel['values'] = ['1', '2', '3', '4'] cb_channel['state'] = 'readonly' lbl_triger.grid(row=0, column=0, sticky='nsew') btn_rising.grid(row=0, column=1) btn_falling.grid(row=0, column=2) lbl_channel.grid(row=0, column=3, sticky='nsew') cb_channel.grid(row=0, column=4, sticky='nsew') frm_header.grid(row=0,column=0, sticky='ew', padx=5, pady=5) # Canvas frm_canvas = tk.Frame(master=window, bg='red') frm_ana = tk.Frame(master=frm_canvas, bg='green') canvas_ch = tk.Canvas(master=frm_canvas, bg='black', width=CH_NAME_WIDTH) canvas_ana = tk.Canvas(master=frm_ana, bg='black') hbar = tk.Scrollbar(master=frm_ana, orient=tk.HORIZONTAL) hbar.config(command=canvas_ana.xview) canvas_ch.grid(row=0, column=0, sticky='n') canvas_ana.config(xscrollcommand=hbar.set) canvas_ana.grid(row=0, column=1, sticky='ew') hbar.grid(row=1, column=1, sticky='ew') frm_ana.grid(row=0, column=1, sticky='nsew') frm_canvas.grid(row=1, column=0, sticky='nsew') # draw screen analyzer draw_ch_list(canvas_ch) draw_ana(canvas_ana) draw_ch([1,0,1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0 ,0 ,1, 0, 1, 1, 0, 1, 1, 1, 1], canvas_ana) window.mainloop()
解决思路及修改方案
问题核心是Tkinter的grid布局未正确配置父容器的行/列权重,导致子组件无法随窗口拉伸。具体修改如下:
- 配置frm_canvas的布局权重:让其内部第0行和第1列可以拉伸,承载canvas_ch和frm_ana
frm_canvas.grid_rowconfigure(0, weight=1) frm_canvas.grid_columnconfigure(1, weight=1)
- 配置frm_ana的布局权重:让canvas_ana可以垂直填充frm_ana的空间
frm_ana.grid_rowconfigure(0, weight=1)
修改Canvas的sticky参数:
- canvas_ch设置为
sticky='ns',让它垂直填充父框架高度:canvas_ch.grid(row=0, column=0, sticky='ns') - canvas_ana设置为
sticky='nsew',让它同时水平和垂直填充父框架空间:canvas_ana.grid(row=0, column=1, sticky='nsew')
- canvas_ch设置为
绑定Configure事件适配尺寸变化:Canvas绘制依赖自身尺寸,窗口调整时需要重新绘制内容。添加以下代码:
# 定义重新绘制函数 def on_canvas_ch_configure(event): event.widget.delete("all") # 清除原有内容 draw_ch_list(event.widget) def on_canvas_ana_configure(event): event.widget.delete("all") draw_ana(event.widget) draw_ch(data, event.widget) # 存储数据方便重新绘制 data = [1,0,1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0 ,0 ,1, 0, 1, 1, 0, 1, 1, 1, 1] # 绑定事件 canvas_ch.bind('<Configure>', on_canvas_ch_configure) canvas_ana.bind('<Configure>', on_canvas_ana_configure)
修改后的完整代码
import tkinter as tk from tkinter import ttk CH_NAME_WIDTH = 100 N_CHANNELS = 4 SIGNAL_WIDTH = 100 SIGNAL_HEIGHT = 200 SIGNAL_OFFSET = 5 def draw_ch_list(canvas): canvas.update() canvas.create_line(CH_NAME_WIDTH, 0, CH_NAME_WIDTH, canvas.winfo_height(), fill='white') ch_separator = int(canvas.winfo_height() / N_CHANNELS) canvas.create_text(CH_NAME_WIDTH / 2, 20, text=f'Channel{1}', fill='green', font=40) for h in range(1, N_CHANNELS): canvas.create_line(0, ch_separator * h, CH_NAME_WIDTH, ch_separator * h, fill='white') canvas.create_text(CH_NAME_WIDTH / 2, 20 + ch_separator * h, text=f'Channel{h+1}', fill='green', font=40) canvas.create_text(CH_NAME_WIDTH / 2, 20 + ch_separator * (N_CHANNELS - 1), text=f'Channel{N_CHANNELS}', fill='green', font=40) def draw_ana(canvas): ch_separator = int(canvas.winfo_height() / N_CHANNELS) for h in range(1, N_CHANNELS): canvas.create_line(0, ch_separator * h, canvas.winfo_width(), ch_separator * h, fill='white') def draw_ch(data, canvas): for count, byte in enumerate(data): if byte & 1: canvas.create_line(SIGNAL_WIDTH * (count + 1), (canvas.winfo_height() / N_CHANNELS) - SIGNAL_HEIGHT, SIGNAL_WIDTH * (count + 2), (canvas.winfo_height() / N_CHANNELS) - SIGNAL_HEIGHT, fill='red') else: canvas.create_line(SIGNAL_WIDTH * (count + 1), (canvas.winfo_height() / N_CHANNELS) - SIGNAL_OFFSET, SIGNAL_WIDTH * (count + 2), (canvas.winfo_height() / N_CHANNELS) - SIGNAL_OFFSET, fill='red') if count != 0 and data[count - 1] != data[count]: canvas.create_line(SIGNAL_WIDTH * (count + 1), (canvas.winfo_height() / N_CHANNELS) - SIGNAL_OFFSET, SIGNAL_WIDTH * (count + 1), (canvas.winfo_height() / N_CHANNELS) - SIGNAL_HEIGHT, fill='red') window = tk.Tk() window.title('PIAna') window.geometry(f'{window.winfo_screenwidth()}x{window.winfo_screenheight()}') window.grid_rowconfigure(1, weight=1) window.grid_columnconfigure(0, weight=1) # Header frm_header = tk.Frame(master=window, bg='white', relief=tk.GROOVE, borderwidth=1) lbl_triger = tk.Label(master=frm_header, text='Trigger:', font=40) btn_rising = tk.Button(master=frm_header, text='Rising') btn_falling = tk.Button(master=frm_header, text='Falling') lbl_channel = tk.Label(master=frm_header, text='Channel:', font=40) selected_channel = tk.StringVar() cb_channel = ttk.Combobox(master=frm_header, textvariable=selected_channel) cb_channel['values'] = ['1', '2', '3', '4'] cb_channel['state'] = 'readonly' lbl_triger.grid(row=0, column=0, sticky='nsew') btn_rising.grid(row=0, column=1) btn_falling.grid(row=0, column=2) lbl_channel.grid(row=0, column=3, sticky='nsew') cb_channel.grid(row=0, column=4, sticky='nsew') frm_header.grid(row=0,column=0, sticky='ew', padx=5, pady=5) # Canvas frm_canvas = tk.Frame(master=window, bg='red') # 配置frm_canvas的行和列权重 frm_canvas.grid_rowconfigure(0, weight=1) frm_canvas.grid_columnconfigure(1, weight=1) frm_ana = tk.Frame(master=frm_canvas, bg='green') # 配置frm_ana的行权重 frm_ana.grid_rowconfigure(0, weight=1) canvas_ch = tk.Canvas(master=frm_canvas, bg='black', width=CH_NAME_WIDTH) canvas_ana = tk.Canvas(master=frm_ana, bg='black') hbar = tk.Scrollbar(master=frm_ana, orient=tk.HORIZONTAL) hbar.config(command=canvas_ana.xview) # 修改canvas_ch的sticky参数 canvas_ch.grid(row=0, column=0, sticky='ns') canvas_ana.config(xscrollcommand=hbar.set) # 修改canvas_ana的sticky参数 canvas_ana.grid(row=0, column=1, sticky='nsew') hbar.grid(row=1, column=1, sticky='ew') frm_ana.grid(row=0, column=1, sticky='nsew') frm_canvas.grid(row=1, column=0, sticky='nsew') # 绑定Configure事件适配尺寸变化 def on_canvas_ch_configure(event): event.widget.delete("all") draw_ch_list(event.widget) def on_canvas_ana_configure(event): event.widget.delete("all") draw_ana(event.widget) draw_ch(data, event.widget) data = [1,0,1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0 ,0 ,1, 0, 1, 1, 0, 1, 1, 1, 1] canvas_ch.bind('<Configure>', on_canvas_ch_configure) canvas_ana.bind('<Configure>', on_canvas_ana_configure) window.mainloop()
内容的提问来源于stack exchange,提问作者RASC
相关产品推荐
相关产品推荐

