如何在 TKinter 中创建可滚动的单选按钮列表

How to create a scrollable radiobutton list in TKinter

我想在 tkinter 中创建一个可滚动的单选按钮列表,但我没有让它工作。我已经尝试将我的单选按钮列表嵌入各种 tkinter 容器中,但我得到了相同的结果,即滚动条不起作用并且单选按钮列表没有从容器中清除(例如,所以我可以用不同的替换它单选按钮列表)。我已经搜索了如何执行此操作,但找不到任何内容。这可能吗?

这是我的示例应用程序:

import tkinter as tk
from tkinter import ttk


class CodeSampleForWhosebug:
    def __init__(self, window):

        self.main_window = window
        self.mainframe = ttk.Frame(self.main_window, padding = '15 3 12 12')
        self.mainframe.grid(column = 0, row = 0, sticky = "W, E, N, S")

        self.file_choice = tk.StringVar()
        self.contents_list = list()

        self.display_folder_btn = ttk.Button(self.mainframe, text = "Display list of choices", width = 20)
        self.display_folder_btn.grid(row = 1, column = 0, columnspan = 2)
        self.display_folder_btn.bind("<Button-1>", self.list_folder_contents)

        self.folder_contents_canvas = tk.Canvas(self.mainframe)
        self.scroll_y = tk.Scrollbar(self.folder_contents_canvas, orient="vertical")
        self.scroll_y.pack(fill = 'y', side = 'right')
        self.folder_contents_canvas.grid(row=2, column = 0, columnspan = 2)
        self.folder_contents_frame = tk.Text(self.folder_contents_canvas, height = 7, width = 50, yscrollcommand = self.scroll_y.set)
        self.folder_contents_frame.pack(side = "top", fill = "x", expand = False, padx = 20, pady = 20)


    def list_folder_contents(self, event):
        try:
            self.contents_list = ['A dictum nulla auctor id.', 'A porttitor diam iaculis quis.', 'Consectetur adipiscing elit.', \
                                  'Curabitur in ante iaculis', 'Finibus tincidunt nunc.', 'Fusce elit ligula', \
                                  'Id sollicitudin arcu semper sit amet.', 'Integer at sapien leo.', 'Lorem ipsum dolor sit amet', \
                                  'Luctus ligula suscipit', 'Nam vitae erat a dolor convallis', \
                                  'Praesent feugiat quam ac', 'Pretium diam.', 'Quisque accumsan vehicula dolor', \
                                  'Quisque eget arcu odio.', 'Sed ac elit id dui blandit dictum', 'Sed et eleifend leo.', \
                                  'Sed vestibulum fermentum augue', 'Suspendisse pharetra cursus lectus', 'Ultricies eget erat et', \
                                  'Vivamus id lorem mi.']
            contents_dict = dict()
            self.folder_contents_frame.delete(1.0, 'end')
            counter = 0
            for i in self.contents_list:
                contents_dict[str(counter+1)] = i
                counter+=1
            for (text, value) in contents_dict.items():
                #self.folder_contents_frame.insert(1.0, text+"\t"+value+"\n")
                ttk.Radiobutton(self.folder_contents_frame, text = value, variable = self.file_choice, value = text, style = "TRadiobutton").grid(column = 0, columnspan = 2, sticky = tk.W)
            self.scroll_y.config(command = self.folder_contents_frame.yview)

        except Exception as exc:
            print(exc)


#-----------------------------------------

def main():
    root = tk.Tk()
    root.title('Scrollable radiobutton list')
    root.geometry("500x600")
    tabs = ttk.Notebook(root)
    tabs.pack(fill = "both")
    scrollable_radiobutton_list_frame = ttk.Frame(tabs)
    tabs.add(scrollable_radiobutton_list_frame, text = "Scrollable radiobutton list")
             
    my_checker = CodeSampleForWhosebug(window = scrollable_radiobutton_list_frame)

    root.mainloop()

if __name__ == '__main__':
    main()

如果您在 list_folder_contents 方法中注释掉创建单选按钮的行并取消注释其上方的行,您可以看到我希望它如何运行,但使用单选按钮而不是纯文本。

编辑:附带问题:如果有人知道为什么(在文本版本中)以相反的顺序添加项目,我也很感激那里的指导。

感谢您提供的任何帮助!

解决这个问题的一种方法是创建一个可滚动的框架 class 并使用它。我有一个可滚动的框架 class 取自 SO(我不记得确切的答案)并根据我的需要进行了修改,这应该适用于您的应用程序。

import tkinter as tk
from tkinter import ttk

