0

I have researched and tested a number of solutions on stackoverflow and other sites but I cannot get a scrollbar to work with my application.

I'm looking for a scrollbar capable of scrolling the SubFrame group that my application creates. I also need to delimit a minimum window height, and that this window can be extended, but without breaking the scrollbar.

Thank you in advance to all who will help me.

I admit not having understood which should go above the other, the Canvas or the Frame.

Here is a piece of code, with all the graphical widgets to visualize the scrollbar effect :

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()
RamboSushi
  • 69
  • 5
  • Try using [this](https://stackoverflow.com/a/66215091/11106801). It's a `ScrollableFrame` that I created. You can use it just like a normal `tkinter` `Frame`. It's not perfect but I am looking for ways to improve it. – TheLizzard Aug 09 '21 at 13:20
  • @TheLizzard Hello, again :) I will try, thanks – RamboSushi Aug 09 '21 at 13:21
  • @TheLizzard That work, but this solution is not the best. I wanted to make it work with canvas to keep it simple :) – RamboSushi Aug 09 '21 at 13:27
  • 1
    Please reduce this code down to a [mcve]. I doubt we need all of those functions and widgets if the question is about scrolling a frame. – Bryan Oakley Aug 09 '21 at 13:49
  • 1
    By the way, the code in the answer I gave to your other question had a critical flat that I just fixed. The parent of `self.topFrame` and `self.groupOfFrames` needs to ber `self`, not `root`. – Bryan Oakley Aug 09 '21 at 15:31

1 Answers1

1

It is the canvas that has the ability to scroll, so your self.groupOfFrames needs to go inside the canvas. And since you want the scrollbar and canvas to appear as a single complex object, they should go in a frame.

You need to make sure that when the window is resized, the frame inside the canvas is resized as well. And you also need to make sure that when you add something to self.groupOfFrames you also update the scrollregion. These can be done by binding to the <Configure> event of each widget.

Thus, I would create the FrameStack like the following. Please note that self.topFrame and self.bottomFrame are children of self rather than root. That is a mistake I didn't catch in the code I gave you in your previous question.

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)
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Thanks a lot. When I scroll the frame (or the canvas), the display is not smooth, it is buggy, with bands. Do you know how to solve this? – RamboSushi Aug 10 '21 at 09:00