рамка tkinter внутри холста не расширяется, чтобы заполнить область

#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