简体   繁体   中英

tkinter frame inside of canvas not expanding to fill area

I have a scrollable frame class that I borrowed from some code I found, and I'm having trouble adjusting it to fit my needs. It was managed by .pack(), but I needed to use .grid(), so I simply packed a frame ( self.region ) into it so I could grid my widgets inside of that. However, the widgets inside of this frame won't expand to meet the edges of the container and I'm not sure why. Theres a lot of issues similar to mine out there, but none of the solutions seemed to help. I tried using .grid_columnconfigure , .columnconfigure() , and .bind("Configure") all to no avail. Does anyone have any suggestions to get the widgets in my scrollable region to expand east and west to fill the window?

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()

在此处输入图片说明

The problem is that the scrollframe container Frame is not filling the Canvas horizontally. Instead of bothering to fix some copy/paste, example scrollframe, and explain it, I'll just give you my scrollframe. It is substantially more robust than the one you are using, and the problem you are having doesn't exist with it. I've already plugged it into a version of your script below.

The solution to your scrollframe's issue is found in my on_canvas_configure method. It simply tells the container frame to be the same width as the canvas, on canvas <Configure> events.

在此处输入图片说明

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'} & {*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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM