tkinter scrollable canvas after adding widgets with grid manager - python

I'm trying to create a canvas widget with a number of widgets embedded within it. Since there will frequently be too many widgets to fit in the vertical space I have for the canvas, it'll need to be scrollable.
import tkinter as tk # for general gui
import tkinter.ttk as ttk # for notebook (tabs)
class instructionGeneratorApp():
def __init__(self, master):
# create a frame for the canvas and scrollbar
domainFrame = tk.LabelFrame(master)
domainFrame.pack(fill=tk.BOTH, expand=1)
# make the canvas expand before the scrollbar
domainFrame.rowconfigure(0,weight=1)
domainFrame.columnconfigure(0,weight=1)
vertBar = ttk.Scrollbar(domainFrame)
vertBar.grid(row=0, column=1, sticky=tk.N + tk.S)
configGridCanvas = tk.Canvas(domainFrame,
bd=0,
yscrollcommand=vertBar.set)
configGridCanvas.grid(row=0, column=0, sticky=tk.N + tk.S + tk.E + tk.W)
vertBar.config(command=configGridCanvas.yview)
# add widgets to canvas
l = tk.Label(configGridCanvas, text='Products')
l.grid(row=1, column=0)
r = 2
for product in ['Product1','Product2','Product3','Product4','Product5','Product6','Product7','Product8','Product9','Product10','Product11','Product12','Product13','Product14','Product15','Product16','Product17','Product18','Product19','Product20']:
l = tk.Label(configGridCanvas, text=product)
l.grid(row=r, column=0)
c = tk.Checkbutton(configGridCanvas)
c.grid(row=r, column=1)
r += 1
ButtonFrame = tk.Frame(domainFrame)
ButtonFrame.grid(row=r, column=0)
removeServerButton = tk.Button(ButtonFrame, text='Remove server')
removeServerButton.grid(row=0, column=0)
# set scroll region to bounding box?
configGridCanvas.config(scrollregion=configGridCanvas.bbox(tk.ALL))
root = tk.Tk()
mainApp = instructionGeneratorApp(root)
root.mainloop()
As best as I can tell, I'm following the effbot pattern for canvas scrollbars, but I end up with either a scrollbar that isn't bound to the canvas, or a canvas that is extending beyond the edges of its master frame:
I've attempted the solutions on these questions, but there's still something I'm missing:
resizeable scrollable canvas with tkinter
Tkinter, canvas unable to scroll
Any idea what I'm doing wrong?

I have added some comments to #The Pinapple 's solution for future reference.
from tkinter import *
class ProductItem(Frame):
def __init__(self, master, message, **kwds):
Frame.__init__(self, master, **kwds)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
self.text = Label(self, text=message, anchor='w')
self.text.grid(row=0, column=0, sticky='nsew')
self.check = Checkbutton(self, anchor='w')
self.check.grid(row=0, column=1)
class ScrollableContainer(Frame):
def __init__(self, master, **kwargs):
#our scrollable container is a frame, this frame holds the canvas we draw our widgets on
Frame.__init__(self, master, **kwargs)
#grid and rowconfigure with weight 1 are used for the scrollablecontainer to utilize the full size it can get from its parent
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
#canvas and scrollbars are positioned inside the scrollablecontainer frame
#the scrollbars take a command parameter which is used to position our view on the canvas
self.canvas = Canvas(self, bd=0, highlightthickness=0)
self.hScroll = Scrollbar(self, orient='horizontal',
command=self.canvas.xview)
self.hScroll.grid(row=1, column=0, sticky='we')
self.vScroll = Scrollbar(self, orient='vertical',
command=self.canvas.yview)
self.vScroll.grid(row=0, column=1, sticky='ns')
self.canvas.grid(row=0, column=0, sticky='nsew')
#We do not only need a command to position but also one to scroll
self.canvas.configure(xscrollcommand=self.hScroll.set,
yscrollcommand=self.vScroll.set)
#This is the frame where the magic happens, all of our widgets that are needed to be scrollable will be positioned here
self.frame = Frame(self.canvas, bd=2)
self.frame.grid_columnconfigure(0, weight=1)
#A canvas itself is blank, we must tell the canvas to create a window with self.frame as content, anchor=nw means it will be positioned on the upper left corner
self.canvas.create_window(0, 0, window=self.frame, anchor='nw', tags='inner')
self.product_label = Label(self.frame, text='Products')
self.product_label.grid(row=0, column=0, sticky='nsew', padx=2, pady=2)
self.products = []
for i in range(1, 21):
item = ProductItem(self.frame, ('Product' + str(i)), bd=2)
item.grid(row=i, column=0, sticky='nsew', padx=2, pady=2)
self.products.append(item)
self.button_frame = Frame(self.frame)
self.button_frame.grid(row=21, column=0)
self.remove_server_button = Button(self.button_frame, text='Remove server')
self.remove_server_button.grid(row=0, column=0)
self.update_layout()
#If the widgets inside the canvas / the canvas itself change size,
#the <Configure> event is fired which passes its new width and height to the corresponding callback
self.canvas.bind('<Configure>', self.on_configure)
def update_layout(self):
#All pending events, callbacks, etc. are processed in a non-blocking manner
self.frame.update_idletasks()
#We reconfigure the canvas' scrollregion to fit all of its widgets
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
#reset the scroll
self.canvas.yview('moveto', '1.0')
#fit the frame to the size of its inner widgets (grid_size)
self.size = self.frame.grid_size()
def on_configure(self, event):
w, h = event.width, event.height
natural = self.frame.winfo_reqwidth() #natural width of the inner frame
#If the canvas changes size, we fit the inner frame to its size
self.canvas.itemconfigure('inner', width=w if w > natural else natural)
#dont forget to fit the scrollregion, otherwise the scrollbar might behave strange
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
if __name__ == "__main__":
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
sc = ScrollableContainer(root, bd=2)
sc.grid(row=0, column=0, sticky='nsew')
root.mainloop()

