How to make reusable scrollbars in Tkinter? - python

I'd like to create an easy way to add scrollbars to any frame I like. So far, only one works. What's wrong with this script? What's a proper way to do this? I still only have a faint grasp of all the concepts hidden in this, sorry.
import Tkinter as tk
def data(parent):
for i in range(50):
tk.Label(parent,text=i).grid(row=i,column=0)
tk.Label(parent,text="my text"+str(i)).grid(row=i,column=1)
tk.Label(parent,text="..........").grid(row=i,column=2)
class ScrollBar():
#def __init__(self, tk):
# self.canvas = tk.Canvas()
def myfunction(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"), width=200, height=200)
print("orig: ",self.canvas)
def makeScrollBar(self, tk, parent):
self.outerframe = tk.Frame(parent, relief="groove", width=50, height=100, bd=1)
self.outerframe.pack()
self.canvas = tk.Canvas(self.outerframe)
self.innerframe = tk.Frame(self.canvas)
self.myscrollbar = tk.Scrollbar(self.outerframe, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.myscrollbar.set)
self.myscrollbar.pack(side="right",fill="y")
self.canvas.pack(side="left")
self.canvas.create_window((0,0), window=self.innerframe, anchor="nw")
self.innerframe.bind("<Configure>", self.myfunction)
print("orig: ",self.canvas)
return self.innerframe
root = tk.Tk()
root.wm_geometry("800x600+100+100")
scrollbargenerator = ScrollBar()
b = scrollbargenerator.makeScrollBar(tk, root)
c = scrollbargenerator.makeScrollBar(tk, root)
data(b)
data(c)
root.mainloop()

First, you manage to create your scrollbars. If you scroll your mouse over the first scrollbar it will actually scroll the canvas. The thumb does not appear (or do not move) because the scrollregion is never set for this canvas.
In fact, your use of classes and objects is broken. Your Scrollbar class is instantiated once and this only instance update its fields each time makeScrollBar is called. Thus in myfunction callback, self.canvas always refer to the lastly created canvas.
You can easily fix your code my using different scrollbar generators
scrollbargenerator = ScrollBar()
b = scrollbargenerator.makeScrollBar(tk, root)
scrollbargenerator = ScrollBar()
c = scrollbargenerator.makeScrollBar(tk, root)
or capturing the canvas in a closure
def myfunction(self, canvas):
canvas.configure(scrollregion=canvas.bbox("all"), width=200, height=200)
def makeScrollBar(self, tk, parent):
#(...)
self.innerframe.bind("<Configure>", (lambda canvas: (lambda event: self.myfunction(canvas)))(self.canvas))
or relying on the information already present in event
#staticmethod
def myfunction(event):
event.widget.master.configure(scrollregion=event.widget.master.bbox("all"), width=200, height=200)

Related

tkinter Scrollbar collapses the Canvas it's in

I'm attempting to make a program with two sections. The left section will display a vertically scrollable list while the right section will display info based on the items selected in the list. Ignore the right section since I haven't gotten there yet.
Below is a general idea of what it'll look like except the left section will scroll vertically.
Unfortunately when I pack the scrollbar the left section completely disappears.
Below is the code.
import tkinter as tk
class Tasks(tk.Tk):
def __init__(self, builds=None):
super().__init__()
if builds is None:
self.builds = []
else:
self.builds = builds
self.title('Title')
self.geometry('1000x600')
self.configure(bg='red')
self.tasks_canvas = tk.Canvas(self, width=200, bg='green')
self.tasks_frame = tk.Frame(self.tasks_canvas)
self.scrollbar = tk.Scrollbar(self.tasks_canvas, orient='vertical',command=self.tasks_canvas.yview)
self.canvas_frame = self.tasks_canvas.create_window((0, 0), window=self.tasks_frame, anchor='n')
self.tasks_canvas.configure(yscrollcommand=self.scrollbar.set)
self.tasks_canvas.pack(side=tk.LEFT, fill=tk.Y)
self.scrollbar.pack(side=tk.LEFT, fill=tk.Y, expand=1)
if __name__ == '__main__':
root = Tasks()
root.mainloop()
I'm sure I'm missing a simply concept but I just can't figure it out.
The reason this is happening is because of the way pack geometry manager works. Have a look at this answer. Quoting it here:
By default pack will attempt to shrink (or grow) a container to exactly fit its children. Because the scrollbar is a children of the canvas in the, the canvas shrinks to fit.
To get around this, you can use an extra Frame to contain the Canvas and the Scrollbar and set the parent of the Scrollbar as this Frame.
import tkinter as tk
class Tasks(tk.Tk):
def __init__(self, builds=None):
super().__init__()
self.title('Title')
self.geometry('400x120')
self.configure(bg='red')
self.t_frame = tk.Frame(self)
self.t_frame.pack(side=tk.LEFT)
self.tasks_canvas = tk.Canvas(self.t_frame, width=100, bg='green')
self.scrollbar = tk.Scrollbar(self.t_frame, orient='vertical',command=self.tasks_canvas.yview)
self.tasks_canvas.configure(yscrollcommand=self.scrollbar.set)
self.tasks_canvas.pack(side=tk.LEFT, fill=tk.Y)
self.scrollbar.pack(side=tk.LEFT, fill=tk.Y)
if __name__ == '__main__':
root = Tasks()
root.mainloop()

Scrollbar disappears from Tkinter canvas, and refuses to adhere to side-packing

I am trying to attach a scrollbar to a Tkinter canvas.
To test the scrollbar, I dynamically generated 100 "Hello World" labels, and packed these inside a content-holding frame.
My code fails. Issues:
The scrollbar does not even appear.
The content frame instead expands to the full height of the 100-packed labels.
Interestingly, the scrollbar magically reappears if I shift self.frame_for_content.pack() under self.scrollbar.pack(). (Why this is so is beyond me, but the scrollbar still does not work or adhere to the side packing behaviour either.)
I have attempted to incorporate some of Brian Oakley's suggestions on scroll region and bbox, to no avail.
I have reduced the code to the bare minimum, but am unable to push through. Would appreciate the help.
import tkinter as tk
import tkinter.ttk as ttk
class TestGUI(tk.Tk):
def __init__(self):
super().__init__()
self.canvas = tk.Canvas(self)
self.frame_for_content = tk.Frame(self.canvas)
self.canvas_frame = self.canvas.create_window((0,0), window=self.frame_for_content, anchor=tk.NW)
self.scrollbar = tk.Scrollbar(self.canvas, orient=tk.VERTICAL, command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.frame_for_content.pack()
self.canvas.pack(side=tk.LEFT)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
for i in range(100):
tk.Label(self.frame_for_content, text="Hello World - " + str(i)).pack()
self.update()
self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL))
TestGUI().mainloop()
You don't want self.frame_for_content.pack() because you're using the canvas like a geometry manager for that widget, so self.canvas.create_window takes the place of .pack or .grid.
To get the sizes right, you can get the width & height from the canvas bounding box.
I think this does what you want:
import tkinter as tk
class TestGUI(tk.Tk):
def __init__(self):
super().__init__()
self.canvas = tk.Canvas(self)
self.frame_for_content = tk.Frame(self.canvas)
self.canvas_frame = self.canvas.create_window((0,0), window=self.frame_for_content, anchor=tk.NW)
self.scrollbar = tk.Scrollbar(self, orient=tk.VERTICAL, command=self.canvas.yview)
self.canvas.pack(side=tk.LEFT)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
for i in range(100):
tk.Label(self.frame_for_content, text="Hello World - " + str(i)).pack()
self.update()
bbox = self.canvas.bbox(tk.ALL)
self.canvas.config(yscrollcommand=self.scrollbar.set,
width=bbox[2], height=bbox[3], scrollregion=bbox)
TestGUI().mainloop()

