0

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.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
LUKISO2
  • 62
  • 5
  • 2
    Any widgets you want to be affected by the scrollbar have to be descendants of `secondFrame`, not `frameMain`. – jasonharper Apr 30 '22 at 16:07

1 Answers1

0

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()
Billy
  • 1,157
  • 1
  • 9
  • 18
  • Thank you very much for your help, although I wasn't trying to add the scrollbar to frameLeft but to frameMain, but after your suggestion and jasonhaper comment I was able to make it work, just another quick question, do you know how to stretch the canvas to fit the window? If I add `myCanvas.pack(fill=tk.BOTH, expand=1)` nothing happens and when I try to add `secondFrame.pack(fill=tk.BOTH, expand=1)` right after `myCanvas.create_window` it stretches but I loos the ability to actually use the scrollbar. – LUKISO2 Apr 30 '22 at 21:38
  • @LUKISO2 If you use `secondFrame.pack()`, it will not be a canvas element. In order for the window to be part of canvas you will have to use `canvas.create_window()`. And for dynamic resizing, see [this question](https://stackoverflow.com/questions/22835289/how-to-get-tkinter-canvas-to-dynamically-resize-to-window-width). – Billy May 01 '22 at 04:17