首页 > 解决方案 > Python Tkinter 滚动条和框架未显示所有复选框

问题描述

一些 tkinter 代码有问题,我相信我离它太近了,看不到我面前的问题。我正在将复选框加载到框架中并将滚动条附加到该位置。

在我达到 1000 多个复选框之前,这很有效。然后它似乎被切断了,即使框架扩展了适合所有复选框的高度,它也没有在 gui 中显示它们。您可以在此处的图片中看到他们停止显示复选框故障的位置

这是我的代码:(请原谅它看起来有多乱,它是更大代码集的子集,我刚刚隔离了错误)

from tkinter import *


build_vars = {}
build_Radios = []
parent = Tk()

center_container = Frame(parent, width=5, height=5)
center_container.grid(row=1, sticky="nsew")

# Center Row Columns
center_center_container = Frame(center_container, width=150, height=200)
center_center_container.grid(row=0, column=2, sticky="ns")

build_canvas = Canvas(center_center_container, background='green')
build_canvas.grid(row=0, column=0, sticky=N+E+W+S)

# Create a vertical scrollbar linked to the canvas.
vsbar = Scrollbar(center_center_container, orient=VERTICAL, command=build_canvas.yview)
vsbar.grid(row=0, column=1, sticky=NS)
build_canvas.configure(yscrollcommand=vsbar.set)

# Create a frame on the canvas to contain the buttons.
frame_buttons = Frame(build_canvas, bd=2, background='red')

def create_build_radios():

    # for index, item in enumerate(filtered_builds):
    for index, item in enumerate(list(range(3000))):
        build_vars[item] = IntVar()
        radio = Checkbutton(frame_buttons, text=item, variable=build_vars[item], onvalue=1,
                        offvalue=0,
                        command=lambda item=item: sel(item))
        radio.grid(row=index, column=0, sticky=W)
        build_Radios.append(radio)

    # Create canvas window to hold the buttons_frame.
    build_canvas.create_window((0, 0), window=frame_buttons, anchor=NW)
    build_canvas.update_idletasks()  # Needed to make bbox info available.
    bbox = build_canvas.bbox(ALL)  # Get bounding box of canvas with Buttons.
    build_canvas.configure(scrollregion=bbox, width=150, height=400)

def sel(item):
    print(item)

create_build_radios()
parent.mainloop()

标签: pythontkintercheckboxscrollbarframe

解决方案


