我注意到 Tkinter Canvas 实现有一个奇怪的行为。这是一个小片段来强调这种行为。
shall_configure_innerframe = False
shall_configure_periodically = False
from tkinter import Tk, Canvas
from tkinter.ttk import Style, Frame, Label
win = Tk()
# Styles
Style().configure('outer.TFrame', background='black')
Style().configure('inner.TFrame', background='red')
# Outer frame (to highlight Canvas area)
frame = Frame(win, style='outer.TFrame')
frame.pack(expand=True, fill='both', padx=1, pady=1)
# Canvas
canvas = Canvas(frame)
canvas.pack(expand=True, fill='both', padx=3, pady=3)
# Inner frame
innerframe = Frame(canvas, style='inner.TFrame')
win_id = canvas.create_window(0, 0, anchor='nw', window=innerframe)
# Let's add some content to actually see the frame
Label(innerframe, text='AAA').pack()
# Callback when canvas is configured (to set window width to the full canvas)
def on_canvas_configure(event):
canvas_width = event.width
canvas.itemconfig(win_id, width=canvas_width)
canvas.bind('<Configure>', on_canvas_configure)
# Callback when innerframe gets configured
def on_innerframe_configure(event):
region = canvas.bbox('all')
print(f'onscroll: {region}')
canvas.config(scrollregion=region)
if shall_configure_innerframe:
innerframe.bind('<Configure>', on_innerframe_configure)
# Function to periodically call the on_innerframe_configure
def configure_periodically(*args, **kwargs):
on_innerframe_configure(None)
win.after(2000, configure_periodically)
if shall_configure_periodically:
win.after(2000, configure_periodically)
win.mainloop()
如您所见,窗口的“核心”部分是内部有框架的画布,然后调整该框架的大小以适合画布(宽度)。
根据一开始两个布尔值的配置,我可以得到这里突出显示的三个结果之一:
基本上:
scrollregion
参数(因此 shall_configure_innerframe = False
和 shall_configure_periodically = False
),我会得到结果 A:内部框架与 Canvas 边框对齐scrollregion
参数(因此 shall_configure_innerframe = False
和 shall_configure_periodically = True
),我会得到结果 A,直到计时器耗尽,然后一旦调用该函数,我就会得到结果 B:内部框架首先对齐与画布边框一起,然后出现 2px 垂直填充(但不在水平侧)scrollregion
参数(因此 shall_configure_innerframe = True
和 shall_configure_periodically
并不重要),我会得到结果 C:内框在垂直和水平两侧都有 2px 填充我在后一种情况下注意到的是,内部框架发生了移动,而不是调整了大小,因为最后两个像素消失在画布之外(我在实际应用程序中注意到了这一点,其中内容消失了)。
在所有情况下,计算出的 bbox 始终相同:
onscroll: (0, 0, 382, 19)
。
为什么画布会这样?目前我只是在右侧添加 4px 填充,但这对我来说似乎不正确。
注意:当然真正的应用程序有点复杂,我实际上需要设置滚动区域
摆弄我的代码(以及问题评论中链接的 Сергей Кох),我发现实际上 Canvas 的默认实现周围有一个 2 像素的突出显示边框。
注意:关于问题的图像,我删除了屏幕缩放,因此我也可以“测量”用户界面上的像素
例如,当我简单地将背景设置为黄色(使用
canvas = Canvas(frame, bg='yellow')
)而不创建窗口时会发生什么:
如你所见,从外面开始有
在
on_canvas_configure
函数中,event.width
参数设置为168,即黄色+灰色。
我无法找到计算画布实际视图宽度的函数。但是我发现画布将原点设置在黄色区域的开头,因此通过
canvas.canvasx(0)
我能够理解突出显示宽度是多少((0, 0) 屏幕坐标对应于画布的左上角)画布小部件,(0, 0) 画布坐标到黄色区域的开头)。
因此,我将 on_canvas_configure 函数更改为以下函数:
def on_canvas_configure(event):
canvas_width = event.width + 2 * canvas.canvasx(0)
canvas.itemconfig(win_id, width=canvas_width)
如您所见,内部小部件现在是画布的正确宽度,并且不会超出该宽度。
为了删除周围的 2px 边框,我只需要显式删除突出显示即可:
canvas = Canvas(frame, highlightthickness=0)
(在这两种情况下我都设置了
shall_configure_innerframe = True
,因为这是我的真实用例)。
TL;DR:您应该始终记住,如果没有其他说明,画布将具有 2px 的灰色边框。该边框将被考虑在项目宽度/高度中,因此视图区域将减少边框大小的两倍。要解决此问题,请将
highlightthickness
设置为 0 或在设置内容大小时考虑其大小