1

I am using Tkinter for a project, and I found in Stackoverflow a code to create a scrollbar, which I understood well. I wanted to scroll some buttons, which worked, but now when I try to scroll some grids (button and scales), it doesn't work.

I have tried to manage my code with answers here and here, but I didn't reach my goal!

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

# -------------------------------- Importation ------------------------------- #

import tkinter as tk
from tkinter import messagebox

# ------------------------------ Initialisation ------------------------------ #

root = tk.Tk()

width_screen, height_screen = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry("%dx%d+0+0" % (width_screen, height_screen))



# ----------------------- Creation of a list of sounds ----------------------- #

wav_files = ["a.wav","b.wav","c.wav","d.wav","e.wav","f.wav","g.wav","h.wav","i.wav","j.wav","k.wav","l.wav","m.wav","n.wav","o.wav","p.wav","q.wav","r.wav","s.wav","t.wav","u.wav","v.wav","w.wav","x.wav","y.wav","z.wav","aa.wav","bb.wav","cc.wav","dd.wav","ee.wav","ff.wav"]

# -------------------------- Vertical scrolled frame ------------------------- #

class VerticalScrolledFrame(tk.Frame):

    def __init__(self, parent, *args, **kw):
        tk.Frame.__init__(self, parent, *args, **kw)            

        # create a canvas object and a vertical scrollbar for scrolling it
        vscrollbar = tk.Scrollbar(self, orient=tk.VERTICAL)
        vscrollbar.grid(row=0, column=1, sticky='ns')
        canvas = tk.Canvas(self, bd=0, highlightthickness=0,
                        yscrollcommand=vscrollbar.set, width=root.winfo_screenwidth(), height=root.winfo_screenheight())
        canvas.grid(row=0, column=0, sticky="news")
        vscrollbar.config(command=canvas.yview)

        # reset the view
        #canvas.xview_moveto(0)
        #canvas.yview_moveto(0)

        # create a frame inside the canvas which will be scrolled with it
        self.interior = interior = tk.Frame(canvas)
        interior_id = canvas.create_window(0, 0, window=interior,
                                           anchor=tk.NW)

        # track changes to the canvas and frame width and sync them,
        # also updating the scrollbar
        def _configure_interior(event):
            # update the scrollbars to match the size of the inner frame
            size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
            canvas.config(scrollregion="0 0 %s %s" % size)
            if interior.winfo_reqwidth() != canvas.winfo_width():
                # update the canvas's width to fit the inner frame
                canvas.config(width=interior.winfo_reqwidth())

        interior.bind('<Configure>', _configure_interior)

        def _configure_canvas(event):
            if interior.winfo_reqwidth() != canvas.winfo_width():
                # update the inner frame's width to fill the canvas
                canvas.itemconfigure(interior_id, width=canvas.winfo_width())
        canvas.bind('<Configure>', _configure_canvas)


# ------------------------------- Sound buttons ------------------------------ #


class Make_sound:
    def __init__(self, name, parent):
        self.varbutton = tk.StringVar()
        self.varbutton.set("OFF")
        self.name = name
        self.parent = parent

        self.soundbuttoncreator()


    def launchsound(self):
        if self.varbutton.get() == "OFF":
            self.varbutton.set("ON")
        else:
            self.varbutton.set("OFF")

    def soundbuttoncreator(self):

        self.volumescale = tk.Scale(self.parent, orient='vertical', from_=0, to=10, resolution=0.1, tickinterval=2, label='Volume (db)', )
        self.volumescale.grid(row=0,column=1, rowspan=2, sticky="nsew")
        self.faderscale = tk.Scale(self.parent, orient='horizontal', from_=0, to=10, resolution=0.1, tickinterval=2, label='Volume (db)')
        self.faderscale.grid(row=1,column=0, sticky="nsew")
        self.button = tk.Checkbutton(self.parent,text=self.name, indicatoron=False, selectcolor="green", background="red", onvalue="ON", offvalue="OFF")
        self.button.grid(row=0, column=0, sticky="nsew")

def sounds_buttons(parent):
    for i in range(len(wav_files)):
        new_name = wav_files[i][:-4]
        globals()["wav_files"][i] = Make_sound(new_name,parent)

# ---------------------------------------------------------------------------- #
#                                   Creation                                   #
# ---------------------------------------------------------------------------- #

