How to create an automatically filled scrolling grid of frames (tkinter)? - python

I try to create a scrolling grid of frames. I will have a variable number of frames, so I want to specify the number of columns, but not the number of rows, though we can scroll down if it is too long.
I have written some code, but now I don't know how to specify that when creating a frame (a button + 2 scales) with sounds_buttons(), it belongs to a certain row and a certain column. Also, I won't always have a multiple of 5 for the number of frames, so the last row can be constituted of only 1,2,3 or 4 frames.
The goal would be to have something like that, but with an undetermined number of rows: tkinter Canvas Scrollbar with Grid?.
Thank you very much !
#!/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 frame for the canvas with non-zero row&column weights
self.frame_canvas = tk.Frame(self)
self.frame_canvas.grid(row=2, column=0, pady=(8, 0), sticky='nw')
self.frame_canvas.grid_rowconfigure(0, weight=1)
self.frame_canvas.grid_columnconfigure(0, weight=1)
# Set grid_propagate to False to allow buttons resizing later
self.frame_canvas.grid_propagate(False)
# create a canvas object and a vertical scrollbar for scrolling it
vscrollbar = tk.Scrollbar(self.frame_canvas, orient=tk.VERTICAL)
vscrollbar.grid(row=0, column=1, sticky='ns')
self.canvas = tk.Canvas(self.frame_canvas, bd=0, highlightthickness=0,
yscrollcommand=vscrollbar.set, width=root.winfo_screenwidth(), height=root.winfo_screenheight())
self.canvas.grid(row=0, column=0, sticky="news")
vscrollbar.config(command=self.canvas.yview)
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
# 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(self.canvas)
interior_id = self.canvas.create_window(0, 0, window=interior,
anchor=tk.NW)
self.interior.update_idletasks()
# 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())
self.canvas.config(scrollregion="0 0 %s %s" % size)
if interior.winfo_reqwidth() != self.canvas.winfo_width():
# update the canvas's width to fit the inner frame
self.canvas.config(width=interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
def _configure_canvas(event):
if interior.winfo_reqwidth() != self.canvas.winfo_width():
# update the inner frame's width to fill the canvas
self.canvas.itemconfigure(interior_id, width=self.canvas.winfo_width())
self.canvas.bind('<Configure>', _configure_canvas)
def _on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
# ------------------------------- 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_=100, to=0, resolution=0.5, label='Volume',command=self.setvolume, cursor="fleur")
self.volumescale.grid(row=0,column=1, rowspan=2, sticky="nsew")
self.volumescale.set(100)
self.faderscale = tk.Scale(self.frame, orient='horizontal', from_=-1, to=1, resolution=0.01, label='Balance G/D', command=self.setbalance, cursor="fleur")
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, cursor="plus")
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 setvolume(self,event):
pass
def setbalance(self,event):
pass
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:
I have modified the Make_sound class and sounds_buttons function. There is an error.
class Make_sound:
def __init__(self, name, parent, i):
self.varbutton = tk.StringVar()
self.name = name
self.parent = parent
self.num = i
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.rows = self.num//5
self.columns = self.num%5
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_=100, to=0, resolution=0.5, label='Volume',command=self.setvolume, cursor="fleur")
self.volumescale.grid(row=0,column=1, rowspan=2, sticky="nsew")
self.volumescale.set(100)
self.faderscale = tk.Scale(self.frame, orient='horizontal', from_=-1, to=1, resolution=0.01, label='Balance G/D', command=self.setbalance, cursor="fleur")
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, cursor="plus")
self.button.grid(row=0, column=0, sticky="nsew")
self.frame.grid(row=self.rows, column=self.columns)
def setvolume(self,event):
pass
def setbalance(self,event):
pass
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,i)

Related

Trying to change a scrolled canvas width with mouse wheel

