我很困惑(正如你从我的代码中看到的那样,它目前是来自各种来源的大杂烩)。我正在尝试使用可滚动的小部件网格(当前为 tk.Entry's)在放大时填充框架。目前我只是想添加一个垂直滚动条,但我打算在了解它的工作原理后添加一个水平滚动条。
我在 HeaderCol 的二维数组中有两行标题。这是为了管理标题中的合并单元格,这或多或少是有效的。我的最终目标是拥有一个类似电子表格的界面,但通过按钮、下拉菜单和网格中的条目进行受控交互。
欢迎任何建议!
from tkinter import Menu, Entry, Canvas, Frame, Tk, Label, Scrollbar
class HeaderCol:
MAX_COL = 0
HEADERS = [[]]
def __init__(self, name, row, col, width=1, header_row=0):
if row == 0:
HeaderCol.MAX_COL += col
self.name = name
self.row = row
self.col = col
self.width = width
if len(HeaderCol.HEADERS) <= header_row:
HeaderCol.HEADERS.append([])
HeaderCol.HEADERS[header_row].append(self)
class TransactionsGrid:
# create the header information
HEADER_TOP_ROW = 0
HEADER_SECOND_ROW = 1
col = 0
ENTRY = HeaderCol('Entry', HEADER_TOP_ROW, col, 2)
col += ENTRY.width
DUE = HeaderCol('Due', HEADER_TOP_ROW, col, 4)
col += DUE.width
PAID = HeaderCol('Paid', HEADER_TOP_ROW, col, 7)
col += PAID.width
TENANT_STATUS = HeaderCol('Tenant Status', HEADER_TOP_ROW, col)
col += TENANT_STATUS.width
MANAGEMENT = HeaderCol('Management', HEADER_TOP_ROW, col, 2)
col += MANAGEMENT.width
NET = HeaderCol('Net', HEADER_TOP_ROW, col)
col += NET.width
NOTES = HeaderCol('Notes', HEADER_TOP_ROW, col)
HeaderCol.MAX_COL = col + NOTES.width
col = 0
DATE = HeaderCol('Date', HEADER_SECOND_ROW, col, 1, 1)
col += DATE.width
EVENT = HeaderCol('Event', HEADER_SECOND_ROW, col, 1, 1)
col += EVENT.width
FEES = HeaderCol('Fees & Charges', HEADER_SECOND_ROW, col, 1, 1)
col += FEES.width
def __init__(self, root):
root.grid_rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
self.frame_main = Frame(root)
self.frame_main.grid(sticky="news")
self.frame_canvas = Frame(self.frame_main)
self.frame_canvas.grid(row=2, column=0, pady=(5, 0), sticky='nw')
self.frame_canvas.rowconfigure(0, weight=1)
self.frame_canvas.columnconfigure(0, weight=1)
# self.frame_canvas.grid_propagate(False)
canvas = Canvas(self.frame_canvas, bg="yellow")
canvas.grid(row=0, column=0, sticky="news")
# Link a scrollbar to the canvas
vsb = Scrollbar(self.frame_canvas, orient="vertical", command=canvas.yview)
vsb.grid(row=0, column=1, sticky='ns')
canvas.configure(yscrollcommand=vsb.set)
# Create a frame to contain the buttons
frame_buttons = Frame(canvas, bg="blue")
canvas.create_window((0, 0), window=frame_buttons, anchor='nw')
self.frame_main.pack(fill='none', expand=False)
canvas.place(relx=.5, rely=.5, anchor="center")
self.buttons = []
self.add_headers()
# add a bunch of empty tk.Entry's
for row in range(5):
button_row = []
for col in range(HeaderCol.MAX_COL):
button = Entry(self.frame_canvas)
button.configure(highlightthickness=0)
button_row.append(button)
if col == 0:
button.insert(0, 'A')
button.grid(column=col, row=row + 1, sticky="")
self.buttons.append(button_row)
frame_buttons.update_idletasks()
def add_headers(self):
button_row = []
for header in HeaderCol.HEADERS[0]:
button = Label(self.frame_canvas)
button_row.append(button)
button.config(text=header.name)
button.grid(column=header.col, row=0, columnspan=header.width, sticky="")
# self.buttons.append(button_row)
button_row = []
for header in HeaderCol.HEADERS[1]:
button = Label(self.frame_canvas)
button_row.append(button)
button.config(text=header.name)
button.grid(column=header.col, row=1, columnspan=header.width, sticky="")
# self.buttons.append(button_row)
def menubar(self):
menu = Menu(self.master)
self.master.config(menu=menu)
file_menu = Menu(menu)
menu.add_cascade(label="File", menu=file_menu)
edit_menu = Menu(menu)
menu.add_cascade(label="Edit", menu=edit_menu)
file_menu.add_command(label="Item")
file_menu.add_command(label="Exit", command=self.exit_program)
edit_menu.add_command(label="Undo")
edit_menu.add_command(label="Redo")
@staticmethod
def exit_program():
exit(0)
def main():
root = Tk()
# transaction_table = TransactionsTable(root)
transaction_table = TransactionsGrid(root)
#transaction_table = AnotherOne(root)
"""
frame = tk.Frame(root)
frame.pack()
pt = Table(frame)
pt.show()
"""
root.mainloop()
if __name__ == '__main__':
main()
感谢@Derek 的建议,我创建了以下带有可选水平滚动条的类。它或多或少起作用,但是当水平滚动时,它会移动一些小部件网格,但并不是所有小部件都显示在右侧:
self.frame_main = ScrollableFrame(root, horizontal=True)
这里是修改后的代码:
from tkinter import Frame, Scrollbar, Canvas
from tkinter.constants import RIGHT, Y, LEFT, BOTH, NW, BOTTOM, X
class ScrollableFrame(Frame):
"""
Make a frame scrollable with scrollbar on the right.
After adding or removing widgets to the scrollable frame,
call the update() method to refresh the scrollable area.
"""
def __init__(self, frame, width=16, horizontal=False):
self.y_scrollbar = Scrollbar(frame, width=width)
self.y_scrollbar.pack(side=RIGHT, fill=Y, expand=False)
if horizontal:
self.x_scrollbar = Scrollbar(frame, width=width, orient='horizontal')
self.x_scrollbar.pack(side=BOTTOM, fill=X, expand=False)
self.canvas = Canvas(frame,
yscrollcommand=self.y_scrollbar.set,
xscrollcommand=self.x_scrollbar.set)
else:
self.canvas = Canvas(frame, yscrollcommand=self.y_scrollbar.set)
self.canvas.pack(side=LEFT, fill=BOTH, expand=True)
if horizontal:
self.canvas.pack(side=BOTTOM, fill=BOTH, expand=True)
self.x_scrollbar.config(command=self.canvas.xview)
self.y_scrollbar.config(command=self.canvas.yview)
self.canvas.bind('<Configure>', self.__fill_canvas)
# base class initialization
Frame.__init__(self, frame)
# assign this obj (the inner frame) to the windows item of the canvas
self.windows_item = self.canvas.create_window(0, 0, window=self, anchor=NW)
def __fill_canvas(self, event):
"""Enlarge the windows item to the canvas width"""
canvas_width = event.width
self.canvas.itemconfig(self.windows_item, width=canvas_width, height=event.height)
def update(self):
"""Update the canvas and the scrollregion"""
self.update_idletasks()
self.canvas.config(scrollregion=self.canvas.bbox(self.windows_item))
这是我清理过的使用 ScrollableFrame 的代码:
def __init__(self, root):
self.frame_main = ScrollableFrame(root, horizontal=True)
self.buttons = [[]]
self.add_headers()
for row in range(25):
button_row = []
for col in range(HeaderCol.MAX_COL):
button = Entry(self.frame_main)
button.configure(highlightthickness=0)
button_row.append(button)
button.grid(column=col, row=row + 2, sticky="")
self.buttons.append(button_row)
self.frame_main.update()
这应该会有所帮助,它将创建一个带有滚动条的
Entry
小部件列表。
我在相关地方插入了评论。
还有一些改进可能会有所帮助。
import tkinter as tk
root = tk.Tk()
root.rowconfigure(0, weight = 1)
root.columnconfigure(0, weight = 1)
container = tk.Frame(root)
container.grid(row = 0, column = 0, sticky = tk.NSEW)
container.rowconfigure(0, weight = 1)
container.columnconfigure(0, weight = 1)
# NOTE: yscrollincrement must be defined for smooth scroll effects
canvas = tk.Canvas(container, yscrollincrement = 19, highlightthickness = 0)
canvas.grid(row = 0, column = 0, sticky = tk.NSEW)
scrollbar = tk.Scrollbar(
root, orient = tk.VERTICAL, width = 14, command = canvas.yview)
scrollbar.grid(row = 0, column = 1, sticky = tk.NS)
canvas.configure(yscrollcommand = scrollbar.set)
# frame contained in canvas
frame = tk.Frame(canvas)
# dynamically change canvas scrollregion
frame.bind(
"<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
# canvas window widget contains frame
canvas.create_window((0, 0), window = frame, anchor = tk.NW)
wide, high = 20, 22 # Set width, height of Entry widgets
for i in range(20):
K = tk.Entry(frame, width = wide, textvariable = tk.StringVar())
K.grid(row = i, column = 0, sticky = tk.NSEW)
# use access[n].get() to retrieve text from Entry widgets
access = frame.children
root.geometry(f"{wide*7}x{high*7}")
root.minsize(wide*7, high*7)
root.mainloop()