创建子帧的应用程序中的 tkinter 滚动条 (frame/canvas)

tkinter scrollbar (frame/canvas) in application that create SubFrames

我已经在 Whosebug 和其他网站上研究并测试了许多解决方案,但我的应用程序无法使用滚动条。

我正在寻找能够滚动我的应用程序创建的 SubFrame 组的滚动条。我还需要界定一个最小 window 高度,并且这个 window 可以扩展,但不会破坏滚动条。

在此先感谢所有愿意帮助我的人。

我承认我不明白哪个应该高于另一个,Canvas 或框架。

这是一段代码,所有图形小部件都可视化滚动条效果:

import tkinter as tk
from tkinter import ttk

class FrameStack(tk.Frame):
    def __init__(self, parent):
        super().__init__(parent)
        self.subframes = []

        self.topFrame = tk.Frame(root)
        self.topFrame.pack(side="top", fill="x")

        self.groupOfFrames = tk.Frame(root)
        self.groupOfFrames.pack(side="top", fill="both", expand=True, pady=(32,0))

        self.scrollbar = tk.Scrollbar(self.groupOfFrames, orient="vertical")
        self.scrollbar.pack(side="right",fill="y")

        #self.canvas = tk.Canvas(self.groupOfFrames, yscrollcommand=self.scrollbar.set)
        #self.canvas.pack()
        #self.canvas_window = self.canvas.create_window(0, 0, anchor="nw", window=self.groupOfFrames)

        self.create_widget()

    def create_widget(self):
        tk.Label(self.topFrame, text="BUS").grid(row=0, column=0)
        tk.Label(self.topFrame, text="IP").grid(row=1, column=0)
        tk.Label(self.topFrame, text="REG").grid(row=2, column=0)
        tk.Label(self.topFrame, text="SIZE").grid(row=3, column=0)

        self.combobox_bus = ttk.Combobox(self.topFrame, values=list(dic.keys()), width=40, justify='center', state="readonly")
        self.combobox_bus.bind('<<ComboboxSelected>>', self.getUpdateDataIP)
        self.combobox_bus.grid(row=0, column=1, sticky="nsew")

        self.combobox_ip = ttk.Combobox(self.topFrame, justify='center', width=40, state="readonly")
        self.combobox_ip.bind('<<ComboboxSelected>>', self.getUpdateDataReg)
        self.combobox_ip.grid(row=1, column=1, sticky="nsew")

        self.button_read = tk.Button(self.topFrame, text="Read", command=self.read)
        self.button_write = tk.Button(self.topFrame, text="Write", command=self.write)
        self.button_read.grid(row=0, column=2, columnspan=2, sticky="nsew")
        self.button_write.grid(row=1, column=2, columnspan=2, sticky="nsew")

        self.button_hsid = tk.Button(self.topFrame, text="HSID")
        self.button_hsid.grid(row=0, column=4, columnspan=2, sticky="nsew")

        self.button_add = tk.Button(self.topFrame, text="+", command=self.add_frame)
        self.button_add.grid(row=1, column=4, columnspan=2, sticky="nsew")

        self.combobox_reg_dl = ttk.Combobox(self.topFrame, width=40, justify='center', state="readonly")
        self.combobox_reg_dl.bind('<<ComboboxSelected>>', self.getUpdateDisplayReg)
        self.combobox_reg_dl.grid(row=2, column=1, sticky="nsew")

        self.button_dump = tk.Button(self.topFrame, text="Dump", command=self.dump)
        self.button_load = tk.Button(self.topFrame, text="Load", command=self.load)
        self.button_dump.grid(row=2, column=2, columnspan=2, sticky="nsew")
        self.button_load.grid(row=2, column=4, columnspan=2, sticky="nsew")

        self.select_size = tk.StringVar()
        self.select_size.set("0")
        self.entry_size = tk.Entry(self.topFrame, textvariable=self.select_size, justify='center')
        self.entry_size.grid(row=3, column=1, sticky="nsew")
        tk.Scale(self.topFrame, from_=0, to=1024, variable=self.select_size)

        self.select_read_size = tk.IntVar()
        self.select_read_size.set(8)
        self.radio_8 = tk.Radiobutton(self.topFrame, text=" 8", variable=self.select_read_size, value=8)
        self.radio_16 = tk.Radiobutton(self.topFrame, text="16", variable=self.select_read_size, value=16)
        self.radio_32 = tk.Radiobutton(self.topFrame, text="32", variable=self.select_read_size, value=32)
        self.radio_64 = tk.Radiobutton(self.topFrame, text="64", variable=self.select_read_size, value=64)
        self.radio_8.grid(row=3, column=2)
        self.radio_16.grid(row=3, column=3)
        self.radio_32.grid(row=3, column=4)
        self.radio_64.grid(row=3, column=5)

    def getUpdateDataIP(self, event):
        self.combobox_ip['values'] = list(dic[self.combobox_bus.get()].keys())
        self.combobox_ip.set('')
        self.combobox_reg_dl['values'] = ['']
        self.combobox_reg_dl.set('')
        for f in self.subframes:
            f.combobox_reg_rw['values'] = ['']
            f.combobox_reg_rw.set('')

    def getUpdateDataReg(self, event):
        self.combobox_reg_dl['values'] = list(dic[self.combobox_bus.get()][self.combobox_ip.get()].keys())
        self.combobox_reg_dl.set('')
        for f in self.subframes:
            f.combobox_reg_rw['values'] = list(dic[self.combobox_bus.get()][self.combobox_ip.get()].keys())
            f.combobox_reg_rw.set('')

    def getUpdateDisplayReg(self, event):   
        self.combobox_reg_dl.set(self.combobox_reg_dl.get()+" ("+dic[self.combobox_bus.get()][self.combobox_ip.get()][self.combobox_reg_dl.get()]+")")

    def delete_frame(self, frame):
        self.subframes.remove(frame)
        frame.destroy()

    def add_frame(self):
        f = SubFrame(parent=self.groupOfFrames, controller=self)
        if self.combobox_bus.get()!="" and self.combobox_ip.get()!="":
            f.combobox_reg_rw['values'] = list(dic[self.combobox_bus.get()][self.combobox_ip.get()].keys())
        self.subframes.append(f)
        f.pack(side="top", fill="x", pady=(0,5))

    def read(self):
        pass

    def write(self):
        pass

    def dump(self):
        pass

    def load(self):
        pass

