python - Tkinter - 调整包含其他小部件的小部件
问题描述
我正在尝试构建看起来像的 UI
------------------------------| s |
Label-1 | Canvas-1 | c |
------------------------------| r |
Label-2 | Canvas-2 | o |
------------------------------| l |
Label-3 | Canvas-3 | l |
------------------------------| |
. . . | Y |
Label-n | Canvas-n | |
------------------------------| |
| scrollbar-horizontal| |
------------------------------| |
使用下面发布的代码,我可以做到。我唯一缺少的是能够使用Canvas
对象调整部件大小,同时保持 y-scroll-bar 的功能。我找到了一种通过取消注释来启用调整大小的方法,self.frame.pack(expand=True, fill='x')
但是 y-scroll-bar 不起作用。我也可以让 y-scroll-bar 工作,但是调整Canvas
对象的大小并不能很好地工作,当我放大窗口时,我可以看到一个白色的空白区域,如下所示。
也许值得一提的是Frame
,我的代码中的内部使用了网格管理器,我试图同时使用两者grid_rowconfigure
并grid_columnconfigure
获得我想要的东西。但我无法让它工作。
我想知道如何让两者同时工作。
import tkinter
import tkinter.font
class LineData(object):
def __init__(self, desc, box):
self.desc = desc
self.box = box
class LinePlotter(object):
def __init__(self, label, canvas, desc, box):
self.label = label
self.canvas = canvas
self.desc = desc
self.box = box
def plot(self):
self.label.set(self.desc)
x1, y1 = self.box[0], 1
x2, y2 = self.box[1], 20
self.canvas.create_rectangle(x1, y1, x2, y2, outline="#D1D0CE",
width=4, fill="#D1D0CE")
self.canvas.create_rectangle(x2+100, y1, x2+(x2-x1)+100, y2, outline="#D1D0CE",
width=4, fill="#D1D0CE")
class Plotter(tkinter.Frame):
def __init__(self, parent, lines_data):
super().__init__(root)
self.root = root
self.outer_canvas = tkinter.Canvas(root, borderwidth=0, background="#ffffff")
self.frame = tkinter.Frame(self.outer_canvas, background="#ffffff")
self.scrollY = tkinter.Scrollbar(root, orient=tkinter.VERTICAL, command=self.outer_canvas.yview)
self.outer_canvas.configure(yscrollcommand=self.scrollY.set)
self.scrollY.pack(side="right", fill="y", expand=False)
self.outer_canvas.pack(side="left", fill="both", expand=True)
self.outer_canvas.create_window((4, 4), window=self.frame, anchor="nw",
tags="self.frame")
# uncommenting this parts allows to resize Canvas part but then y-scroll-bar does not work
# self.frame.pack(expand=True, fill='x')
self.frame.bind("<Configure>", self.onFrameConfigure)
self.lines_data = lines_data
def onFrameConfigure(self, event):
self.outer_canvas.configure(scrollregion=self.outer_canvas.bbox("all"))
def configure_xscroll_bar(self):
x1, y1, x2, y2 = self.canvases[0].bbox(tkinter.ALL)
x1 = y1 = 0
for c in self.canvases:
t1, u1, t2, u2 = c.bbox(tkinter.ALL)
x1 = min(x1, t1)
x2 = max(x2, t2)
y1 = min(y1, u1)
y2 = max(y2, u2)
for c in self.canvases:
c.config(scrollregion=(x1-5, y1-5, x2+5, y2+5))
def xview(self, *args):
for c in self.canvases:
c.xview(*args)
def create_x_scroll_bars(self):
self.scrollX = tkinter.Scrollbar(self.frame, orient=tkinter.HORIZONTAL)
self.scrollX['command'] = self.xview
l = len(self.lines_data)
self.scrollX.grid(row=l, column=1, sticky='NSEW')
def initUI(self):
self.create_x_scroll_bars()
self.canvases = []
h = 40
for row_idx, line_data in enumerate(self.lines_data):
desc = tkinter.StringVar()
font = tkinter.font.Font(size=11)
label = tkinter.Label(self.frame, textvariable=desc, font=font)
label.grid(row=row_idx, column=0, sticky=tkinter.W)
canvas = tkinter.Canvas(self.frame, bg='white',
xscrollcommand=self.scrollX.set,
height=h)
line = LinePlotter(desc, canvas, line_data.desc, line_data.box)
line.plot()
canvas.grid(row=row_idx, column=1, sticky='NSEW')
self.canvases.append(canvas)
self.configure_xscroll_bar()
# for row_idx in range(len(self.lines_data)+1):
# self.frame.grid_rowconfigure(row_idx, weight=1)
# self.frame.grid_columnconfigure(1, weight=1)
self.pack(expand=True, fill=tkinter.BOTH)
def prepare_data():
lst = []
for i in range(1, 20):
d = "{}".format(i)
x1 = (i+1)*20
x2 = x1+30
l = LineData(d, (x1, x2))
lst.append(l)
return lst
root = tkinter.Tk()
Plotter(root, prepare_data()).initUI()
root.mainloop()
解决方案
我能够自己解决问题,所以让我分享一下使它工作的重要因素:
- 不要使用包管理器,而是使用所有小部件和框架网格管理器;
- 正确使用 `sticky` 选项,以便正确放置和扩展小部件;
- 使用 `itemconfig` 更改函数 `onFrameConfigure` 中的框架属性;
- 为不同的小部件设置不同的背景颜色对于了解哪些小部件的行为不符合预期非常有帮助。
下面是修改后的工作代码。
import tkinter
import tkinter.font
class LineData(object):
def __init__(self, desc, box):
self.desc = desc
self.box = box
class LinePlotter(object):
def __init__(self, label, canvas, desc, box):
self.label = label
self.canvas = canvas
self.desc = desc
self.box = box
def plot(self):
self.label.set(self.desc)
x1, y1 = self.box[0], 1
x2, y2 = self.box[1], 20
self.canvas.create_rectangle(x1, y1, x2, y2, outline="#D1D0CE",
width=4, fill="#D1D0CE")
self.canvas.create_rectangle(x2+100, y1, x2+(x2-x1)+100, y2, outline="#D1D0CE",
width=4, fill="#D1D0CE")
class Plotter(tkinter.Frame):
def __init__(self, root, lines_data):
super().__init__(root, background="green")
self.root = root
self.root.grid_columnconfigure(index=0, weight=1)
self.root.grid_rowconfigure(index=0, weight=1)
self.outer_canvas = tkinter.Canvas(root, borderwidth=0, background="blue")
self.outer_canvas.grid(row=0, column=0, sticky="WENS")
self.scrollY = tkinter.Scrollbar(root, orient=tkinter.VERTICAL, command=self.outer_canvas.yview, background="red")
self.outer_canvas.configure(yscrollcommand=self.scrollY.set)
self.scrollY.grid(row=0, column=1, sticky="NSE")
self.frame = tkinter.Frame(self.outer_canvas, background="black")
self.frame_id = self.outer_canvas.create_window((4, 4), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.bind("<Configure>", self.onFrameConfigure)
self.outer_canvas.bind('<Configure>', self.onFrameConfigure)
self.lines_data = lines_data
def onFrameConfigure(self, event):
f_reqsize = (self.frame.winfo_reqwidth(), self.frame.winfo_reqheight())
c_size = (self.outer_canvas.winfo_width(), self.outer_canvas.winfo_height())
f_width = max(f_reqsize[0], c_size[0])
f_height = max(f_reqsize[1], c_size[1])
self.outer_canvas.config(scrollregion=self.outer_canvas.bbox("all"))
self.outer_canvas.itemconfig(self.frame_id, width=f_width, height=f_height)
def configure_xscroll_bar(self):
x1, y1, x2, y2 = self.canvases[0].bbox(tkinter.ALL)
x1 = y1 = 0
for c in self.canvases:
t1, u1, t2, u2 = c.bbox(tkinter.ALL)
x1 = min(x1, t1)
x2 = max(x2, t2)
y1 = min(y1, u1)
y2 = max(y2, u2)
for c in self.canvases:
c.config(scrollregion=(x1-5, y1-5, x2+5, y2+5))
def xview(self, *args):
for c in self.canvases:
c.xview(*args)
def create_x_scroll_bars(self):
self.scrollX = tkinter.Scrollbar(self.frame, orient=tkinter.HORIZONTAL)
self.scrollX['command'] = self.xview
l = len(self.lines_data)
self.scrollX.grid(row=l, column=1, sticky='NSEW')
def initUI(self):
self.create_x_scroll_bars()
self.canvases = []
self.frame.grid_columnconfigure(1, weight=1)
h = 40
for row_idx, line_data in enumerate(self.lines_data):
desc = tkinter.StringVar()
font = tkinter.font.Font(size=11)
label = tkinter.Label(self.frame, textvariable=desc, font=font)
label.grid(row=row_idx, column=0, sticky=tkinter.W)
canvas = tkinter.Canvas(self.frame, bg='white',
xscrollcommand=self.scrollX.set,
height=h)
line = LinePlotter(desc, canvas, line_data.desc, line_data.box)
line.plot()
canvas.grid(row=row_idx, column=1, sticky='NSEW')
self.canvases.append(canvas)
self.configure_xscroll_bar()
def prepare_data():
lst = []
for i in range(1, 20):
d = "{}".format(i)
x1 = (i+1)*20
x2 = x1+30
l = LineData(d, (x1, x2))
lst.append(l)
return lst
root = tkinter.Tk()
Plotter(root, prepare_data()).initUI()
root.mainloop()
推荐阅读
- python - PyGitHub - 错误 403 {“message”:“此 API 返回大小最大为 1 MB 的 blob
- javascript - 在异步函数中使用 for 循环中的 await
- php - 平均非整数时如何显示半星(星级)
- python - 使用蓝牙和 Python 改变安卓手机的音量
- r - aictab() 出错,替换有 3 行,数据有 1
- firebase - 从 Firebase 中存储的照片中解锁 Flutter 面部
- flutter - 如何使用 ListTile 记住和突出显示选定的抽屉项目?
- dart - 如何在 Dart 中检测函数是否是异步的
- python - Python:to_csv 用于解码的数据帧
- google-sheets - 如何在 ARRAYFORMULA 中使用 SUMIFS 函数,匹配两个条件,以避免复制?