I'm trying to control multiple canvases widths with the mouse wheel. What I have so far is this:
import tkinter as tk
class App(tk.Frame):
row_amount = 3
def __init__(self, root):
super(App, self).__init__(root)
self.root = root
self.main_frame = tk.Frame(root)
self.main_frame.pack(expand=True, fill=tk.BOTH)
self.row_collection = RowCollection(root, self.main_frame)
for i in range(App.row_amount): self.row_collection.row()
window_height = App.row_amount * 100
window_width = root.winfo_screenwidth() - 30
root.geometry(f'{window_width}x{window_height}+0+0')
self.row_collection.right_frame.grid_columnconfigure(0, weight=1)
self.row_collection.left_frame.grid_columnconfigure(0, weight=1)
self.pack()
class RowCollection:
"""Collection of rows"""
def __init__(self, root, frame):
self.row_list = []
self.root = root
self.frame = frame
self.right_frame = tk.Frame(self.frame, bg='red')
self.right_frame.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.BOTH)
self.left_frame = tk.Frame(self.frame)
self.left_frame.pack(side=tk.LEFT, fill=tk.Y)
self.scrollbar = tk.Scrollbar(self.right_frame, orient=tk.HORIZONTAL)
self.scrollbar.config(command=self.scroll_x)
def row(self):
row = Row(self)
self.row_list.append(row)
return row
def scroll_x(self, *args):
for row in self.row_list:
row.canvas.xview(*args)
def zoomer(self, event=None):
print('zooming')
for row in self.row_list:
scale_factor = 0.1
curr_width = row.canvas.winfo_reqwidth()
print(f'event delta={event.delta}')
if event.delta > 0:
row.canvas.config(width=curr_width * (1 + scale_factor))
elif event.delta < 0:
row.canvas.config(width=curr_width * (1 - scale_factor))
row.canvas.configure(scrollregion=row.canvas.bbox('all'))
class Row:
"""Every row consists of a label on the left side and a canvas with a line on the right side"""
row_count = 0
label_width = 15
line_weight = 3
line_yoffset = 3
padx = 20
def __init__(self, collection):
self.frame = collection.frame
self.root = collection.root
self.collection = collection
self.canvas = None
self.label = None
self.text = f'Canvas {Row.row_count}'
self.height = 100
self.root.update()
self.label = tk.Label(self.collection.left_frame,
text=self.text,
height=1,
width=Row.label_width,
relief='raised')
self.label.grid(row=Row.row_count, column=0, sticky='ns')
# configure row size to match future canvas height
self.collection.left_frame.grid_rowconfigure(Row.row_count, minsize=self.height)
self.root.update()
self.canvas = tk.Canvas(self.collection.right_frame,
width=10000,
height=self.height,
bg='white',
highlightthickness=0)
self.canvas.grid(row=Row.row_count, column=0, sticky=tk.W)
self.root.update()
# draw line
self.line = self.canvas.create_rectangle(self.padx,
self.canvas.winfo_height() - Row.line_yoffset,
self.canvas.winfo_width() - self.padx,
self.canvas.winfo_height() - Row.line_yoffset + Row.line_weight,
fill='#000000', width=0, tags='line')
# config canvas
self.canvas.config(scrollregion=self.canvas.bbox('all'))
self.canvas.config(xscrollcommand=self.collection.scrollbar.set)
self.canvas.bind('<Configure>', lambda event: self.canvas.configure(scrollregion=self.canvas.bbox('all')))
self.canvas.bind('<MouseWheel>', self.collection.zoomer)
# Create point at canvas edge to prevent scrolling from removing padding
self.bounding_point = self.canvas.create_rectangle(0, 0, 0, 0, width=0)
self.bounding_point = self.canvas.create_rectangle(self.canvas.winfo_width(), self.canvas.winfo_width(),
self.canvas.winfo_width(), self.canvas.winfo_width(),
width=0)
Row.row_count += 1
self.collection.scrollbar.grid(row=Row.row_count, column=0, sticky='ew')
if __name__ == '__main__':
root = tk.Tk()
app = App(root)
root.mainloop()
The canvases themselves are inside right_frame, and the number of canvases is given by row_amount. The left_frame contains labels for each of the canvases. The canvases should be allowed to be pretty wide, so I initially set a width value of 10000. Because of that, they start partially visible, with the rest being accessible via a scrollbar.
What I would like is for the mouse wheel to control the size of the canvas as a whole (that is, both what is currently visible and what could be viewed using the scrollbar), similar to what would happen in an audio or video editing software timeline.
Right now, when I use the mouse wheel, what seems to get resized is not the whole canvas, but only the 'visible' portion. Resize it to be small enough and you can start to see it's frame background on the right portion of the window.
What am I missing here?
What am I missing here?
I think what you're missing is that the drawable area of the canvas is not at all related to the physical size of the canvas widget. You do not need to resize the canvas once it has been created. You can draw well past the borders of the widget.
If you want to be able to scroll elements into view that are not part of the visible canvas, you must configure the scrollregion to define the area of the virtual canvas that should be visible.
You said in a comment you're trying to create a timeline. Here's an example of a canvas widget that "grows" by adding a tickmark every second. Notice that the canvas is only 500,100, but the drawable area gets extended every second.
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=500, height=100, bg="black")
vsb = tk.Scrollbar(root, orient="vertical", command=canvas.yview)
hsb = tk.Scrollbar(root, orient="horizontal", command=canvas.xview)
canvas.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
canvas.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
counter = 0
def add_tick():
global counter
# get the current state of the scrollbar. We'll use this later
# to determine if we should auto-scroll
xview = canvas.xview()
# draw a new tickmark
counter += 1
x = counter * 50
canvas.create_text(x, 52, anchor="n", text=counter, fill="white")
canvas.create_line(x, 40, x, 50, width=3, fill="red")
# update the scrollable region to include the new tickmark
canvas.configure(scrollregion=canvas.bbox("all"))
# autoscroll, only if the widget was already scrolled
# as far to the right as possible
if int(xview[1]) == 1:
canvas.xview_moveto(1.0)
canvas.after(1000, add_tick)
add_tick()
root.mainloop()