how do i put dynamically added entry fields in a set frame or canvas that does not resize

i have dynamically addable and delete able entry fields that i want to set inside a frame or canvas inside of a main frame but when i try the frame dissappears or dynamically grows with the entry fields. i want the canvas to use the scrollbar if entry fields exceed the window size.
from tkinter import *
import tkinter as tk
class Demo2:
def __init__(self, master):
global rows
self.master = master
self.frame = tk.Frame(self.master)
master.title("test")
self.frame.pack()
addboxButton = Button(self.frame, text='<Add Time Input>', fg="Red", command=self.addBox)
addboxButton.pack()
this is where my buttons are added and deleted.
def addBox(self):
def delete():
delboxButton.grid_remove()
ent1.delete(0,END)
ent2.delete(0,END)
ent1.grid_remove()
ent2.grid_remove()
root = self.frame
frame=Frame(root,width=900,height=900)
frame.pack()
canvas=Canvas(frame,bg='#FFFFFF',width=700,height=300,scrollregion=(0,0,700,300))
vbar=Scrollbar(frame,orient=VERTICAL)
vbar.pack(side=RIGHT,fill=Y)
vbar.config(command=canvas.yview)
canvas.config(width=700,height=300)
canvas.config(yscrollcommand=vbar.set)
canvas.pack(side=LEFT,expand=TRUE,fill=BOTH)
I am trying to figure out now how to make the first set of entry start out on the screen when its opened. and bind the add call to an action.
i = 0
ent1 = Entry(canvas)
ent1.grid(row=i, column=0,sticky="nsew")
i += 1
i = 0
ent2 = Entry(canvas)
ent2.grid(row=i, column=1,sticky="nsew")
i += 1
delboxButton = Button(canvas, text='delete', fg="Red", command=delete)
delboxButton.grid(row=0 ,column=2)
root = tk.Tk()
root.title("test Complete")
root.geometry("500x500")
app = Demo2(root)
root.mainloop()
The normal way this is tackled is to create a single frame and add it to the canvas with the canvas create_window method. Then, you can put whatever you want in the frame using pack, place or grid.
For a description of the technique see Adding a scrollbar to a group of widgets in Tkinter
Here's an example illustrating how the technique works for widgets created by a button. I didn't include the delete functionality or the ability for everything to resize properly to keep the example short, but you seem to have a pretty good idea of how to make the delete function work and I don't know exactly what sort of resize behavior you want.
import tkinter as tk
class Demo2:
def __init__(self, master):
self.master = master
self.entries = []
self.canvas = tk.Canvas(master, width=400, height=200)
self.vsb = tk.Scrollbar(master, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.add_button = tk.Button(master, text="Add", command=self.add)
self.container = tk.Frame()
self.canvas.create_window(0, 0, anchor="nw", window=self.container)
self.add_button.pack(side="top")
self.vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
# start with 3 entry widgets
self.add()
self.add()
self.add()
def add(self):
entry = tk.Entry(self.container)
entry.pack(side="top", fill="x")
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
self.entries.append(entry)
root = tk.Tk()
demo = Demo2(root)
root.mainloop()

Python & tkinter: canvas.lift and canvas.lower on overlapping buttons does not work

I created two overlapping buttons on a canvas, using tkinter and python 3.4:
Now I would like to bring button1 to the front (the button you cannot see right now, because it is under button2)
self.canvas.lift(self.button1)
But for some reason this does not work. Just nothing happens. Also lowering button2 has no effect. Can you tell me why?
import tkinter as tk
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.canvas = tk.Canvas(self, width=400, height=400, background="bisque")
self.canvas.create_text(50,10, anchor="nw", text="Click to lift button1")
self.canvas.grid(row=0, column=0, sticky="nsew")
self.canvas.bind("<ButtonPress-1>", self.click_on_canvas)
self.button1 = tk.Button(self.canvas, text="button1")
self.button2 = tk.Button(self.canvas, text="button2")
x = 40
self.canvas.create_window(x, x, window=self.button1)
self.canvas.create_window(x+5, x+5, window=self.button2)
def click_on_canvas(self, event):
print("lifting", self.button1)
self.canvas.lift(self.button1)
self.canvas.lower(self.button2)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Instead of calling lift() on the canvas, you need to call it on the widget instance directly:
def click_on_canvas(self, event):
print("lifting", self.button1)
self.button1.lift()
self.button2.lower() # Not necessary to both lift and lower
This is only true for widgets displayed via a window on your canvas.
If you were to draw objects such as lines or rectangles, you would use lift() or tag_raise() on the canvas instance as you were doing before.

Tkinter Python managing scrollbar inside the listbox in a canvas

I got the code below which contain a listbox inside a canvas, I'm trying to place the scrollbar only inside the listbox but what I've got is the scrollbar always set on the whole form. Can you pls help me to put the scrollbar inside the listbox only and not on the whole canvas.
def __init__(self):
self.form = Tk()
self.form.title("Admin");
self.form.geometry('608x620+400+50')
self.form.option_add("*font",("Monotype Corsiva",13))
self.form.overrideredirect(True)
self.form.resizable(width=FALSE, height=FALSE)
self.canvas = Canvas(self.form)
self.canvas.pack(expand=YES,fill=BOTH)
self.photo = PhotoImage(file='src/back3.gif')
self.canvas.create_image(-7,-8,image=self.photo,anchor=NW)
self.scrollbar = tk.Scrollbar(self.canvas, orient="vertical")
self.lb = tk.Listbox(self.canvas, width=78, height=15,yscrollcommand=self.scrollbar.set)
self.lb.place(relx=0.04,rely=0.17)
self.scrollbar.config(command=self.lb.yview)
self.scrollbar.pack(side="right", fill="none")
I suggest you to create a Frame to wrap the Listbox and the Scrollbar. Here it is a class I wrote to do the same thing, it does not fit exactly with your code - I use grid() instead of pack() -, but you get the idea.
class ScrollableListbox(tk.Listbox):
def __init__(self, master, *arg, **key):
self.frame = tk.Frame(master)
self.yscroll = tk.Scrollbar(self.frame, orient=tk.VERTICAL)
tk.Listbox.__init__(self, self.frame, yscrollcommand=self.yscroll.set, *arg, **key)
self.yscroll['command'] = self.yview
def grid(self, *arg, **key):
self.frame.grid(*arg, **key)
tk.Listbox.grid(self, row=0, column=0, sticky='nswe')
self.yscroll.grid(row=0, column=1, sticky='ns')

Categories

Resources