From what I can tell you are looking for something like this..
from tkinter import *
class ProductItem(Frame):
def __init__(self, master, message, **kwds):
Frame.__init__(self, master, **kwds)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
self.text = Label(self, text=message, anchor='w')
self.text.grid(row=0, column=0, sticky='nsew')
self.check = Checkbutton(self, anchor='w')
self.check.grid(row=0, column=1)
class ScrollableContainer(Frame):
def __init__(self, master, **kwargs):
Frame.__init__(self, master, **kwargs) # holds canvas & scrollbars
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.canvas = Canvas(self, bd=0, highlightthickness=0)
self.hScroll = Scrollbar(self, orient='horizontal',
command=self.canvas.xview)
self.hScroll.grid(row=1, column=0, sticky='we')
self.vScroll = Scrollbar(self, orient='vertical',
command=self.canvas.yview)
self.vScroll.grid(row=0, column=1, sticky='ns')
self.canvas.grid(row=0, column=0, sticky='nsew')
self.canvas.configure(xscrollcommand=self.hScroll.set,
yscrollcommand=self.vScroll.set)
self.frame = Frame(self.canvas, bd=2)
self.frame.grid_columnconfigure(0, weight=1)
self.canvas.create_window(0, 0, window=self.frame, anchor='nw', tags='inner')
self.product_label = Label(self.frame, text='Products')
self.product_label.grid(row=0, column=0, sticky='nsew', padx=2, pady=2)
self.products = []
for i in range(1, 21):
item = ProductItem(self.frame, ('Product' + str(i)), bd=2)
item.grid(row=i, column=0, sticky='nsew', padx=2, pady=2)
self.products.append(item)
self.button_frame = Frame(self.frame)
self.button_frame.grid(row=21, column=0)
self.remove_server_button = Button(self.button_frame, text='Remove server')
self.remove_server_button.grid(row=0, column=0)
self.update_layout()
self.canvas.bind('<Configure>', self.on_configure)
def update_layout(self):
self.frame.update_idletasks()
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
self.canvas.yview('moveto', '1.0')
self.size = self.frame.grid_size()
def on_configure(self, event):
w, h = event.width, event.height
natural = self.frame.winfo_reqwidth()
self.canvas.itemconfigure('inner', width=w if w > natural else natural)
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
if __name__ == "__main__":
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
sc = ScrollableContainer(root, bd=2)
sc.grid(row=0, column=0, sticky='nsew')
root.mainloop()

Related

Tkinter canvas don't update on scrolling

The Canvas(blue area) din't get update on scrolling. It updates only if I resized the window.
So, where is the issue. I want to get the Canvas updated while scrolling.
class ScrollBar:
def __init__(self,root):
self.canvas = Canvas(root, bg='blue')
self.scroll_y = Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.frame = Frame(self.canvas, bg='pink')
#insert Widgets
self.create_widgets()
#Configure frame in canvas
self.frame_id = self.canvas.create_window(0, 0, window=self.frame, anchor='nw')
#Update scrollregion
self.canvas.update_idletasks()
self.canvas.configure(scrollregion=self.canvas.bbox('all'),yscrollcommand=self.scroll_y.set)
self.canvas.pack(fill='both', expand=True, side='left')
self.scroll_y.pack(fill='y', side='right')
#Configure resized window
self.frame.grid_columnconfigure(0, weight=1)
self.canvas.bind("<Configure>", self.resize_frame)
def create_widgets(self):
for i in range(20):
Button(self.frame, text=f'Button {i}').grid(row=i, column=0, sticky='nsew', pady=2)
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
def resize_frame(self,event):
self.canvas.itemconfig(self.frame_id, height=event.height, width=event.width)
root = Tk()
ScrollBar(root)
root.mainloop()
Screenshot of Output