# ----------------------------- Buttons of sound ----------------------------- #

scframe = VerticalScrolledFrame(root)
scframe.pack(side=tk.LEFT)

sounds_buttons(scframe.interior)

root.mainloop()

EDIT 1: It seems that the output is just made of the last grid, because the text in the only button shown is ff, which corresponds to the last sound in the list. I didn't have this problem with just buttons (and not grids): I could see every sounds and I had the scrolling bar active...

EDIT 2: The problem is solved, but now we have a new problem : putting a command to the checkbutton isn't possible like that. You can see by yourself that the self.varbutton.set("OFF) isn't respected, the button is always on "ON"... Here is the code with the problem of command :

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

# -------------------------------- Importation ------------------------------- #

import tkinter as tk
from tkinter import messagebox

# ------------------------------ Initialisation ------------------------------ #

root = tk.Tk()

width_screen, height_screen = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry("%dx%d+0+0" % (width_screen, height_screen))



# ----------------------- Creation of a list of sounds ----------------------- #

wav_files = ["a.wav","b.wav","c.wav","d.wav","e.wav","f.wav","g.wav","h.wav","i.wav","j.wav","k.wav","l.wav","m.wav","n.wav","o.wav","p.wav","q.wav","r.wav","s.wav","t.wav","u.wav","v.wav","w.wav","x.wav","y.wav","z.wav","aa.wav","bb.wav","cc.wav","dd.wav","ee.wav","ff.wav"]

# -------------------------- Vertical scrolled frame ------------------------- #

class VerticalScrolledFrame(tk.Frame):

    def __init__(self, parent, *args, **kw):
        tk.Frame.__init__(self, parent, *args, **kw)            

        # create a canvas object and a vertical scrollbar for scrolling it
        vscrollbar = tk.Scrollbar(self, orient=tk.VERTICAL)
        vscrollbar.grid(row=0, column=1, sticky='ns')
        canvas = tk.Canvas(self, bd=0, highlightthickness=0,
                        yscrollcommand=vscrollbar.set, width=root.winfo_screenwidth(), height=root.winfo_screenheight())
        canvas.grid(row=0, column=0, sticky="news")
        vscrollbar.config(command=canvas.yview)

        # reset the view
        #canvas.xview_moveto(0)
        #canvas.yview_moveto(0)

        # create a frame inside the canvas which will be scrolled with it
        self.interior = interior = tk.Frame(canvas)
        interior_id = canvas.create_window(0, 0, window=interior,
                                           anchor=tk.NW)

        # track changes to the canvas and frame width and sync them,
        # also updating the scrollbar
        def _configure_interior(event):
            # update the scrollbars to match the size of the inner frame
            size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
            canvas.config(scrollregion="0 0 %s %s" % size)
            if interior.winfo_reqwidth() != canvas.winfo_width():
                # update the canvas's width to fit the inner frame
                canvas.config(width=interior.winfo_reqwidth())

        interior.bind('<Configure>', _configure_interior)

        def _configure_canvas(event):
            if interior.winfo_reqwidth() != canvas.winfo_width():
                # update the inner frame's width to fill the canvas
                canvas.itemconfigure(interior_id, width=canvas.winfo_width())
        canvas.bind('<Configure>', _configure_canvas)


# ------------------------------- Sound buttons ------------------------------ #


class Make_sound:
    def __init__(self, name, parent):
        self.varbutton = tk.StringVar()
        self.name = name
        self.parent = parent

        self.soundbuttoncreator()


    def launchsound(self):
        print(self.varbutton.get())
        if self.varbutton.get() == 1:
            self.list=[]
        else:
            self.list.append("A")

    def soundbuttoncreator(self):
        self.frame = tk.Frame(self.parent) # create a frame to hold the widgets
        
        # use self.frame as parent instead of self.parent
        self.volumescale = tk.Scale(self.frame, orient='vertical', from_=0, to=10, resolution=0.1, tickinterval=2, label='Volume (db)', )
        self.volumescale.grid(row=0,column=1, rowspan=2, sticky="nsew")
        self.faderscale = tk.Scale(self.frame, orient='horizontal', from_=0, to=10, resolution=0.1, tickinterval=2, label='Volume (db)')
        self.faderscale.grid(row=1,column=0, sticky="nsew")
        self.button = tk.Checkbutton(self.frame, text=self.name, indicatoron=False, selectcolor="green", background="red", variable=self.varbutton, command=self.launchsound) 
        self.button.grid(row=0, column=0, sticky="nsew")

        self.frame.pack()  # use pack() on the frame so new instance of `Make_sound` will not overlap the old instances