How to make a responsive canva on tkinter?

(This first edit was made before changing the title, please read till the end!)
I am having problems while adjusting the screen of Tkinter on Windows 10.
I was doing things like that:
width_screen = root.winfo_screenwidth()
height_screen = root.winfo_screenheight()
root.geometry(f'{width_screen}x{height_screen}')
But the problem is that this configuration hide my taskbar... I search a way to set the screen like as it is for my browser for example, with a maximized window and a taskbar.
Thank you very much for your help.
EDIT 1: It is not working with this code...
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# -------------------------------- Importation ------------------------------- #
import os
import subprocess
import tkinter as tk
# ------------------------------ Initialisation ------------------------------ #
root = tk.Tk() #initialise l'application
root.title("Bruits ambiants pour l'écoute du patient")
width_screen = root.winfo_screenwidth()
height_screen = root.winfo_screenheight()
root.state('zoomed')
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","gg.wav","hh.wav","ii.wav","jj.wav"]
# ---------------------------------------------------------------------------- #
# Vertical scrolled frame #
# ---------------------------------------------------------------------------- #
class VerticalScrolledFrame(tk.Frame):
def __init__(self, parent, *args, **kw):
tk.Frame.__init__(self, parent, *args, **kw)
# Create a frame for the canvas with non-zero row&column weights
self.frame_canvas = tk.Frame(self,bg="gray50")
self.frame_canvas.grid(row=2, column=0, sticky='nw')
self.frame_canvas.grid_rowconfigure(0, weight=1)
self.frame_canvas.grid_columnconfigure(0, weight=1)
self.parent=parent
# create a canvas object and a vertical scrollbar for scrolling it
vscrollbar = tk.Scrollbar(self.frame_canvas, orient=tk.VERTICAL)
vscrollbar.grid(row=0, column=1, sticky='ns')
self.canvas = tk.Canvas(self.frame_canvas, bd=0, highlightthickness=0,
yscrollcommand=vscrollbar.set, width=self.parent.winfo_screenwidth(),
height=self.parent.winfo_screenheight()-100)
self.canvas.grid(row=0, column=0, sticky="news")
vscrollbar.config(command=self.canvas.yview)
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
# create a frame inside the canvas which will be scrolled with it
self.interior = interior = tk.Frame(self.canvas,bg="gray50")
interior_id = self.canvas.create_window(0, 0, window=interior,
anchor=tk.NW)
self.interior.update_idletasks()
# 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())
self.canvas.config(scrollregion="0 0 %s %s" % size)
if interior.winfo_reqwidth() != self.canvas.winfo_width():
# update the canvas's width to fit the inner frame
self.canvas.config(width=interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
self.canvas.config(scrollregion=self.canvas.bbox("all"))
def _configure_canvas(event):
if interior.winfo_reqwidth() != self.canvas.winfo_width():
# update the inner frame's width to fill the canvas
self.canvas.itemconfigure(interior_id, width=self.canvas.winfo_width())
self.canvas.bind('<Configure>', _configure_canvas)
def _on_mousewheel(self, event):
if len(wav_files) > 25:
self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
# ---------------------------------------------------------------------------- #
# Sound Buttons #
# ---------------------------------------------------------------------------- #
class Make_sound:
def __init__(self, name, parent, i):
self.varbutton = tk.StringVar()
self.name = name
self.parent = parent
self.num = i
self.soundbuttoncreator()
def soundbuttoncreator(self):
self.rows = self.num//4
self.columns = self.num%4
self.frame = tk.Frame(self.parent,bg="gray50", bd=3, relief="flat") # create a frame to hold the widgets
# use self.frame as parent instead of self.parent
self.button = tk.Checkbutton(self.frame, text=self.name.capitalize(), indicatoron=False, selectcolor="DeepSkyBlue3", background="slate gray", activebackground="LightSteelBlue3",variable=self.varbutton, command=self.launchsound, height=6, width=20)
self.button.pack()
self.button.bind("<Enter>", self.on_enter)
self.button.bind("<Leave>", self.on_leave)
self.frame.grid(row=self.rows, column=self.columns)
def on_enter(self, e):
self.button['background'] = 'LightSteelBlue3'
def on_leave(self, e):
self.button['background'] = 'slate gray'
def launchsound(self):
pass
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,i)
def end_all():
for i in range(len(wav_files)):
globals()["wav_files"][i].varbutton.set("0")
try:
globals()["wav_files"][i].chan.stop()
except AttributeError:
pass
# ---------------------------------------------------------------------------- #
# Creation #
# ---------------------------------------------------------------------------- #
# ---------------------------------- Button ---------------------------------- #
frame_test = tk.Frame(root)
frame_test.grid(row=10,column=0, columnspan=5, sticky="s",padx=5,pady=10)
Button_open = tk.Button(frame_test, text="Open", background="slate gray", activebackground="LightSteelBlue3")
Button_open.pack(fill="x")
Button_end = tk.Button(frame_test, text="End", background="slate gray", activebackground="LightSteelBlue3")
Button_end.pack(fill="x")
# ---------------------------------------------------------------------------- #
# LEFT BUTTONS #
# ---------------------------------------------------------------------------- #
frame_buttons = tk.Frame(root,bd=5,bg="gray50")
frame_buttons.grid(row=1,column=0,rowspan=8,padx=5,pady=10,sticky="nw")
scframe = VerticalScrolledFrame(frame_buttons)
scframe.grid(row=1,column=0,rowspan=20,columnspan=3)
sounds_buttons(scframe.interior)
# ----------------------------------- test ----------------------------------- #
panel = tk.Button(root, text="test", background="slate gray", activebackground="LightSteelBlue3")
panel.grid(row=0,column=0,sticky="nw")
# ---------------------------------------------------------------------------- #
# ROOT #
# ---------------------------------------------------------------------------- #
root.mainloop()
(Some EDITS were suppressed for your readability)
LAST EDIT:
I found out that the problem comes from a part of my class:
self.canvas = tk.Canvas(self.frame_canvas, bd=0, highlightthickness=0,
yscrollcommand=vscrollbar.set, width=self.parent.winfo_screenwidth(),
height=self.parent.winfo_screenheight()-100)
In fact, the height=self.parent.winfo_screenheight()-100 part is not working properly. If I put height=self.parent.winfo_screenheight()-1000, here is my output:
It is promising, because I now see the frame. Now, I understand that I just want the canvas to be responsive and not to set height and width, though I can work with it many computers!
Could you please explain me a way to achieve that? Maybe for example, always having 4 columns of buttons but with their dimensions that can change, and setting the button list to always be taking the rest of the screen (we could say that it may take the half of the screen width, and that the buttons over and below must grow as the button list grows?).
Try this code below:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# -------------------------------- Importation ------------------------------- #
import os
import subprocess
import tkinter as tk
# ------------------------------ Initialisation ------------------------------ #
root = tk.Tk() # initialise l'application
root.title("Bruits ambiants pour l'écoute du patient")
width_screen = root.winfo_screenwidth()
height_screen = root.winfo_screenheight()
root.state('zoomed')
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", "gg.wav", "hh.wav", "ii.wav",
"jj.wav"]
# ---------------------------------------------------------------------------- #
# Vertical scrolled frame #
# ---------------------------------------------------------------------------- #
class VerticalScrolledFrame(tk.Frame):
def __init__(self, parent, *args, **kw):
tk.Frame.__init__(self, parent, *args, **kw)
# Create a frame for the canvas with non-zero row&column weights
self.parent = parent
# create a canvas object and a vertical scrollbar for scrolling it
vscrollbar = tk.Scrollbar(self, orient=tk.VERTICAL)
vscrollbar.pack(fill="y", side="right",expand=True)
self.canvas = tk.Canvas(self, bd=0, highlightthickness=1,
yscrollcommand=vscrollbar.set)
self.canvas.pack(fill="both", expand=True)
vscrollbar.config(command=self.canvas.yview)
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
# create a frame inside the canvas which will be scrolled with it
self.interior = interior = tk.Frame(self.canvas, bg="gray50")
interior_id = self.canvas.create_window(0, 0, window=interior,
anchor=tk.NW)
self.interior.update_idletasks()
# 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())
self.canvas.config(scrollregion="0 0 %s %s" % size)
if interior.winfo_reqwidth() != self.canvas.winfo_width():
# update the canvas's width to fit the inner frame
self.canvas.config(width=interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
self.canvas.config(scrollregion=self.canvas.bbox("all"))
def _configure_canvas(event):
if interior.winfo_reqwidth() != self.canvas.winfo_width():
# update the inner frame's width to fill the canvas
self.canvas.itemconfigure(interior_id, width=self.canvas.winfo_width())
self.canvas.bind('<Configure>', _configure_canvas)
def _on_mousewheel(self, event):
if len(wav_files) > 25:
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
# ---------------------------------------------------------------------------- #
# Sound Buttons #
# ---------------------------------------------------------------------------- #
class Make_sound:
def __init__(self, name, parent, i):
self.varbutton = tk.StringVar()
self.name = name
self.parent = parent
self.num = i
self.soundbuttoncreator()
def soundbuttoncreator(self):
self.rows = self.num // 4
self.columns = self.num % 4
self.frame = tk.Frame(self.parent, bg="gray50", bd=3, relief="flat") # create a frame to hold the widgets
# use self.frame as parent instead of self.parent
self.button = tk.Checkbutton(self.frame, text=self.name.capitalize(), indicatoron=False,
selectcolor="DeepSkyBlue3", background="slate gray",
activebackground="LightSteelBlue3", variable=self.varbutton,
command=self.launchsound, height=6, width=20)
self.button.pack()
self.button.bind("<Enter>", self.on_enter)
self.button.bind("<Leave>", self.on_leave)
self.frame.grid(row=self.rows, column=self.columns)
def on_enter(self, e):
self.button['background'] = 'LightSteelBlue3'
def on_leave(self, e):
self.button['background'] = 'slate gray'
def launchsound(self):
pass
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, i)
def end_all():
for i in range(len(wav_files)):
globals()["wav_files"][i].varbutton.set("0")
try:
globals()["wav_files"][i].chan.stop()
except AttributeError:
pass
# ---------------------------------------------------------------------------- #
# Creation #
# ---------------------------------------------------------------------------- #
# ---------------------------------- Button ---------------------------------- #
frame_test = tk.Frame(root)
frame_test.grid(row=10, column=0, columnspan=5, sticky="ns")
Button_open = tk.Button(frame_test, text="Open", background="slate gray", activebackground="LightSteelBlue3")
Button_open.grid(row=0, column=0, sticky="ns")
Button_end = tk.Button(frame_test, text="End", background="slate gray", activebackground="LightSteelBlue3")
Button_end.grid(row=1, column=0, sticky="ns")
# ---------------------------------------------------------------------------- #
# LEFT BUTTONS #
# ---------------------------------------------------------------------------- #
frame_buttons = tk.Frame(root, bd=5, bg="gray50")
frame_buttons.grid(row=1, column=0, rowspan=8, padx=5, pady=10, sticky="nwes")
scframe = VerticalScrolledFrame(frame_buttons)
scframe.pack(fill="both", expand=True)
sounds_buttons(scframe.interior)
# ----------------------------------- test ----------------------------------- #
panel = tk.Button(root, text="test", background="slate gray", activebackground="LightSteelBlue3")
panel.grid(row=0, column=0, sticky="nw")
# ---------------------------------------------------------------------------- #
# ROOT #
# ---------------------------------------------------------------------------- #
for i in range(1, 11):
root.grid_rowconfigure(i, weight=1)
for i in range(frame_test.grid_size()[1]+1):
frame_test.grid_rowconfigure(i, weight=1)
root.mainloop()
Too much code,A little hard to understand your layout.
I changed a lot from your code,your frame_buttons doesn't use sticky="nwes".So it couldn't fill in the frame.
And in the canvas, you also need to use pack manager(if you didn't use sticky="nwes" and set the rowconfigure, I still recommend you use pack).
for responsive, you need to set gird_rowconfigure
check the code for more details.
The output:
If there only one button on the canvas, the scrollbar is disabled:

How to move popup window when scrolling a tkinter treeview?

I have a tkinter treeview with a vertical scrollbar. To make it (look like) editable, I create a popup Entry when the user double-clicks on a cell of the treeview. However, I can't make the popup to move when the treeview is scrolled.
import tkinter as tk
from tkinter import ttk
class EntryPopup(ttk.Entry):
def __init__(self, parent, itemId, col, **kw):
super().__init__(parent, **kw)
self.tv = parent
self.iId = itemId
self.column = col
self['exportselection'] = False
self.focus_force()
self.bind("<Return>", self.onReturn)
def saveEdit(self):
self.tv.set(self.iId, column=self.column, value=self.get())
print("EntryPopup::saveEdit---{}".format(self.iId))
def onReturn(self, event):
self.tv.focus_set()
self.saveEdit()
self.destroy()
class EditableDataTable(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.tree = None
self.entryPopup = None
columns = ("Col1", "Col2")
# Create a treeview with vertical scrollbar.
self.tree = ttk.Treeview(self, columns=columns, show="headings")
self.tree.grid(column=0, row=0, sticky='news')
self.tree.heading("#1", text="col1")
self.tree.heading("#2", text="col2")
self.vsb = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview)
self.tree.configure(yscrollcommand=self.vsb.set)
self.vsb.grid(column=1, row=0, sticky='ns')
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.entryPopup = None
self.curSelectedRowId = ""
col1 = []
col2 = []
for r in range(50):
col1.append("data 1-{}".format(r))
col2.append("data 2-{}".format(r))
for i in range(min(len(col1),len(col2))):
self.tree.insert('', i, values=(col1[i], col2[i]))
self.tree.bind('<Double-1>', self.onDoubleClick)
def createPopup(self, row, column):
x,y,width,height = self.tree.bbox(row, column)
# y-axis offset
pady = height // 2
self.entryPopup = EntryPopup(self.tree, row, column)
self.entryPopup.place(x=x, y=y+pady, anchor='w', width=width)
def onDoubleClick(self, event):
rowid = self.tree.identify_row(event.y)
column = self.tree.identify_column(event.x)
self.createPopup(rowid, column)
root = tk.Tk()
for row in range(2):
root.grid_rowconfigure(row, weight=1)
root.grid_columnconfigure(0, weight=1)
label = tk.Label(root, text="Double-click to edit and press 'Enter'")
label.grid(row=0, column=0, sticky='news', padx=10, pady=5)
dataTable = EditableDataTable(root)
dataTable.grid(row=1, column=0, sticky="news", pady=10, padx=10)
root.geometry("450x300")
root.mainloop()
To reproduce the problem, double-click on the treeview. While the edit box is open, move your mouse pointer to hover over the treeview. Now scroll using the mouse wheel. The treeview moves but the popup edit box does not.
I have done something similar before by binding a function to mousewheel and recalculate all the new x & y location of your hovering widgets.
class EditableDataTable(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.tree = None
self.entryPopup = None
self.list_of_entries = []
...
self.tree.bind("<MouseWheel>", self._on_mousewheel)
def _on_mousewheel(self, event):
if self.list_of_entries:
def _move():
for i in self.list_of_entries:
try:
iid = i.iId
x, y, width, height = self.tree.bbox(iid, column="Col2") #hardcoded to col2
i.place(x=x, y=y+height//2, anchor='w', width=width)
except ValueError:
i.place_forget()
except tk.TclError:
pass
self.master.after(5, _move)
def createPopup(self, row, column):
x,y,width,height = self.tree.bbox(row, column)
# y-axis offset
pady = height // 2
self.entryPopup = EntryPopup(self.tree, row, column)
self.list_of_entries.append(self.entryPopup)
self.entryPopup.place(x=x, y=y+pady, anchor='w', width=width)
Note that this only works on the second column, but should be easy enough to implement for the rest.
You will need to do the math and move the entry widget when the tree is scrolled. I have edited your code and I programmed the scrollbar buttons only. If you click the button the entry widget will scroll with the tree. I did not program the wheelmouse scrolling or dragging the scrollbar. You should be able to figure out the rest.
import tkinter as tk
import tkinter.font as tkfont
from tkinter import ttk
class EntryPopup(ttk.Entry):
def __init__(self, parent, itemId, col, **kw):
super().__init__(parent, **kw)
self.tv = parent
self.iId = itemId
self.column = col
self['exportselection'] = False
self.focus_force()
self.bind("<Return>", self.onReturn)
def saveEdit(self):
self.tv.set(self.iId, column=self.column, value=self.get())
print("EntryPopup::saveEdit---{}".format(self.iId))
def onReturn(self, event):
self.tv.focus_set()
self.saveEdit()
self.destroy()
class EditableDataTable(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.tree = None
self.entryPopup = None
columns = ("Col1", "Col2")
# Create a treeview with vertical scrollbar.
self.tree = ttk.Treeview(self, columns=columns, show="headings")
self.tree.grid(column=0, row=0, sticky='news')
self.tree.heading("#1", text="col1")
self.tree.heading("#2", text="col2")
self.vsb = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview)
self.tree.configure(yscrollcommand=self.vsb.set)
self.vsb.grid(column=1, row=0, sticky='ns')
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.entryPopup = None
self.curSelectedRowId = ""
col1 = []
col2 = []
for r in range(50):
col1.append("data 1-{}".format(r))
col2.append("data 2-{}".format(r))
for i in range(min(len(col1),len(col2))):
self.tree.insert('', i, values=(col1[i], col2[i]))
self.tree.bind('<Double-1>', self.onDoubleClick)
self.vsb.bind('<ButtonPress-1>', self.func)
def func(self, event):
print(self.vsb.identify(event.x, event.y))
if hasattr(self.entryPopup, 'y'):
item = self.vsb.identify(event.x, event.y)
if item == 'uparrow':
self.entryPopup.y += 20
elif item == 'downarrow':
self.entryPopup.y -= 20
self.entryPopup.place(x=self.entryPopup.x, y=self.entryPopup.y, )
def createPopup(self, row, column):
x, y, width, height = self.tree.bbox(row, column)
# y-axis offset
pady = height // 2
self.entryPopup = EntryPopup(self.tree, row, column)
self.entryPopup.x = x
self.entryPopup.y = y+pady
self.entryPopup.place(x=x, y=y+pady, anchor='w', width=width)
def onDoubleClick(self, event):
rowid = self.tree.identify_row(event.y)
column = self.tree.identify_column(event.x)
self.createPopup(rowid, column)
root = tk.Tk()
for row in range(2):
root.grid_rowconfigure(row, weight=1)
root.grid_columnconfigure(0, weight=1)
label = tk.Label(root, text="Double-click to edit and press 'Enter'")
label.grid(row=0, column=0, sticky='news', padx=10, pady=5)
dataTable = EditableDataTable(root)
dataTable.grid(row=1, column=0, sticky="news", pady=10, padx=10)
root.geometry("450x300")
root.mainloop()
I have simpler solution than tracking mouse events:
self.tree.configure(yscrollcommand = self.ScrollTree)
def ScrollTree(self, a, b):
if self.entryPopup is not None:
pos = self.tree.bbox(self.entryPopup.iid , 'value')
# if cell visible
if pos != '':
self.entryPopup.place(x=pos[0], y=pos[1], width = pos[2], height = pos[3])
else:
self.entryPopup.place_forget()
# update attached scrollbar
self.vsb.set(a, b)

Scrollable frame does not fill canvas

I use Python 2.7 and I have a scrollable frame where the canvas is not shrinking to fit the frame I want to make scrollable.
I looked at this question for an answer but it does not work when I run it:
How to resize a scrollable frame to fill the canvas?
When I print the width of the frame inside the canvas, it says 0.
I also ran the code from the answer of this question on my computer :
Scrollable Frame does not resize properly using tkinter in Python
but it will still show the white canvas to the left of the labels, and it does not resize when the labels are deleted.
It looks like this:
This is my code, based on the answer in this question:
Adding a scrollbar to a group of widgets in Tkinter
from Tkinter import *
class Scrollable_frame(Frame):
def __init__(self, parent, title, values):
self.parent = parent
Frame.__init__(self, self.parent)
self.canvas = Canvas(self, borderwidth=0, background="#ffffff")
self.scrollbar = Scrollbar(self, command=self.canvas.yview)
self.innerFrame = Radiobutton_frame(self.canvas,title,values)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.grid(row=0, column=0, sticky= N+S)
self.scrollbar.grid(row=0, column=1, sticky = N+S)
self.canvas.create_window((0,0),window = self.innerFrame,anchor="nw")
self.innerFrame.bind("<Configure>", self.set_canvas_scrollregion)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def set_canvas_scrollregion(self, event):
width = event.width - 4
self.canvas.itemconfigure("self.innerFrame ", width=width)
self.canvas.config(scrollregion=self.canvas.bbox("all"))
class Radiobutton_frame(LabelFrame):
def __init__(self, parent, title, values):
"""
In: parent - Canvas
title - String
values - List of Int
"""
self.radiobuttons = {}
self.parent = parent
self.selection = StringVar()
self.selection.set("init")
LabelFrame.__init__(self, self.parent, text = title)
for value in values:
self.add_radiobutton(value)
def add_radiobutton(self, value):
"""
Adds a radiobutton to the frame.
In: item - String
"""
# Associate to same variable to make them function as a group
self.radiobuttons[value] = Radiobutton(master = self,
variable = self.selection,
text = value,
value = value)
self.radiobuttons[value].pack(anchor=W)
# Usage example
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
scrollableFrame = Scrollable_frame(root, "Canvas not resizing", range(30))
scrollableFrame.grid(row=0, column=0, sticky=N+S+E+W)
if __name__ == '__main__':
root.mainloop()
I don't think above question's code snippet fits to a Minimal, Complete, and Verifiable example but at the very least it's runnable.
You have three mistakes compared to that of: How to resize a scrollable frame to fill the canvas?
The most significant of which is that in the linked question, the OP uses the option tags where you don't. Replace:
self.canvas.create_window((0,0),window = self.innerFrame,anchor="nw")
with:
self.canvas.create_window((0,0),window = self.innerFrame, anchor="nw", tags="my_tag")
Another mistake is that you're binding the event of a frame's resizing as opposed to the actual Canvas' resizing, also pointed out in Bryan's comment here. Replace:
self.innerFrame.bind("<Configure>", self.set_canvas_scrollregion)
with:
self.canvas.bind("<Configure>", self.set_canvas_scrollregion)
Lastly, tkinter doesn't seem to accept space character with tags, replace:
self.canvas.itemconfigure("self.innerFrame ", width=width)
with:
self.canvas.itemconfigure("my_tag", width=width)
Finally, you should have:
from Tkinter import *
class Scrollable_frame(Frame):
def __init__(self, parent, title, values):
self.parent = parent
Frame.__init__(self, self.parent)
self.canvas = Canvas(self, borderwidth=0, background="#ffffff")
self.scrollbar = Scrollbar(self, command=self.canvas.yview)
self.innerFrame = Radiobutton_frame(self.canvas,title,values)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.grid(row=0, column=0, sticky= N+S)
self.scrollbar.grid(row=0, column=1, sticky = N+S)
self.canvas.create_window((0,0),window = self.innerFrame,anchor="nw",
tags="my_tag")
self.canvas.bind("<Configure>", self.set_canvas_scrollregion)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def set_canvas_scrollregion(self, event):
width = event.width - 4
self.canvas.itemconfigure("my_tag", width=width)
self.canvas.config(scrollregion=self.canvas.bbox("all"))
class Radiobutton_frame(LabelFrame):
def __init__(self, parent, title, values):
"""
In: parent - Canvas
title - String
values - List of Int
"""
self.radiobuttons = {}
self.parent = parent
self.selection = StringVar()
self.selection.set("init")
LabelFrame.__init__(self, self.parent, text = title)
for value in values:
self.add_radiobutton(value)
def add_radiobutton(self, value):
"""
Adds a radiobutton to the frame.
In: item - String
"""
# Associate to same variable to make them function as a group
self.radiobuttons[value] = Radiobutton(master = self,
variable = self.selection,
text = value,
value = value)
self.radiobuttons[value].pack(anchor=W)
# Usage example
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
scrollableFrame = Scrollable_frame(root, "Canvas not resizing", range(30))
scrollableFrame.grid(row=0, column=0, sticky=N+S+E+W)
if __name__ == '__main__':
root.mainloop()

Tkinter canvas not filing root window

The following code creates a canvas with a frame inside. I am using the canvas to enable the addition of a vertical scrollbar, which appears as necessary. I have coloured the various sections - root, blue; window, green; canvas, red and frame, cyan. From this it can be seen that window completely fills root (no blue seen). However, canvas only fills window in the vertical direction (green visible to the rhs on expanding the window, and frame only fills canvas in the horizontal direction (red visible below cyan). I can't see what is causing the problem. Frame is bound to canvas and canvas has sticky='nsew' set so should be filling window.
import Tkinter as tk
import tkFileDialog
class AutoScrollbar(tk.Scrollbar):
# a scrollbar that hides itself if it's not needed. only
# works if you use the grid geometry manager.
def set(self, lo, hi):
if float(lo) <= 0.0 and float(hi) >= 1.0:
# grid_remove is currently missing from Tkinter!
self.tk.call("grid", "remove", self)
else:
self.grid()
tk.Scrollbar.set(self, lo, hi)
def pack(self, **kw):
raise TclError, "cannot use pack with this widget"
def place(self, **kw):
raise TclError, "cannot use place with this widget"
class Window(tk.Frame):
def UserInput(self,status,name):
row = self.row
optionLabel = tk.Label(self.frame)
optionLabel["text"] = name
optionLabel.grid(row=row, column=0, sticky='w')
var = tk.StringVar(root)
var.set(status)
w = tk.Entry(self.frame, textvariable= var)
w.grid(row=row, column=1, sticky='ew')
self.grid_columnconfigure(1,weight=1)
self.row += 1
return w
def on_canvas_resize(self,event):
padding = 8
width = self.canvas.winfo_width() - padding
self.canvas.itemconfigure("frame", width=width)
def OnFrameConfigure(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def __init__(self,parent):
tk.Frame.__init__(self,parent)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0,weight=1)
self.vscrollbar = AutoScrollbar(self,orient = tk.VERTICAL)
self.vscrollbar.grid(row = 0,column = 3, sticky = 'ns')
self.canvas = tk.Canvas(self, yscrollcommand = self.vscrollbar.set,bg='red')
self.canvas.grid(row=0, column=0, sticky='nsew')
self.canvas.grid_rowconfigure(0, weight = 1)
self.canvas.grid_columnconfigure(0, weight = 1)
self.frame = tk.Frame(self.canvas, bg='cyan')
self.frame.bind("<Configure>", self.OnFrameConfigure)
self.frame.grid_rowconfigure(0, weight=1)
self.frame.grid_columnconfigure(0,weight=1)
self.frame.grid_columnconfigure(1,weight=3)
self.vscrollbar.config(command=self.canvas.yview)
self.canvas.create_window(0, 0, anchor = tk.NW, window= self.frame, tags =["frame"])
self.canvas.bind("<Configure>", self.on_canvas_resize)
self.row = 0
for i in range(10):
self.Number = self.UserInput("1", "number")
if __name__ == "__main__":
root = tk.Tk()
root.configure(background='blue')
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0,weight=1)
window = Window(root)
window.configure(background='green')
window.grid(row=0,column=0,sticky='nsew')
root.mainloop()
You are putting the canvas in column 0 (zero), and you are properly giving that column a weight. However, you are also giving an equal weight to column 1 (one) (see line 29), which prevents the canvas from filling the window. Remove line 29, which is setting the weight of column 1 to 1.
Your frame fills the canvas horizontally because that is what you are telling it to do. If you want it to also be the same height you will need to set its height, too.

Categories

Resources