class SubFrame(tk.Frame):
    def __init__(self, parent, controller):
        super().__init__(parent)
        self.parent = parent
        self.controller = controller
        self.create_widget()

    def create_widget(self):
        self.combobox_reg_rw = ttk.Combobox(self, width=47, justify='center', state="readonly")
        self.combobox_reg_rw.bind('<<ComboboxSelected>>', self.getUpdateDisplayReg)
        self.select_val = tk.StringVar()
        self.entry_value = tk.Entry(self, bd=1.5, width=20, textvariable=self.select_val, justify='center')
        self.button_write = tk.Button(self, text="Write", command=self.write)
        self.button_read = tk.Button(self, text="Read", command=self.read)
        self.button_help = tk.Button(self, text="?", command=self.help)
        self.button_remove = tk.Button(self, text="-", command=self.remove)

        self.combobox_reg_rw.grid(row=0, column=0, columnspan=2, sticky="ew")
        self.entry_value.grid(row=0, column=2, columnspan=2, sticky="ew")
        self.button_write.grid(row=1, column=0, sticky="ew")
        self.button_read.grid(row=1, column=1, sticky="ew")
        self.button_help.grid(row=1, column=2, sticky="nsew")
        self.button_remove.grid(row=1, column=3, sticky="nsew")

    def remove(self):
        self.controller.delete_frame(self)

    def getUpdateDisplayReg(self, event):   
        self.combobox_reg_rw.set(self.combobox_reg_rw.get()+" ("+dic[self.controller.combobox_bus.get()][self.controller.combobox_ip.get()][self.combobox_reg_rw.get()]+")")

    def write(self):
        print(self.entry_value.get())

    def read(self):
        print(self.combobox_reg_rw.get())

    def help(self):
        pass

if __name__ == "__main__":
    #dic = extractor.main()

    dic = {'1': {'1.1': {'1.1.1': 'A', '1.1.2': 'B'}, '1.2': {'1.2.1': 'C', '1.2.2': 'D'}}, '2': {'2.1': {'2.1.1': 'E', '2.2.2': 'F'}, '2.2': {'2.2.1': 'G', '2.2.2': 'H'}}}

    root = tk.Tk()
    root.title("XXXXXX")
    root.resizable(False, True)

    fs = FrameStack(root)
    fs.pack(fill="both", expand=True)
    root.mainloop()

canvas 具有滚动功能,因此您的 self.groupOfFrames 需要进入 canvas。由于您希望滚动条和 canvas 显示为一个复合体 object,因此它们应该放在一个框架中。

您需要确保在 window 调整大小时,canvas 内的框架也会调整大小。并且您还需要确保当您向 self.groupOfFrames 添加内容时,您也会更新滚动区域。这些可以通过绑定到每个小部件的 <Configure> 事件来完成。

因此,我将创建如下所示的 FrameStack。请注意 self.topFrameself.bottomFrameself 的 children 而不是 root。这是我在上一个问题中给你的代码中没有发现的错误。

class FrameStack(tk.Frame):
    def __init__(self, parent):
        super().__init__(parent)
        self.subframes = []
        self.topFrame = tk.Frame(self)
        self.bottomFrame = tk.Frame(self)

        self.topFrame.pack(side="top", fill="x")
        self.bottomFrame.pack(side="bottom", fill="both", expand=True)

        self.canvas = tk.Canvas(self.bottomFrame, bd=0)
        vsb = tk.Scrollbar(self.bottomFrame, command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=vsb.set)

        vsb.pack(side="right", fill="y")
        self.canvas.pack(side="left", fill="both", expand=True)

        self.groupOfFrames = tk.Frame(self.canvas)
        self.canvas.create_window(0, 0, anchor="nw", window=self.groupOfFrames, tags=("inner",))

        self.canvas.bind("<Configure>", self._resize_inner_frame)
        self.groupOfFrames.bind("<Configure>", self._reset_scrollregion)


    def _reset_scrollregion(self, event):
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

    def _resize_inner_frame(self, event):
        self.canvas.itemconfig("inner", width=event.width)