def sounds_buttons(parent):
    for i in range(len(wav_files)):
        new_name = wav_files[i][:-4]
        globals()["wav_files"][i] = Make_sound(new_name,parent)

# ---------------------------------------------------------------------------- #
#                                   Creation                                   #
# ---------------------------------------------------------------------------- #

# ----------------------------- Buttons of sound ----------------------------- #

scframe = VerticalScrolledFrame(root)
scframe.pack(side=tk.LEFT)

sounds_buttons(scframe.interior)

root.mainloop()

EDIT 3: I edited my second code (see EDIT 2).

TrissN
  • 35
  • 8
  • Can you modify your example so that it doesn't depend on external files? For example, can you add some hard-coded data rather than reading from two json files? Are the sound files required? Since the question is about scrolling I'm guessing they can be removed from the code, too. – Bryan Oakley Jul 09 '20 at 16:27
  • Usually, if the widgets aren't scrolling that means you haven't placed them in the inner frame. I can't run your code, but have you verified that the widgets you want to scroll are inside the `interior` frame? – Bryan Oakley Jul 09 '20 at 16:31
  • You put all the set of `Scale` and `Checkbutton` to same grid cells: two `Scale`s at (0,1) and (1,0), `Checkbutton` at (0,0). – acw1668 Jul 09 '20 at 16:44
  • I edited my code for you to be able to run it! I think that I made right about the widgets inside the interior frame @BryanOakley, but an error is possible! – TrissN Jul 09 '20 at 19:41
  • @acw1668, I did set a rowspan = 2, so I don't see the problem, could you explain more in details please? Thank you! – TrissN Jul 09 '20 at 19:43
  • It is not related to `rowspan` option. What I mean is that you put the set of `Scale` and `Checkbutton` into same location. That's why you can only see the last set of widgets. – acw1668 Jul 10 '20 at 00:58

1 Answers1

1

It is because you put all the set of Scale and Checkbutton into same location, so the last set will overlap the previous ones:

def soundbuttoncreator(self):

    self.volumescale = tk.Scale(self.parent, orient='vertical', from_=0, to=10, resolution=0.1, tickinterval=2, label='Volume (db)', )
    self.volumescale.grid(row=0,column=1, rowspan=2, sticky="nsew") # same for all instances of Make_sound
    self.faderscale = tk.Scale(self.parent, orient='horizontal', from_=0, to=10, resolution=0.1, tickinterval=2, label='Volume (db)')
    self.faderscale.grid(row=1,column=0, sticky="nsew") # same for all instances of Make_sound
    self.button = tk.Checkbutton(self.parent,text=self.name, indicatoron=False, selectcolor="green", background="red", onvalue="ON", offvalue="OFF")
    self.button.grid(row=0, column=0, sticky="nsew") # same for all instances of Make_sound

You should put the set of widgets in a frame and use pack() on the frame:

def soundbuttoncreator(self):
    self.frame = tk.Frame(self.parent) # create a frame to hold the widgets
    
    # use self.frame as parent instead of self.parent
    self.volumescale = tk.Scale(self.frame, orient='vertical', from_=0, to=10, resolution=0.1, tickinterval=2, label='Volume (db)', )
    self.volumescale.grid(row=0,column=1, rowspan=2, sticky="nsew")
    self.faderscale = tk.Scale(self.frame, orient='horizontal', from_=0, to=10, resolution=0.1, tickinterval=2, label='Volume (db)')
    self.faderscale.grid(row=1,column=0, sticky="nsew")
    self.button = tk.Checkbutton(self.frame, text=self.name, indicatoron=False, selectcolor="green", background="red",
                                 onvalue="ON", offvalue="OFF", variable=self.varbutton) 
    self.button.grid(row=0, column=0, sticky="nsew")

    self.frame.pack()  # use pack() on the frame so new instance of `Make_sound` will not overlap the old instances
acw1668
  • 40,144
  • 5
  • 22
  • 34