tkinter scrollbar (frame/canvas) in application that create SubFrames

I have researched and tested a number of solutions on stackoverflow and other sites but I cannot get a scrollbar to work with my application.
I'm looking for a scrollbar capable of scrolling the SubFrame group that my application creates. I also need to delimit a minimum window height, and that this window can be extended, but without breaking the scrollbar.
Thank you in advance to all who will help me.
I admit not having understood which should go above the other, the Canvas or the Frame.
Here is a piece of code, with all the graphical widgets to visualize the scrollbar effect :
import tkinter as tk
from tkinter import ttk
class FrameStack(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.subframes = []
self.topFrame = tk.Frame(root)
self.topFrame.pack(side="top", fill="x")
self.groupOfFrames = tk.Frame(root)
self.groupOfFrames.pack(side="top", fill="both", expand=True, pady=(32,0))
self.scrollbar = tk.Scrollbar(self.groupOfFrames, orient="vertical")
self.scrollbar.pack(side="right",fill="y")
#self.canvas = tk.Canvas(self.groupOfFrames, yscrollcommand=self.scrollbar.set)
#self.canvas.pack()
#self.canvas_window = self.canvas.create_window(0, 0, anchor="nw", window=self.groupOfFrames)
self.create_widget()
def create_widget(self):
tk.Label(self.topFrame, text="BUS").grid(row=0, column=0)
tk.Label(self.topFrame, text="IP").grid(row=1, column=0)
tk.Label(self.topFrame, text="REG").grid(row=2, column=0)
tk.Label(self.topFrame, text="SIZE").grid(row=3, column=0)
self.combobox_bus = ttk.Combobox(self.topFrame, values=list(dic.keys()), width=40, justify='center', state="readonly")
self.combobox_bus.bind('<<ComboboxSelected>>', self.getUpdateDataIP)
self.combobox_bus.grid(row=0, column=1, sticky="nsew")
self.combobox_ip = ttk.Combobox(self.topFrame, justify='center', width=40, state="readonly")
self.combobox_ip.bind('<<ComboboxSelected>>', self.getUpdateDataReg)
self.combobox_ip.grid(row=1, column=1, sticky="nsew")
self.button_read = tk.Button(self.topFrame, text="Read", command=self.read)
self.button_write = tk.Button(self.topFrame, text="Write", command=self.write)
self.button_read.grid(row=0, column=2, columnspan=2, sticky="nsew")
self.button_write.grid(row=1, column=2, columnspan=2, sticky="nsew")
self.button_hsid = tk.Button(self.topFrame, text="HSID")
self.button_hsid.grid(row=0, column=4, columnspan=2, sticky="nsew")
self.button_add = tk.Button(self.topFrame, text="+", command=self.add_frame)
self.button_add.grid(row=1, column=4, columnspan=2, sticky="nsew")
self.combobox_reg_dl = ttk.Combobox(self.topFrame, width=40, justify='center', state="readonly")
self.combobox_reg_dl.bind('<<ComboboxSelected>>', self.getUpdateDisplayReg)
self.combobox_reg_dl.grid(row=2, column=1, sticky="nsew")
self.button_dump = tk.Button(self.topFrame, text="Dump", command=self.dump)
self.button_load = tk.Button(self.topFrame, text="Load", command=self.load)
self.button_dump.grid(row=2, column=2, columnspan=2, sticky="nsew")
self.button_load.grid(row=2, column=4, columnspan=2, sticky="nsew")
self.select_size = tk.StringVar()
self.select_size.set("0")
self.entry_size = tk.Entry(self.topFrame, textvariable=self.select_size, justify='center')
self.entry_size.grid(row=3, column=1, sticky="nsew")
tk.Scale(self.topFrame, from_=0, to=1024, variable=self.select_size)
self.select_read_size = tk.IntVar()
self.select_read_size.set(8)
self.radio_8 = tk.Radiobutton(self.topFrame, text=" 8", variable=self.select_read_size, value=8)
self.radio_16 = tk.Radiobutton(self.topFrame, text="16", variable=self.select_read_size, value=16)
self.radio_32 = tk.Radiobutton(self.topFrame, text="32", variable=self.select_read_size, value=32)
self.radio_64 = tk.Radiobutton(self.topFrame, text="64", variable=self.select_read_size, value=64)
self.radio_8.grid(row=3, column=2)
self.radio_16.grid(row=3, column=3)
self.radio_32.grid(row=3, column=4)
self.radio_64.grid(row=3, column=5)
def getUpdateDataIP(self, event):
self.combobox_ip['values'] = list(dic[self.combobox_bus.get()].keys())
self.combobox_ip.set('')
self.combobox_reg_dl['values'] = ['']
self.combobox_reg_dl.set('')
for f in self.subframes:
f.combobox_reg_rw['values'] = ['']
f.combobox_reg_rw.set('')
def getUpdateDataReg(self, event):
self.combobox_reg_dl['values'] = list(dic[self.combobox_bus.get()][self.combobox_ip.get()].keys())
self.combobox_reg_dl.set('')
for f in self.subframes:
f.combobox_reg_rw['values'] = list(dic[self.combobox_bus.get()][self.combobox_ip.get()].keys())
f.combobox_reg_rw.set('')
def getUpdateDisplayReg(self, event):
self.combobox_reg_dl.set(self.combobox_reg_dl.get()+" ("+dic[self.combobox_bus.get()][self.combobox_ip.get()][self.combobox_reg_dl.get()]+")")
def delete_frame(self, frame):
self.subframes.remove(frame)
frame.destroy()
def add_frame(self):
f = SubFrame(parent=self.groupOfFrames, controller=self)
if self.combobox_bus.get()!="" and self.combobox_ip.get()!="":
f.combobox_reg_rw['values'] = list(dic[self.combobox_bus.get()][self.combobox_ip.get()].keys())
self.subframes.append(f)
f.pack(side="top", fill="x", pady=(0,5))
def read(self):
pass
def write(self):
pass
def dump(self):
pass
def load(self):
pass
class SubFrame(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
self.parent = parent
self.controller = controller
self.create_widget()
def create_widget(self):
self.combobox_reg_rw = ttk.Combobox(self, width=47, justify='center', state="readonly")
self.combobox_reg_rw.bind('<<ComboboxSelected>>', self.getUpdateDisplayReg)
self.select_val = tk.StringVar()
self.entry_value = tk.Entry(self, bd=1.5, width=20, textvariable=self.select_val, justify='center')
self.button_write = tk.Button(self, text="Write", command=self.write)
self.button_read = tk.Button(self, text="Read", command=self.read)
self.button_help = tk.Button(self, text="?", command=self.help)
self.button_remove = tk.Button(self, text="-", command=self.remove)
self.combobox_reg_rw.grid(row=0, column=0, columnspan=2, sticky="ew")
self.entry_value.grid(row=0, column=2, columnspan=2, sticky="ew")
self.button_write.grid(row=1, column=0, sticky="ew")
self.button_read.grid(row=1, column=1, sticky="ew")
self.button_help.grid(row=1, column=2, sticky="nsew")
self.button_remove.grid(row=1, column=3, sticky="nsew")
def remove(self):
self.controller.delete_frame(self)
def getUpdateDisplayReg(self, event):
self.combobox_reg_rw.set(self.combobox_reg_rw.get()+" ("+dic[self.controller.combobox_bus.get()][self.controller.combobox_ip.get()][self.combobox_reg_rw.get()]+")")
def write(self):
print(self.entry_value.get())
def read(self):
print(self.combobox_reg_rw.get())
def help(self):
pass
if __name__ == "__main__":
#dic = extractor.main()
dic = {'1': {'1.1': {'1.1.1': 'A', '1.1.2': 'B'}, '1.2': {'1.2.1': 'C', '1.2.2': 'D'}}, '2': {'2.1': {'2.1.1': 'E', '2.2.2': 'F'}, '2.2': {'2.2.1': 'G', '2.2.2': 'H'}}}
root = tk.Tk()
root.title("XXXXXX")
root.resizable(False, True)
fs = FrameStack(root)
fs.pack(fill="both", expand=True)
root.mainloop()
It is the canvas that has the ability to scroll, so your self.groupOfFrames needs to go inside the canvas. And since you want the scrollbar and canvas to appear as a single complex object, they should go in a frame.
You need to make sure that when the window is resized, the frame inside the canvas is resized as well. And you also need to make sure that when you add something to self.groupOfFrames you also update the scrollregion. These can be done by binding to the <Configure> event of each widget.
Thus, I would create the FrameStack like the following. Please note that self.topFrame and self.bottomFrame are children of self rather than root. That is a mistake I didn't catch in the code I gave you in your previous question.
class FrameStack(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.subframes = []
self.topFrame = tk.Frame(self)
self.bottomFrame = tk.Frame(self)
self.topFrame.pack(side="top", fill="x")
self.bottomFrame.pack(side="bottom", fill="both", expand=True)
self.canvas = tk.Canvas(self.bottomFrame, bd=0)
vsb = tk.Scrollbar(self.bottomFrame, command=self.canvas.yview)
self.canvas.configure(yscrollcommand=vsb.set)
vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.groupOfFrames = tk.Frame(self.canvas)
self.canvas.create_window(0, 0, anchor="nw", window=self.groupOfFrames, tags=("inner",))
self.canvas.bind("<Configure>", self._resize_inner_frame)
self.groupOfFrames.bind("<Configure>", self._reset_scrollregion)
def _reset_scrollregion(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def _resize_inner_frame(self, event):
self.canvas.itemconfig("inner", width=event.width)

How can I create this layout with Python3 using Tkinter with the grid geometry manager?

I am trying to create the following layout:
This is my code:
from . import FlowPane,JsonPane,PropertiesPane,ToolbarPane
import tkinter as tk
class ConflixEditor(tk.Tk):
def __init__(self, args):
super().__init__()
self.__dict__.update({k: v for k, v in locals().items() if k != 'self'})
self.minsize(width=1024, height=768)
self.title('Conflix Editor')
# Widget Creation
self.frame = tk.Frame(self)
self.toolbarPane = ToolbarPane.ToolbarPane(self.frame, bg='black')
self.flowPane = FlowPane.FlowPane(self.frame, bg='red')
self.propertiesPane = PropertiesPane.PropertiesPane(self.frame, bg='blue')
self.jsonPane = JsonPane.JsonPane(self.frame, bg='green')
# Widget Layout
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(2, weight=1)
self.frame.grid(row=0, column=0, sticky=tk.N+tk.E+tk.S+tk.W)
self.toolbarPane.grid(row=0, column=0, columnspan=3, rowspan=2, sticky=tk.N+tk.E+tk.W)
self.flowPane.grid(row=2, column=0, columnspan=2, rowspan=5, sticky=tk.N+tk.S+tk.W)
self.propertiesPane.grid(row=2, column=2, columnspan=1, rowspan=5, sticky=tk.N+tk.E+tk.S)
self.jsonPane.grid(row=7, column=0, columnspan=3, rowspan=3, sticky=tk.E+tk.S+tk.W)
The constructors for FlowPane, JsonPane, PropertiesPane, ToolbarPane all take two parameters: the parent widget and the background color.
Instead of getting the desired result above, I am getting the following result:
What am I doing wrong? How can I create the desired layout? Note that the background colors are just temporary to confirm that each widget is using the correct amount of space. This is eventually going to be an application for designing and building Netflix Conductor workflows. I want to have a toolbar with menus and buttons in the black area, a Canvas in the red area for displaying the flow-chart elements that represent tasks in the workflows, a Treeview for viewing properties in the blue area, and a Text Editor in the green area at the bottom for viewing/editing the raw JSON.
You need to:
specify height option for toolbarPane and jsonPane;
specify width option for propertiesPane;
add tk.E to sticky option for flowPane;
use grid_rowconfigure() and grid_columnconfigure() for self and self.frame as well
Below is an updated code:
class ConflixEditor(tk.Tk):
def __init__(self, *args):
super().__init__()
#self.__dict__.update({k: v for k, v in locals().items() if k != 'self'})
self.minsize(width=1024, height=768)
self.title('Conflix Editor')
# make self.frame use all the space of root window
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
# Widget Creation
self.frame = tk.Frame(self)
self.toolbarPane = ToolbarPane.ToolbarPane(self.frame, bg='black', height=100) # added height option
self.flowPane = FlowPane.FlowPane(self.frame, bg='red')
self.propertiesPane = PropertiesPane.PropertiesPane(self.frame, bg='blue', width=250) # added width option
self.jsonPane = JsonPane.JsonPane(self.frame, bg='green', height=200) # added height option
# Widget Layout
self.frame.grid_columnconfigure(0, weight=1) # used self.frame instead of self
self.frame.grid_rowconfigure(2, weight=1) # used self.frame instead of self
self.frame.grid(row=0, column=0, sticky=tk.N+tk.E+tk.S+tk.W)
self.toolbarPane.grid(row=0, column=0, columnspan=3, rowspan=2, sticky=tk.N+tk.E+tk.W)
self.flowPane.grid(row=2, column=0, columnspan=2, rowspan=5, sticky=tk.N+tk.E+tk.S+tk.W) # added tk.E
self.propertiesPane.grid(row=2, column=2, columnspan=1, rowspan=5, sticky=tk.N+tk.E+tk.S)
self.jsonPane.grid(row=7, column=0, columnspan=3, rowspan=3, sticky=tk.E+tk.S+tk.W)

Tkinter labels not filling up in the Y direction after using pack even though fill=tk.Y was added

I have been looking at various sources but to no avail.
In my tkinter window, I have running Labels which start with "start" to 1 and all the way to 100. I have two problems:
My labels are not stretching out in the Y direction even though fill=tk.Y is called (for self.firstlabel.pack and x.pack), as evidenced by the green background. Why is this happening?
I've also tried to get the canvas height by first calling self.update() and then printing out the canvas height using print(self.canvas.winfo_height()). However the canvas height is still 1. Why is this the case?
Thank you all in advance for your answers!
import tkinter as tk
class Test(tk.Tk):
def __init__(self, tasks=None):
super().__init__()
self.title("Test")
# setting up container
container = tk.Frame(self, background="bisque")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
container.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.E, tk.W))
container.grid_columnconfigure(0, weight=1)
container.grid_rowconfigure(0, weight=3)
container.grid_rowconfigure(1, weight=1)
# two frames in container, container1 for labels, container2 for textbox
self.container1 = tk.Frame(container, background="yellow")
self.container2 = tk.Frame(container, background="blue")
self.container1.grid(row=0, sticky=(tk.N, tk.S, tk.E, tk.W))
self.container2.grid(row=1, sticky=(tk.N, tk.S, tk.E, tk.W))
self.canvas = tk.Canvas(self.container1, borderwidth=0, background="green")
self.frameinsidecanvas = tk.Frame(self.canvas, background="pink")
self.canvas.pack(fill="both", expand=True)
self.frameinsidecanvas.pack(fill="both", expand=True)
self.update()
print(self.canvas.winfo_height())
# setup scrollbar
self.horizontalscrollbar = tk.Scrollbar(self.container1, orient="horizontal", command=self.canvas.xview)
self.canvas.configure(xscrollcommand=self.horizontalscrollbar.set)
self.horizontalscrollbar.pack(side="bottom", fill="x")
self.canvas.create_window((0, 100), window=self.frameinsidecanvas, anchor="w")
self.textbox = tk.Text(self.container2, height=1)
self.textbox.pack(fill="both", expand=True)
# creating the instructional label
self.firstlabel = tk.Label(self.frameinsidecanvas, text="Start")
# self.tasks.append(self.firstlabel)
self.firstlabel.pack(side="left", fill=tk.Y, expand=True)
# showing the labels
for labels in range(0,100):
x = tk.Label(self.frameinsidecanvas, text=str(labels))
x.pack(side=tk.LEFT, fill=tk.Y, expand=True)
self.bind("<Configure>", self.on_frame_configure)
def on_frame_configure(self, event=None):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
test = Test()
test.wm_geometry("1100x500")
test.mainloop()
The problem is that at the start of the program the canvas height is equal to 1. After that the tkinter window is rendered it changes the value.
You can simply bind a canvas listner to <Configure> for get when the height changes and after modify the inner fram of consequence.
import tkinter as tk
# You want to display the labels in column or that each label had the height of the canvas?
class Test(tk.Tk):
def __init__(self, tasks=None):
super().__init__()
self.canvas_width = None
self.title("Test")
# setting up container
container = tk.Frame(self, background="bisque")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
container.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.E, tk.W))
container.grid_columnconfigure(0, weight=1)
container.grid_rowconfigure(0, weight=3)
container.grid_rowconfigure(1, weight=1)
# two frames in container, container1 for labels, container2 for textbox
self.container1 = tk.Frame(container, background="yellow")
self.container2 = tk.Frame(container, background="blue")
self.container1.grid(row=0, sticky=(tk.N, tk.S, tk.E, tk.W))
self.container2.grid(row=1, sticky=(tk.N, tk.S, tk.E, tk.W))
self.canvas = tk.Canvas(self.container1, borderwidth=0, background="black")
self.frameinsidecanvas = tk.Frame(self.canvas, background="pink")
self.canvas.pack(fill="both", expand=True)
self.frameinsidecanvas.pack(fill="both", expand=True)
# setup scrollbar
self.horizontalscrollbar = tk.Scrollbar(self.container1, orient="horizontal", command=self.canvas.xview)
self.canvas.configure(xscrollcommand=self.horizontalscrollbar.set)
self.horizontalscrollbar.pack(side="bottom", fill="x")
self.canvas.create_window((0, 100), window=self.frameinsidecanvas, anchor="w")
self.textbox = tk.Text(self.container2, height=1)
self.textbox.pack(fill="both", expand=True)
# creating the instructional label
self.firstlabel = tk.Label(self.frameinsidecanvas, text="start")
# self.tasks.append(self.firstlabel)
self.firstlabel.pack(side="left", fill=tk.Y)
# showing the labels
for label_index in range(0, 10):
x = tk.Label(self.frameinsidecanvas, text=label_index)
x.pack(side=tk.LEFT, fill=tk.Y, expand=True)
self.canvas.bind("<Configure>", self.update_width)
def update_width(self, e):
self.canvas_width = self.canvas.winfo_width()
self.canvas_height = self.canvas.winfo_height()
self.firstlabel.configure(height=self.canvas_height)
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
test = Test()
test.wm_geometry("1100x500")
test.mainloop()
For the first question,
If you want to fill the label widget, use firstlabel.pack(fill = 'x')
if you want to align the text on the left side, use firstlabel = tk.Label(frameinsidecanvas, text="Start", anchor = 'w') I hope you got the idea.
The below code will align the text on the left side and fill the label widget.
self.firstlabel = tk.Label(self.frameinsidecanvas, text="Start", anchor = 'w')
self.firstlabel.pack(fill=tk.Y, expand=True)

