How to implement a scrollbar to grid in tkinter - python

I have a hard time implementing a scrollbar into my Tkinter project. I've been through numerous articles and answered questions on how to implement a scrollbar, but I'm just unable to implement a working solution after an entire day of researching this one 'simple' matter.
My current code looks like this:
import tkinter as tk
from tkinter import Button, ttk
from PIL import ImageTk, Image
from functools import partial
import queue as qu
import math
import re
import os
window = tk.Tk()
queue = qu.Queue()
#Basic values
#the window size
windowSize = "700x1000"
#picture and container size
x, y = 200, 300
#tmp
sidepanelsize = 200
window.geometry(windowSize)
#button identifier
def change(i):
print(I)
#temporary content generator
for g in range(12):
for item in os.listdir("."):
if re.search(r"\.(jpg|png)$", item):
queue.put(item)
n = queue.qsize()
#other panels that are going to be used later
frameLeft = tk.Frame(master=window, width=sidepanelsize, relief=tk.RIDGE)
frameLeft.pack(fill=tk.Y, side=tk.LEFT)
label1 = tk.Label(master=frameLeft, text="Left Panel")
label1.pack()
buttonLeft1 = tk.Button(master=frameLeft, text="Button 1", command=lambda: print("I'm a side button!"))
buttonLeft1.pack()
frameMain = tk.Frame(master=window, relief=tk.GROOVE, borderwidth=1)
frameMain.pack(side=tk.TOP, fill=tk.X, expand=1)
# SCROLLBAR IF YOU DISABLE THIS SECTION AND PUTS SOME PICTURES IN THE FOLDER WHITH THE FILE THE CODE WORKS #
myCanvas = tk.Canvas(master=frameMain)
myCanvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
myScrollbar = ttk.Scrollbar(master=frameMain, orient=tk.VERTICAL, command=myCanvas.yview)
myScrollbar.pack(side=tk.RIGHT, fill=tk.Y)
myCanvas.configure(yscrollcommand=myScrollbar.set)
myCanvas.bind('<Configure>', lambda e: myCanvas.configure(scrollregion=myCanvas.bbox("all")))
secondFrame = tk.Frame(master=myCanvas)
myCanvas.create_window((0, 0), window=secondFrame, anchor=tk.NW)
############################ END OF SCROLLBAR ############################
noOfImgPerRow = math.floor((int(windowSize.split("x")[0])-sidepanelsize+100)/x)
imgs = []
#generates the grid
for i in range(n):
o = i
i = (o % noOfImgPerRow) + 1
j = math.floor(o/noOfImgPerRow) + 1
frameMain.columnconfigure(i, weight = 1, minsize=x+15)
frameMain.rowconfigure(i, weight = 1, minsize=y+50)
frameBox = tk.Frame(
master=frameMain,
relief=tk.RAISED,
borderwidth=1,
width = x,
height = y
)
# here the error references to
frameBox.grid(row=j, column=i, padx=5, pady=5)
img = Image.open(queue.get()).convert("RGBA")
width, height = img.size
if width/x >= height/y:
left = width/2-(round((height*x)/y))/2
right = width/2+(round((height*x)/y))/2
upper = 0
lower = height
else:
left = 0
right = width
upper = height/2-(round((width*y)/x))/2
lower = height/2+(round((width*y)/x))/2
img2 = img.crop([left, upper, right, lower])
img2 = img2.resize((x, y), Image.Resampling.LANCZOS)
imgs.append(ImageTk.PhotoImage(img2))
label = tk.Label(master = frameBox, image = imgs[-1])
label.pack()
mainButton = Button(master=frameBox, text="Start", command=partial(change, o))
mainButton.pack()
window.mainloop()
I've tried to highlight the only thing of concern, that being the scrollbar, everything else is working at the moment, I just wanted to post the whole code for better understanding if it would help in any way.
My problem is whenever I implement the scrollbar, it throws back an error stating:
Traceback (most recent call last):
File "e:\Python\starter\main.py", line 85, in <module>
frameBox.grid(row=j, column=i, padx=5, pady=5)
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.1264.0_x64__qbz5n2kfra8p0\lib\tkinter\__init__.py", line 2522, in grid_configure
self.tk.call(
_tkinter.TclError: cannot use geometry manager grid inside .!frame2 which already has slaves managed by pack
This error seems pretty self-explanatory, just grid the canvas instead of packing it, but when after a lot of small tweaking and doing things a roundabouts things
My second thought was if it has a problem with the grid to wrap the gridded frame in another bigger packed frame, like so:
yetAnotherFrame = tk.Frame(frameMain)
yetAnotherFrame.pack()
noOfImgPerRow = math.floor((int(windowSize.split("x")[0])-sidepanelsize+100)/x)
imgs = []
for i in range(n):
o = i
i = (o % noOfImgPerRow) + 1
j = math.floor(o/noOfImgPerRow) + 1
yetAnotherFrame.columnconfigure(i, weight = 1, minsize=x+15)
yetAnotherFrame.rowconfigure(i, weight = 1, minsize=y+50)
frameBox = tk.Frame(
master=yetAnotherFrame,
relief=tk.RAISED,
borderwidth=1,
width = x,
height = y
)
frameBox.grid(row=j, column=i, padx=5, pady=5)
This actually runs to my surprise, but the scrollbar still isn't working and the layout is broken again.

Solution
In your code frameBox's parent is frameMain. Instead you need to have the canvas as parent or the secondFrame which have the canvas as its parent.
Example
This is basically your code with fixes, but some of the unnecessary parts are removed.
import tkinter as tk
from tkinter import ttk
window = tk.Tk()
window.geometry("400x400")
frameLeft = tk.Frame(master=window, width=400, height=400, relief=tk.RIDGE)
frameLeft.pack(fill=tk.Y, side=tk.LEFT)
myCanvas = tk.Canvas(master=frameLeft)
myCanvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
myScrollbar = ttk.Scrollbar(master=frameLeft, orient=tk.VERTICAL, command=myCanvas.yview)
myScrollbar.pack(side=tk.RIGHT, fill=tk.Y)
myCanvas.configure(yscrollcommand=myScrollbar.set)
myCanvas.bind('<Configure>', lambda e: myCanvas.configure(scrollregion=myCanvas.bbox("all")))
secondFrame = tk.Frame(master=myCanvas)
myCanvas.create_window((0, 0), window=secondFrame, anchor=tk.NW)
for i in range(100):
lbl = tk.Label(secondFrame, text=f"Label {i}")
lbl.grid(column=0, row=i, sticky=tk.W)
window.mainloop()

Related

Scaling grid contents in TkInter with complex structure

I have a tkInter window with a grid in it where each sell is containing several widgets. I want to make the window scalable and make grid cells scale accordingly:
import tkinter as tk
import math
def generate_pod(index, row, col, pod_list):
index = index
# Creating a pod and adding to the grid
pod_frame = tk.Frame(grid_window, borderwidth=0.5, relief="solid")
pod_frame.grid(row=row + 1, column=col)
# Create photoimage
img = tk.PhotoImage(width=160, height=90)
# Create a label diff image
img_label = tk.Label(pod_frame, image=img)
img_label.image = img # Keep a reference to prevent garbage collection
table_button = tk.Label(pod_frame, text=str(index))
score_frame = tk.Frame(pod_frame)
# Create a labels
ssim_label = tk.Label(score_frame, text='Sim: ')
# Movement
move_label = tk.Label(score_frame, text='Movement')
# Brightness
bright_label = tk.Label(score_frame, text='Brigh: ')
# Focus
sharp_label = tk.Label(score_frame, text='Foc: ')
alert_pod = tk.Label(alert_window, text=str(index))
# Add the labels to the pod
img_label.pack(side='top')
score_frame.pack(side='bottom')
table_button.pack(side='bottom')
ssim_label.grid(row=1, column=1)
move_label.grid(row=1, column=2)
bright_label.grid(row=2, column=1)
sharp_label.grid(row=2, column=2)
alert_pod.pack(side='top')
def refresh_pods(grid_rows, grid_cols):
# Destroy any existing widgets in the grid_window
for widget in grid_window.winfo_children():
widget.destroy()
pod_list = []
# Create a grid
for row in range(grid_rows):
for col in range(grid_cols):
# Calculate the index of the current pod
index = row * grid_cols + col
grid_window.rowconfigure(row, weight=1)
grid_window.columnconfigure(col, weight=1)
try:
generate_pod(index, row, col, pod_list)
except Exception as e:
print(e)
# Composing header
header_text = 'Header'
header = tk.Label(grid_window, text=header_text)
header.grid(row=0, column=0, columnspan=grid_cols)
# Schedule the refresh_pods() function to be called again in 2 min
main_window.after(120000, lambda: refresh_pods(grid_rows, grid_cols))
# Getting count of tables and count grid size
table_count = 50
grid_cols = math.floor(math.sqrt(table_count) * 1.2)
grid_rows = math.ceil(table_count/grid_cols)
grid_cols = int(grid_cols)
grid_rows = int(grid_rows)
# Create the main window
main_window = tk.Tk()
main_window.title('CDN snapshots aplha ver')
main_window.eval('tk::PlaceWindow . center')
main_window.resizable(True, True)
# Create a frame for the pods
grid_window = tk.Frame(main_window)
grid_window.pack(side='left', anchor='nw')
alert_window = tk.Frame(main_window)
alert_window.pack(side='right')
# Show the first page of pods
refresh_pods(grid_rows, grid_cols)
# Run the main loop
main_window.mainloop()
I found this snippet in the web that is working as I intend, but I failed to apply it to my code, mostly because all pod generation happens in functions and I didn't manage to pass arguments to labels and vice versa label list out of function
import tkinter as tk
import tkinter.font as tkFont
root = tk.Tk()
label_list = []
font = tkFont.Font(family="Helvetica")
pixel = tk.PhotoImage(width=100, height=1)
for x in range(3):
for y in range(3):
root.rowconfigure(y, weight=1)
root.columnconfigure(x, weight=1)
label_list.append(tk.Label(root, width=40, height=40, image=pixel, relief="groove", compound="center"))
label_list[-1].grid(row=x, column=y, sticky="nsew")
def font_resize(event=None):
for lbl in label_list:
x = lbl.winfo_width()
y = lbl.winfo_height()
root.bind( "<Configure>", font_resize)
root.mainloop()

create a scrollbar on entry with cavnas, python tkinter

I'm trying to put a scrollbar on entry that created on canvas..
I've tried this code
from tkinter import *
root = Tk()
root.geometry('400x600')
root.resizable(0,0)
page = Canvas(root, width=400, height=600, bd=0, highlightthickness=0,scrollregion=(0,0,500,500))
MyImage1 = PhotoImage(file='Study With4.png')
CanvasImage = page.create_image(0,0,image= MyImage1, anchor='nw')
entry =Text(page,height=29,width =46,wrap=WORD,bg='#F8F8F8')
scroll = Scrollbar(entry, orient=VERTICAL)
scroll.pack(side=RIGHT, fill=Y)
scroll.config(command=page.yview)
page.config(yscrollcommand=scroll.set)
page.create_window(200,285, window=entry)
page.pack()
mainloop()
but it doesn't work and I don't know where is the problem.
I've made minimal changes to your code, the biggest is creating a Frame called "label"
to contain Text and Scrollbar then inserting that into Canvas window.
Your scroll object was not defined properly with confusion around page and entry objects.
from tkinter import *
root = Tk()
root.geometry('500x600')
root.resizable(0,0)
page = Canvas(
root, width = 500, height = 600, bd = 0,
highlightthickness = 0,scrollregion = (0,0,1000,1000))
page.pack(side = LEFT, fill = BOTH, expand = True)
MyImage1 = PhotoImage(file='Study With4.png')
CanvasImage = page.create_image(0, 0, image = MyImage1, anchor = NW)
label = Frame(root)
label.pack(fill = BOTH, expand = True)
entry = Text(label, height = 29, width = 46, wrap = NONE, bg = '#F8F8F8')
entry.pack(side = LEFT, fill = BOTH, expand = True)
scroll = Scrollbar(label, orient = VERTICAL)
scroll.pack(side=RIGHT, fill=Y)
scroll.config(command = entry.yview)
entry.config(yscrollcommand = scroll.set)
page.pack()
page.create_window(25, 25, window = label, anchor = NW)
mainloop()

Update text widget in tkinter from function

The problem:
I am trying to update the same text widget box from a function that contains some text. Instead a whole new text window appears every time.
Here is my code:
from tkinter import *
import os
#Tkinter graphics
homepage = Tk()
homepage.title("My first GUI")
# set size of window
homepage.geometry('1200x400')
# Add image file
bg = PhotoImage(file = "maxresdefault.png")
# Show image using label
label1 = Label( homepage, image = bg)
label1.place(x = 0, y = 0)
label2 = Label( homepage, text = "Test App")
label2.pack()
# Create Frame
frame1 = Frame(homepage)
frame1.pack()
#button initatiors
def buttonupdate():
S = Scrollbar(homepage)
T = Text(homepage, height=100, width=30)
T.pack()
T.pack(side=RIGHT, fill= Y)
S.pack(side = RIGHT, fill = Y)
S.config(command=T.yview)
T.insert(END, "test")
T.config(yscrollcommand=S.set, state=DISABLED)
# Static buttons
tickets30button = Button(text = "This is button 1", command=buttonupdate)
tickets30button.place(x=0, y=26)
mcibutton = Button(text = "This is button 2")
mcibutton.place(x=0, y=52)
hdebutton = Button(text = "This is button 3")
hdebutton.place(x=0, y=78)
homepage.mainloop()
Here is the result if I click on the first button three times:
Let me know if you have any suggestions that I can try.
Thank you for your time,
I was able to update my text window instead of create a new one, upon each click of a button, thanks to #TheLizzard.
He mentioned to move the section of code that creates the text window outside of the function and keep the section of code that creates the text, inside the function.
Before:
#button initiators
def buttonupdate():
S = Scrollbar(homepage)
T = Text(homepage, height=100, width=30)
T.pack()
T.pack(side=RIGHT, fill= Y)
S.pack(side = RIGHT, fill = Y)
S.config(command=T.yview)
T.insert(END, "test")
T.config(yscrollcommand=S.set, state=DISABLED)
After: (UPDATED)
S = Scrollbar(homepage)
T = Text(homepage, height=100, width=30)
T.pack(side=RIGHT, fill= Y)
S.pack(side = RIGHT, fill = Y)
S.config(command=T.yview)
T.config(yscrollcommand=S.set, state=DISABLED)
#button initatiors
def myTicketstatusbutton():
T.delete(1.0,END)
T.insert(END, "test")

Python tkinter - scrollbar of canvas cuts off frame content

I am currently writing a simple logbook application for my sailing holidays. Therefore I wanted to create a small gui for entering data in lines for the logbook.
I am using grid layout with frames and canvas for the main frame, to show a scroll bar, when there are to many lines.
Everything is looking fine, until the command
self.canvasCenter.configure(scrollregion=self.canvasCenter.bbox("all"))
is called. If I comment this line out, the canvas is using the full frame. If this line is active - the canvas is using only 50% of the available screen and date is cut off.
I tried to strip down the code to the necessary things (not using any additional files, configs, classes, etc.). So that it is executable stand alone:
#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
import sys
if sys.version_info.major == 2:
# We are using Python 2.x
import Tkinter as tk
elif sys.version_info.major == 3:
# We are using Python 3.x
import tkinter as tk
#from lib import logBookData
#from config import *
#Titel of the application shown in title bar
appTitle = "My APP"
#Layout geometry (size of the window)
lWidth = 768
lHeight = 900
layoutGeometry = '900x768'
#Colours for foreground and background for labels and entry fields
#headerBG = "Light Blue"
headerBG = "white"
labelBG = "white"
labelFG = "black"
#Number of columns (keep in Sync with headers and width list below!)
cNumber = 14
#Column width for number of columns defined above
cWidth = [5,7,5,5,5,5,5,5,5,5,5,5,5,10]
cHeaders = ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10", "C11", "C12", "C13", "C14" ]
class logBookGUI(tk.Tk):
def __init__(self,parent):
tk.Tk.__init__(self,parent)
self.parent = parent
self.lineCount = 0
self.initialize()
def initialize(self):
#create a masterframe
self.masterFrame = tk.Frame(self, bg=headerBG, bd=4, relief="groove") #, width = lWidth, height = lHeight)
self.masterFrame.grid(sticky=tk.NSEW)
self.masterFrame.columnconfigure(cNumber, weight=1)
#create Headerframe for Standard Info
self.headerFrame = tk.Frame(self.masterFrame, bg=headerBG, bd=2, relief="groove")
self.headerFrame.grid(row=0, column=0, sticky=tk.NSEW)
self.headerFrame.columnconfigure(cNumber, weight=1)
#Label Bootsname
boatNameLabel = tk.Label(self.headerFrame, text="Bootsname", anchor="w", fg=labelFG,bg=labelBG)
boatNameLabel.grid(column=0, row=0, sticky=tk.NSEW)
#Field Bootsname
self.boatName = tk.StringVar()
self.boatNameEntry = tk.Entry(self.headerFrame,textvariable=self.boatName, width = cWidth[0])
self.boatNameEntry.grid(column=1,row=0, sticky=tk.NSEW)
self.boatName.set(u"Solva")
for i in xrange(cNumber-2):
button = tk.Button(self.headerFrame,text=u"T %s" %i, command=self.OnButtonClick)
button.grid(column=i,row=0, sticky=tk.NSEW)
button = tk.Button(self.headerFrame,text=u"TEST", command=self.OnButtonClick)
button.grid(column=cNumber-2,row=0, sticky=tk.NSEW)
button = tk.Button(self.headerFrame,text=u"Neue Zeile", command=self.newLineClick)
button.grid(column=cNumber-1,row=1, sticky=tk.NSEW)
#create center frame
self.centerFrame = tk.Frame(self.masterFrame, bg=headerBG, bd=2, relief="groove")
self.centerFrame.grid(row=1, column=0, sticky=tk.NSEW)
self.centerFrame.columnconfigure(cNumber, weight=1)
#create a canvas for center frame
self.canvasCenter = tk.Canvas(self.centerFrame)
self.canvasCenter.grid(row=0, column=0, sticky=tk.NSEW)
self.canvasCenter.columnconfigure(cNumber, weight=1)
# Create a vertical scrollbar linked to the canvas.
vsbar = tk.Scrollbar(self.centerFrame, orient=tk.VERTICAL, command=self.canvasCenter.yview)
vsbar.grid(row=0, column=cNumber, sticky=tk.NSEW)
self.canvasCenter.configure(yscrollcommand=vsbar.set)
# Create a frame on the canvas to contain the content.
self.contentFrame = tk.Frame(self.canvasCenter, bg="Red", bd=2, relief="groove")
self.contentFrame.grid(row=0, column=0, sticky=tk.NSEW)
self.contentFrame.columnconfigure(cNumber, weight=1)
column = 0
for header in cHeaders:
label = tk.Label(self.contentFrame, text=header, anchor="w", fg=labelFG,bg=labelBG,borderwidth=2,relief="groove", width = cWidth[column])
label.grid(column = column, row=1, sticky="EW")
column += 1
self.addLine()
# Create canvas window to hold the centerframe.
self.canvasCenter.create_window((0,0), window=self.contentFrame, anchor=tk.NW)
# Update buttons frames idle tasks to let tkinter calculate buttons sizes
self.contentFrame.update_idletasks()
self.canvasCenter.configure(scrollregion=self.canvasCenter.bbox("all"))
self.grid_columnconfigure(cNumber,weight=1)
self.resizable(True,False)
self.update()
self.geometry("%sx%s" % (lHeight, lWidth))
def newLineClick(self):
self.addLine()
def OnButtonClick(self):
pass
def OnPressEnter(self,event):
pass
def addLine(self):
tkLine = []
try:
for i in xrange(cNumber):
self.entryVar = tk.StringVar()
self.entry = tk.Entry(self.contentFrame,textvariable=self.entryVar,width=cWidth[i])
self.entry.grid(column=i, row = self.lineCount + 3, sticky='EW')
tkLine.append(self.entryVar)
self.canvasCenter.configure(scrollregion=self.canvasCenter.bbox("all"))
except IndexError:
print ("Error in config file. Number of columns and given columns do not match - check file config.py")
sys.exit()
self.update()
self.lineCount += 1
if __name__ == "__main__":
app = logBookGUI(None)
app.title(appTitle)
app.mainloop()
Could someone give me an advice how to use scrollregion, so that the whole size of the frame is used?
Many thanks,
Gernot

How to clear part of a tkinter Canvas and show something when submit is pressed?

I'm creating a simple madlib style game and I've come into a bit of a problem. I cannot get the canvas to clear and show the results.
The following code places an image as the background of a canvas. It then places labels and entry fields in 2 columns for all of the words to be inserted. There is a submit button at the bottom of the page. I can't figure out how to get it clear everything except the background image, so that it can display the story, with the users words inserted. If i place it in the callback(), it clears just the background and keeps everything else. I want the opposite.
from tkinter import *
from PIL import Image, ImageTk
canvas_width = 360
canvas_height = 525
file = r"C:\Users\kraak\Desktop\PyCharm Community Edition 2017.1.2\borderedpaper.GIF"
master = Tk()
canvas = Canvas(master, width=canvas_width, height=canvas_height)
old_img = PhotoImage(file=file)
new_img = old_img.subsample(3, 3)
canvas.create_image(-11, -10, anchor=NW, image=new_img)
canvas.create_window(0, 0, height=1, width=1, anchor=NW)
canvas.create_text(0, 0, text="Test")
e1 = Entry(canvas)
canvas.create_window(250, 60, window=e1, height=15, width=100)
label = Label(text="Enter an adjective.")
label.place(x=40, y=50)
e1.focus_set()
e2 = Entry(canvas)
canvas.create_window(250, 85, window=e2, height=15, width=100)
label = Label(text="Enter a nationality.")
label.place(x=40, y=75)
e2.focus_set()
def callback():
print("Pizza was invented by a " + (e1.get()) + " " + (e2.get()))
def answer():
button = Button(text="Submit.", command=callback)
button.place(x=150, y=460)
answer()
canvas.pack()
mainloop()
As Bryan Oakley suggested you can store the id's of the widgets you want to get rid of in a list to make it easier to destroy() them all in the callback() function. Here's showing the modification to your code that would do that—note the lines with a # ADDED comments.
from tkinter import *
from PIL import Image, ImageTk
canvas_width = 360
canvas_height = 525
file = r"C:\Users\kraak\Desktop\PyCharm Community Edition 2017.1.2\borderedpaper.GIF"
master = Tk()
canvas = Canvas(master, width=canvas_width, height=canvas_height)
canvas_entry_widgets = [] # ADDED
old_img = PhotoImage(file=file)
new_img = old_img.subsample(3, 3)
canvas.create_image(-11, -10, anchor=NW, image=new_img)
canvas.create_window(0, 0, height=1, width=1, anchor=NW)
canvas.create_text(0, 0, text="Test")
e1 = Entry(canvas)
canvas.create_window(250, 60, window=e1, height=15, width=100)
label = Label(text="Enter an adjective.")
label.place(x=40, y=50)
e1.focus_set()
canvas_entry_widgets.append(e1) # ADDED
e2 = Entry(canvas)
canvas.create_window(250, 85, window=e2, height=15, width=100)
label = Label(text="Enter a nationality.")
label.place(x=40, y=75)
e2.focus_set()
canvas_entry_widgets.append(e2) # ADDED
def callback():
print("Pizza was invented by a " + (e1.get()) + " " + (e2.get()))
# destroy the canvas entry widgets and clear the list # ADDED
while canvas_entry_widgets: # ADDED
widget = canvas_entry_widgets.pop() # ADDED
widget.destroy() # ADDED
def answer():
button = Button(text="Submit.", command=callback)
button.place(x=150, y=460)
answer()
canvas.pack()
mainloop()
Every widget has a destroy method which can be used to delete the widget. In your callback you can simply call this method for every widget:
def callback():
e1.destroy()
e2.destroy()
...
In your specific case, if you want to delete all the labels you will have to give them unique names. Or, to make this even easier, you can store all of your widgets and iterate over the list.

Categories