class ScrollFrame(tk.Frame):

    def __init__(self, parent):
        super().__init__(parent) # create a frame (self)

        self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff")      # Canvas to scroll
        self.viewPort = tk.Frame(self.canvas, background="#ffffff")     # This frame will hold the child widgets
        self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) 
        self.canvas.configure(yscrollcommand=self.vsb.set)      # Attach scrollbar action to scroll of canvas

        self.vsb.pack(side="right", fill="y")       # Pack scrollbar to right - change as needed
        self.canvas.pack(side="left", fill="both", expand=True)     # Pack canvas to left and expand to fill - change as needed
        self.canvas_window = self.canvas.create_window(
            (0,0), 
            window=self.viewPort, 
            anchor="nw",            
            tags="self.viewPort",
            )       # Add view port frame to canvas

        self.viewPort.bind("<Configure>", self.onFrameConfigure)
        self.canvas.bind("<Configure>", self.onCanvasConfigure)
        self.first = True
        self.onFrameConfigure(None) # Initial stretch on render

    def onFrameConfigure(self, event):
        '''Reset the scroll region to encompass the inner frame'''
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

    def onCanvasConfigure(self, event):
        '''Reset the canvas window to encompass inner frame when required'''
        canvas_width = event.width
        self.canvas.itemconfig(self.canvas_window, width = canvas_width)

    def on_mousewheel(self, event):
        '''Allows the mousewheel to control the scrollbar'''
        self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")

    def bnd_mousewheel(self):
        '''Binds the mousewheel to the scrollbar'''
        self.canvas.bind_all("<MouseWheel>", self.on_mousewheel)

    def unbnd_mousewheel(self):
        '''Unbinds the mousewheel from the scrollbar'''
        self.canvas.unbind_all("<MouseWheel>")

    def delete_all(self):
        '''Removes all widgets from the viewPort, only works if grid was used'''
        children = self.viewPort.winfo_children()
        for child in children:
            child.grid_remove()

class CodeSampleForWhosebug:
    def __init__(self, window):

        self.main_window = window
        self.mainframe = ttk.Frame(self.main_window, padding = '15 3 12 12')
        self.mainframe.grid(column = 0, row = 0, sticky = "W, E, N, S")

        self.file_choice = tk.StringVar()
        self.contents_list = list()

        self.display_folder_btn = ttk.Button(self.mainframe, text = "Display list of choices", width = 20, command=self.list_folder_contents)
        self.display_folder_btn.pack(side="top")

        self.folder_contents_frame = ScrollFrame(self.mainframe)
        self.folder_contents_frame.pack(side = "top", fill = "x", expand = False, padx = 20, pady = 20)


    def list_folder_contents(self):
        try:
            self.contents_list = ['A dictum nulla auctor id.', 'A porttitor diam iaculis quis.', 'Consectetur adipiscing elit.', \
                                  'Curabitur in ante iaculis', 'Finibus tincidunt nunc.', 'Fusce elit ligula', \
                                  'Id sollicitudin arcu semper sit amet.', 'Integer at sapien leo.', 'Lorem ipsum dolor sit amet', \
                                  'Luctus ligula suscipit', 'Nam vitae erat a dolor convallis', \
                                  'Praesent feugiat quam ac', 'Pretium diam.', 'Quisque accumsan vehicula dolor', \
                                  'Quisque eget arcu odio.', 'Sed ac elit id dui blandit dictum', 'Sed et eleifend leo.', \
                                  'Sed vestibulum fermentum augue', 'Suspendisse pharetra cursus lectus', 'Ultricies eget erat et', \
                                  'Vivamus id lorem mi.']
            contents_dict = dict()
            self.folder_contents_frame.delete_all()
            counter = 0
            for i in self.contents_list:
                contents_dict[str(counter+1)] = i
                counter+=1
            for (text, value) in contents_dict.items():
                ttk.Radiobutton(self.folder_contents_frame.viewPort, text = value, variable = self.file_choice, value = text, style = "TRadiobutton").grid(column = 0, columnspan = 2, sticky = tk.W)

        except Exception as exc:
            print(exc)

#-----------------------------------------

def main():
    root = tk.Tk()
    root.title('Scrollable radiobutton list')
    root.geometry("500x600")
    tabs = ttk.Notebook(root)
    tabs.pack(fill = "both")
    scrollable_radiobutton_list_frame = ttk.Frame(tabs)
    tabs.add(scrollable_radiobutton_list_frame, text = "Scrollable radiobutton list")
             
    my_checker = CodeSampleForWhosebug(window = scrollable_radiobutton_list_frame)

    root.mainloop()

if __name__ == '__main__':
    main()