#python #tkinter
#python #tkinter
Вопрос:
У меня есть класс scrollable frame, который я позаимствовал из какого-то найденного кода, и у меня возникли проблемы с настройкой его в соответствии с моими потребностями. Она управлялась .pack (), но мне нужно было использовать .grid (), поэтому я просто вставил в нее рамку ( self.region
), чтобы я мог разместить свои виджеты внутри нее. Однако виджеты внутри этой рамки не будут расширяться, чтобы соответствовать краям контейнера, и я не уверен, почему. Там много проблем, похожих на мои, но ни одно из решений, похоже, не помогло. Я пытался использовать .grid_columnconfigure
, .columnconfigure()
и .bind("Configure")
все безрезультатно. Есть ли у кого-нибудь предложения по расширению виджетов в моей прокручиваемой области на восток и запад, чтобы заполнить окно?
import tkinter as tk
from tkinter import ttk
class ScrollableFrame(ttk.Frame):
"""taken from https://blog.tecladocode.com/tkinter-scrollable-frames/ and modified to
allow for the use of grid inside self.region
Class that allows for the creation of a frame that is scrollable"""
def __init__(self, container, *args, **kwargs):
super().__init__(container, *args, **kwargs)
canvas = tk.Canvas(self)
scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
self.scrollable_frame = ttk.Frame(canvas)
canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
canvas.rowconfigure(0, weight=1)
canvas.columnconfigure(0, weight=1)
scrollbar.pack(side="right", fill="y")
self.scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(
scrollregion=canvas.bbox("all")
)
)
self.scrollable_frame.rowconfigure(0, weight=1)
self.scrollable_frame.columnconfigure(0, weight=1)
self.region=ttk.Frame(self.scrollable_frame)
self.region.pack(fill='both', expand=1)
self.region.grid_rowconfigure(0, weight=1)
self.region.grid_columnconfigure(0, weight=1)
class OtherWindow():
"""data collector object and window"""
def __init__(self, window):
self.window=tk.Toplevel(window)
self.window.grid_columnconfigure(0, weight=1)
self.window.grid_columnconfigure(1, weight=1)
self.window.grid_columnconfigure(2, weight=1)
self.window.grid_columnconfigure(3, weight=1)
self.window.grid_rowconfigure(3, weight=1)
self.lbl1=ttk.Label(self.window, text="this is a sort of long label")
self.lbl1.grid(row=0, column=0, columnspan=2)
self.lbl2=ttk.Label(self.window, text="this is another label")
self.lbl2.grid(row=0, column=2)
self.lbl3=ttk.Label(self.window, text="Other information: blah blah blah blah")
self.lbl3.grid(row=0, column=3)
self.directions=ttk.Label(self.window, text='These are instructions that are kind of long and take'
'up about this much space if I had to guess so random text random text random text', wraplength=700)
self.directions.grid(row=1, column=0, columnspan=4)
self.scrolly=ScrollableFrame(self.window)
self.scrolly.grid(row=2, column=0, columnspan=4,sticky='nsew')
self.frame=self.scrolly.region
self.fillScrollRegion()
self.continueBtn=ttk.Button(self.window, text="Do Something", command=self.do)
self.continueBtn.grid(row=3, column=0, columnspan=4, sticky='nsew')
def fillScrollRegion(self):
"""fills scrollable region with label widgets"""
for i in range(15):
for j in range(5):
lbl=ttk.Label(self.frame, text="Sample text" str(i) ' ' str(j))
lbl.grid(row=i, column=j, sticky='nsew')
def do(self):
pass
root=tk.Tk()
app=OtherWindow(root)
root.mainloop()
Ответ №1:
Проблема в том, что контейнер scrollframe Frame
не заполняет Canvas
горизонтально. Вместо того, чтобы утруждать себя исправлением некоторых копий / вставок, например, scrollframe и объяснять это, я просто дам вам свой scrollframe. Она существенно более надежна, чем та, которую вы используете, и проблема, с которой вы сталкиваетесь, с ней не существует. Я уже подключил ее к версии вашего скрипта ниже.
Решение проблемы вашего scrollframe найдено в моем on_canvas_configure
методе. Он просто сообщает, что рамка контейнера должна быть той же ширины, что и холст, в событиях canvas <Configure>
.
import tkinter as tk, tkinter.ttk as ttk
from typing import Iterable
class ScrollFrame(tk.Frame):
def __init__(self, master, scrollspeed=5, r=0, c=0, rspan=1, cspan=1, grid={}, **kwargs):
tk.Frame.__init__(self, master, **{'width':400, 'height':300, **kwargs})
#__GRID
self.grid(**{'row':r, 'column':c, 'rowspan':rspan, 'columnspan':cspan, 'sticky':'nswe', **grid})
#allow user to set width and/or height
if {'width', 'height'} amp; {*kwargs}:
self.grid_propagate(0)
#give this widget weight on the master grid
self.master.grid_rowconfigure(r, weight=1)
self.master.grid_columnconfigure(c, weight=1)
#give self.frame weight on this grid
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
#_WIDGETS
self.canvas = tk.Canvas(self, bd=0, bg=self['bg'], highlightthickness=0, yscrollincrement=scrollspeed)
self.canvas.grid(row=0, column=0, sticky='nswe')
self.frame = tk.Frame(self.canvas, **kwargs)
self.frame_id = self.canvas.create_window((0, 0), window=self.frame, anchor="nw")
vsb = tk.Scrollbar(self, orient="vertical")
vsb.grid(row=0, column=1, sticky='ns')
vsb.configure(command=self.canvas.yview)
#attach scrollbar to canvas
self.canvas.configure(yscrollcommand=vsb.set)
#_BINDS
#canvas resize
self.canvas.bind("<Configure>", self.on_canvas_configure)
#frame resize
self.frame.bind("<Configure>", self.on_frame_configure)
#scroll wheel
self.canvas.bind_all('<MouseWheel>', self.on_mousewheel)
#makes frame width match canvas width
def on_canvas_configure(self, event):
self.canvas.itemconfig(self.frame_id, width=event.width)
#when frame dimensions change pass the area to the canvas scroll region
def on_frame_configure(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
#add scrollwheel feature
def on_mousewheel(self, event):
self.canvas.yview_scroll(int(-event.delta / abs(event.delta)), 'units')
#configure self.frame row(s)
def rowcfg(self, index, **options):
index = index if isinstance(index, Iterable) else [index]
for i in index:
self.frame.grid_rowconfigure(i, **options)
#so this can be used inline
return self
#configure self.frame column(s)
def colcfg(self, index, **options):
index = index if isinstance(index, Iterable) else [index]
for i in index:
self.frame.grid_columnconfigure(i, **options)
#so this can be used inline
return self
class AuxiliaryWindow(tk.Toplevel):
def __init__(self, master, **kwargs):
tk.Toplevel.__init__(self, master, **kwargs)
self.geometry('600x300 600 200')
self.attributes('-topmost', True)
self.title('This Is Another Title') #:D
#if you reconsider things, you can accomplish more with less
labels = ["this is a sort of long label",
"this is another label",
"Other information: blah blah blah blah"]
for i, text in enumerate(labels):
ttk.Label(self, text=text).grid(row=0, column=i)
self.grid_columnconfigure(i, weight=1)
#doing it this way the text will always fit the display as long as you give it enough height to work with
instr = tk.Text(self, height=3, wrap='word', bg='gray94', font='Arial 8 bold', bd=0, relief='flat')
instr.insert('1.0', ' '.join(['instructions']*20))
instr.grid(row=1, columnspan=3, sticky='nswe')
#instantiate the scrollframe, configure the first 5 columns and return the frame. it's inline mania! :p
self.scrollframe = ScrollFrame(self, 10, 2, 0, cspan=3).colcfg(range(5), weight=1).frame
self.fillScrollRegion()
#why store a reference to this? Do you intend to change/delete it later?
ttk.Button(self, text="Do Something", command=self.do).grid(row=3, columnspan=3, sticky='ew')
def fillScrollRegion(self):
"""fills scrollable region with label widgets"""
r, c = 30, 5 #math is our friend
for i in range(r*c):
ttk.Label(self.scrollframe, text=f"row_{i%r} col_{i//r}").grid(row=i%r, column=i//r, sticky='nsew')
def do(self):
pass
class Root(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry(' 550 150')
self.title('This Is A Title Probably Or Something') #:D
aux = AuxiliaryWindow(self)
self.mainloop()
Root() if __name__ == "__main__" else None