如何在 tkinter 中实现滚动条到网格

How to implement a scrollbar to grid in tkinter

我很难在我的 Tkinter 项目中实现滚动条。我浏览了很多文章并回答了有关如何实现滚动条的问题,但在研究了这一 'simple' 问题一整天后,我还是无法实现一个可行的解决方案。

我当前的代码如下所示:

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

我试图强调唯一值得关注的事情,那就是滚动条,目前其他一切都在工作,我只是想 post 整个代码以便更好地理解它是否有助于无论如何。

我的问题是每当我实现滚动条时,它都会返回一个错误说明:

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

这个错误似乎是不言自明的,只是将 canvas 网格化而不是打包它,但是在经过大量的小调整和做事之后,事情变得迂回了

我的第二个想法是,将网格化框架包裹在另一个更大的打包框架中是否存在网格问题,如下所示:

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)

这确实让我感到惊讶,但滚动条仍然无法正常工作,布局又被破坏了。

解决方案

在您的代码中 frameBox 的父级是 frameMain。相反,您需要将 canvas 作为父级或将 canvas 作为其父级的 secondFrame

例子

这基本上是您的代码并进行了修复,但删除了一些不必要的部分。

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