所以这是更好的解决方案(比另一个更好,可以很容易地在上面放置更多的小部件,但请注意可能存在某种限制(至少是 CPU 的能力):

from tkinter import Tk, Canvas, Frame, Label, Scrollbar, Button, DoubleVar, StringVar, Entry
from tkinter.ttk import Progressbar


class PagedScrollFrame(Frame):
    def __init__(self, master, items_per_page=100, **kwargs):
        Frame.__init__(self, master, **kwargs)
        self.master = master
        self.items_per_page = items_per_page
        self.pages = None
        self.id_list = []
        self.bbox_tag = 'all'

        self._loading_frame = Frame(self)
        self.__load_progress_tracker = DoubleVar(master=self.master, value=0.0)
        self.__percent_tracker = StringVar(master=self.master, value='0.00%')

        self.frame = Frame(self)
        self.frame.pack(side='top', padx=20, pady=20)

        self.canvas = Canvas(self.frame)
        self.canvas.pack(side='left')

        self.bg_label = Label(self.canvas)
        self.bg_label.place(x=0, y=0, relwidth=1, relheight=1)

        self.scrollbar = Scrollbar(self.frame, orient='vertical', command=self.canvas.yview)
        self.scrollbar.pack(side='right', fill='y')
        self.canvas.config(yscrollcommand=self.scrollbar.set)
        self.canvas.bind('<Configure>',
                         lambda e: self.canvas.config(
                             scrollregion=self.canvas.bbox(self.bbox_tag)
                         ))

        self.button_frame = Frame(self)
        self.button_frame.pack(fill='x', side='bottom', padx=20, pady=20)

        self.canvas_frame = Frame(self.button_frame)
        self.button_canvas = Canvas(self.canvas_frame, height=20)
        self.button_canvas.pack(expand=True)
        self.inner_frame = Frame(self.button_canvas)
        self.button_canvas.create_window(0, 0, window=self.inner_frame, anchor='nw')

        self.button_scrollbar = Scrollbar(self.canvas_frame,
                                          orient='horizontal',
                                          command=self.button_canvas.xview)
        self.button_scrollbar.pack(fill='x')
        self.button_canvas.config(xscrollcommand=self.button_scrollbar.set)
        self.button_canvas.bind(
            '<Configure>', lambda e: self.button_canvas.config(
                scrollregion=self.button_canvas.bbox('all')
            )
        )

    def pack_items(self):
        if not self.pages:
            return
        self._loading_frame.place(x=0, y=0, relwidth=1, relheight=1)
        self._loading_frame.lift()
        self._loading_frame.update_idletasks()
        self.after(100, self._pack_items)

    def _pack_items(self):
        Label(self._loading_frame, text='Loading...').pack(expand=True)
        Progressbar(self._loading_frame,
                    orient='horizontal',
                    variable=self.__load_progress_tracker,
                    length=self._loading_frame.winfo_width()
                           - self._loading_frame.winfo_width() // 10).pack(expand=True)
        Label(self._loading_frame, textvariable=self.__percent_tracker).pack(expand=True)
        self.update_idletasks()
        widgets = [widget for page in self.pages for widget in page.winfo_children()]
        length = len(widgets)
        self.after(100, self.__pack_items, widgets, 0, length)

    def __pack_items(self, widgets, index, length):
        if index >= length:
            self._loading_frame.destroy()
            self.canvas.config(scrollregion=self.canvas.bbox('all'))
            return
        widgets[index].pack()
        percent = (index + 1) * 100 / length
        self.__load_progress_tracker.set(value=percent)
        self.__percent_tracker.set(value=f'{percent: .2f}%')
        self.after(1, self.__pack_items, widgets, index + 1, length)

    def change_frame(self, index):
        if not self.pages:
            return
        self.bbox_tag = self.id_list[index]
        self.canvas.config(scrollregion=self.canvas.bbox(self.bbox_tag))
        self.bg_label.lift()
        self.pages[index].lift()

    def create_pages(self, num_of_items, items_per_page=None):
        self.pages = None
        if not items_per_page:
            items_per_page = self.items_per_page
        num_of_pages = num_of_items // items_per_page
        if num_of_items % items_per_page != 0:
            num_of_pages += 1
        start_indexes = [items_per_page * page_num for page_num in range(num_of_pages)]
        end_indexes = [num + items_per_page for num in start_indexes]
        end_indexes[-1] += (num_of_items % items_per_page
                            - (items_per_page if num_of_items % items_per_page != 0 else 0))
        self.pages = [Frame(self.canvas) for _ in range(num_of_pages)]
        self.id_list = []
        for page, frame in enumerate(self.pages):
            self.id_list.append(self.canvas.create_window(0, 0, window=frame, anchor='nw'))
        self.pages[0].lift()
        if num_of_pages >= 2:
            Button(self.button_frame, text='1',
                   command=lambda: self.change_frame(0)).pack(
                side='left', expand=True, fill='both', ipadx=5
            )
            if num_of_pages > 2:
                self.canvas_frame.pack(fill='x', expand=True, side='left')
                for page_num in range(1, num_of_pages - 1):
                    Button(self.inner_frame, text=page_num + 1,
                           command=lambda index=page_num: self.change_frame(index)).pack(
                        expand=True, fill='both', side='left', ipadx=5
                    )
            Button(self.button_frame, text=num_of_pages,
                   command=lambda: self.change_frame(num_of_pages - 1)).pack(
                side='right', fill='both', expand=True, ipadx=5
            )
        return zip(start_indexes, end_indexes, self.pages)


def create_paged_canvas():
    scroll = PagedScrollFrame(root)
    scroll.pack()

    lst = tuple(range(3000))
    for start, end, parent in scroll.create_pages(len(lst)):
        for i in lst[start:end]:
            frame_ = Frame(parent)
            Label(frame_, text=str(i).zfill(4)).pack(side='left')

    scroll.pack_items()


root = Tk()
root.protocol('WM_DELETE_WINDOW', exit)

create_paged_canvas()

root.mainloop()

主要信息:
基本上这会创建分页的可滚动画布。所需要的只是调整create_paged_canvas()函数中的内部循环和可迭代对象。您还可以调整每页显示多少项目(这也允许以后配置,例如在菜单中您可以调用类似的create_paged_canvas()函数并将items_per_page参数更改为其他内容(将不得不再次加载所有内容但是......tkintertkinter,它非常慢,更糟糕的是它不允许直接使用线程,甚至不能谈论进程(那些东西会加快速度,但根本无法完成)))

重要(建议):
我强烈建议不要在导入某些东西时使用通配符(*),你应该导入你需要的东西,例如from module import Class1, func_1, var_2等等或导入整个模块:import module然后你也可以使用别名:import module as md或者类似的东西,重点除非您真正知道自己在做什么,否则不要导入所有内容;名称冲突是问题所在。

杂项:
为了获得更好的性能,最好不要创建标签,而是直接在画布上创建文本(使用其他解决方案)或使用列表框,以防您需要显示大量数据,因为它会加快速度因为不必创建小部件(这也意味着您只能查看几乎所有数据)

如果您有任何问题,请务必提出这些问题!


推荐阅读