未显示所有复选框的Python Tkinter滚动条和框架

2022-04-16 00:00:00 python tkinter scrollbar frame checkbox

问题描述

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

在我看到1000多个复选框之前,这个方法运行得很好。然后它似乎被切断了,即使框架延伸了一个适合所有复选框的高度,它也不会在gui中显示它们。您可以在此处的图像中看到它们停止显示Checkbox Malfunction

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

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

解决方案

所以这是一个更好的解决方案(比其他解决方案好得多,可以很容易地在上面放置更多的小部件,但请注意,可能有某种限制(至少是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或类似的东西,关键是,除非您真正知道自己在做什么,否则不要导入所有内容;名称冲突是问题所在。

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

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

相关文章