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

树莓派逻辑分析仪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')
      
  • 绑定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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 10:45:47