Python 3.5 tkinter: resizing widgets to window

I am reading, reading and reading but still unable to find the appropriate answer to my issue. I plan to create a 'Masterwindow' in which I implement several frames (currently a Navigation Panel to the left, a 'RootFrame', which shows up, when no process is running, a Statusbar at the bottom, which is for alerts and processings and a main frame. Running the Code below, everything just works fine, but the Frames do not adjust to the resize of the window.
Now, I know, I need to do some sticking in main- and subclasses and set grid_column- and rowconfigure to a weight of >0 but still nothing happens.
I cannot see the reason why. What am I missing? Do I need to put everything in another masterframe?? which I stick to the masterwindow? that can't be right since I am inheriting Frames everywhere...
Thanks for your effort and Input. rgds
p.s: oh by the way: can any1 tell me how to iterate through the rows in a grid method so that I can just simply say 'grid the widget in the next row' (relative) and dont have to use absolute integers?
# -*- coding: UTF-8 -*-
import tkinter.ttk
from tkinter import *
class MainApplication(tkinter.Frame):
#classmethod
def main(cls):
root = tkinter.Tk()
app = cls(root)
app.master.title('Sample')
root.resizable(True, True)
root.mainloop()
def __init__(self, parent=None, *args, **kwargs):
tkinter.Frame.__init__(self, parent, *args, **kwargs)
self.grid(sticky=N+E+S+W)
# Var-Declaration
self.h = 600
self.w = 1200
self.widget_fr_opts = dict(relief='groove', borderwidth=1, bg='#EFEFFB')
# Widget-Creation
self.rootframe = RootFrame(self)
self._visible_ = self.rootframe # internal updater what frame is visible - starting with rootframe on init
self.statusbar = Statusbar(self)
self.navbar = Navbar(self)
self.main_db = MainDB(self)
# Widget-Design
# Widget-Arrangement
self.grid_rowconfigure(0, minsize=self.h * 0.95)
self.grid_rowconfigure(1, minsize=self.h * 0.05)
self.grid_columnconfigure(0, minsize=self.w*0.15)
self.grid_columnconfigure(1, minsize=self.w*0.85)
self.navbar.grid(sticky=N+S+E+W, column=0, row=0)
self.main_db.grid(sticky=N+E+S+W, column=1, row=0)
self.rootframe.grid(sticky=N+E+S+W, column=1, row=0)
self.statusbar.grid(sticky=W+E, column=0, columnspan=2, row=1)
self.grid_columnconfigure(1, weight=1)
self.statusbar.columnconfigure(0, weight=1)
self.rootframe.columnconfigure(0, weight=1)
self.main_db.columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.navbar.rowconfigure(4, weight=1)
self.rootframe.rowconfigure(0, weight=1)
self.main_db.rowconfigure(0, weight=1)
self.rootframe.lift(self.main_db)
def visualize(self, master):
"""Lifts master to upper Level"""
master.lift(self.rootframe)
self._visible_ = master
def event_handler(self):
pass
def start_subapp(self, app):
self.visualize(app)
app.activate_content()
class RootFrame(tkinter.Frame):
"""General Launcher Frame as a Placeholder"""
def __init__(self, parent, *args, **kwargs):
tkinter.Frame.__init__(self, parent, *args, **kwargs)
# Var-Daclaration
self.widget_fr_opts = dict(relief='sunken', borderwidth=1)
self.widget_grid_opts = dict(sticky=N+E+S+W, padx=1, pady=1)
# Widget-Creation
self.Image = tkinter.ttk.Label(self, text='there would be a centered image right here', anchor='center')
# Widget-Design
self.configure(**self.widget_fr_opts)
# Widget-Arrangement
self.Image.grid(column=0, row=0, **self.widget_grid_opts)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
class Navbar(tkinter.Frame):
"""vertical NavBar"""
def __init__(self, parent, *args, **kwargs):
tkinter.Frame.__init__(self, parent, *args, **kwargs)
# Var-Daclaration
self._info_ = tkinter.StringVar()
self.widget_fr_opts = dict(relief='groove', borderwidth=1)
self.widget_grid_opts = dict(sticky=N+E+S+W, padx=1, pady=1)
self.widget_grid_subopts = dict(sticky=W+N+E, padx=1, pady=1)
self._statusbar = parent.statusbar
# Widget-Creation
self._info_.set('Menu:\n...Menusentences:')
self.TextInfo = tkinter.ttk.Label(self, textvariable=self._info_)
self.Btn_Progress = tkinter.ttk.Button(self, text='Start Progress',
command=lambda: self.statusbar_input('Starting progress ...')) # some code being started
self.Btn_Database = tkinter.ttk.Button(self, text='Database',
command=lambda: parent.start_subapp(parent.main_db)) # Database window is lifted and content initialized
self.Btn_Exit = tkinter.ttk.Button(self, text='Exit', command=parent.quit)
# Widget-Design
self.configure(**self.widget_fr_opts)
# Widget-Arrangement
self.TextInfo.grid(column=0, row=1, **self.widget_grid_subopts)
self.Btn_Progress.grid(column=0, row=2, **self.widget_grid_subopts)
self.Btn_Database.grid(column=0, row=3, **self.widget_grid_subopts)
self.Btn_Exit.grid(column=0, row=4, **self.widget_grid_subopts)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=0)
self.grid_rowconfigure(1, weight=0)
self.grid_rowconfigure(2, weight=0)
self.grid_rowconfigure(3, weight=0)
self.grid_rowconfigure(4, weight=0)
def statusbar_input(self, comm: str):
self._statusbar.start()
self._statusbar._info_.set(comm)
class Statusbar(tkinter.Frame):
"""Status-Bar at the bottom"""
def __init__(self, parent, *args, **kwargs):
tkinter.Frame.__init__(self, parent, *args, **kwargs)
# Var-Daclaration
self.prgrvalue = tkinter.IntVar()
self._info_ = tkinter.StringVar()
self._user_ = 'some user'
self.widget_fr_opts = dict(relief='sunken', borderwidth=1)
self.widget_grid_opts = dict(padx=1, pady=1)
self.widget_grid_subopts = dict(padx=1, pady=1) # sticky=W + E,
# Widget-Creation
self._info_.set('Initializing ...')
self.prgrvalue.set(0)
self.TextInfo = tkinter.ttk.Label(self, textvariable=self._info_)
self.UserInfo = tkinter.ttk.Label(self, textvariable=self._user_)
self.progress_ = tkinter.ttk.Progressbar(self)
self.Btn_Move = tkinter.ttk.Button(self, text='Move it', command=lambda: self.start()) # just for initial testing, will be removed later
self.Btn_Stop = tkinter.ttk.Button(self, text='Stop it', command=lambda: self.stop())
# Widget-Design
self.configure(**self.widget_fr_opts)
self.progress_.configure(length=200, mode='determinate', orient=tkinter.HORIZONTAL)
# Widget-Arrangement
self.progress_.grid(sticky=W+E, column=0, row=0, **self.widget_grid_subopts)
self.TextInfo.grid(column=1, row=0, **self.widget_grid_subopts)
self.UserInfo.grid(column=2, row=0, padx=1, pady=1) # sticky=E,
self.Btn_Move.grid(column=3, row=0, **self.widget_grid_subopts)
self.Btn_Stop.grid(column=4, row=0, **self.widget_grid_subopts)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, minsize=200)
self.grid_columnconfigure(2, minsize=200)
def start(self):
# just testing
self.progress_.start()
def stop(self):
# just testing
self.progress_.stop()
class MainDB(tkinter.Frame):
"""Frame for visualizing database."""
def __init__(self, parent, *args, **kwargs):
tkinter.Frame.__init__(self, parent, *args, **kwargs)
# Var-Daclaration
self._activated_ = False
self._source_ = None
self.combotext = tkinter.StringVar()
self.combotext.set('Please choose a tab...')
self.widget_fr_opts = dict(relief='sunken', borderwidth=1)
self.widget_grid_opts = dict(sticky=N+E+S+W, padx=1, pady=1)
# Widget-Creation
# CREATION OF TOOLS TO MANIPULATE DATABASE
self.toolframe = tkinter.Frame(self, width=100, height=50, relief='groove', borderwidth=1)
self.combo = tkinter.ttk.Combobox(self.toolframe, textvariable=self.combotext)
# more to come
# CREATION OF DATABASE'WINDOW
self.dbframe = tkinter.Frame(self, width=100, relief='groove', borderwidth=1)
self.db_treeview = tkinter.ttk.Treeview(self.dbframe, columns=('size', 'modified'), selectmode='extended')
# more to come
# Widget-Design
# Widget-Arrangement
self.toolframe.grid(column=0, row=0, sticky=N+E+W, padx=1, pady=1)
self.combo.grid(column=0, row=0, sticky=N+E, padx=1, pady=1)
self.dbframe.grid(column=0, row=1, sticky=NSEW, padx=1, pady=1)
self.db_treeview.grid(column=0, row=0, sticky=NSEW, padx=1, pady=1)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=0)
self.grid_rowconfigure(1, weight=1)
self.dbframe.grid_columnconfigure(0, weight=1)
self.dbframe.grid_rowconfigure(0, weight=1, minsize=600)
self.toolframe.grid_columnconfigure(0, weight=1)
self.toolframe.grid_rowconfigure(0, weight=1)
def activate_content(self):
# some contentloading and initializations
pass
def db_connector(self, comm: str, save=False) -> bool:
# some connection code
pass
if __name__ == '__main__':
UserScreen = MainApplication.main()
You are neglecting to give a weight to any rows or columns in the root window. Without it, tkinter doesn't know how to allocate extra space when you resize the window.
def main(cls):
...
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
Since you're only putting a single widget in the root window, I recommend using pack since you can accomplish everything in a single line.
self.pack(fill="both", expand=True)

Categories