Create a fix button with scrollbar in Tkinter - python

I run an example about scrollbar based here. But I want to add a button (Prev) to frame and when scrollbar moves down, this button still stay there, while other content moves along with scrollbar.
Here is my code:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
self.frame = tk.Frame(self.canvas, background="#ffffff")
self.vsb = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((4,4), window=self.frame, anchor="nw", tags="self.frame")
self.frame.bind("<Configure>", self.onFrameConfigure)
self.populate()
def populate(self):
button = tk.Button(self.frame, text='Prev')
button.grid(row=0, columns=1)
for row in range(1,100):
tk.Label(self.frame, text="%s" % row, width=3, borderwidth="1", relief="solid").grid(row=row, column=0)
t="this is the second column for row %s" %row
tk.Label(self.frame, text=t).grid(row=row, column=1)
def onFrameConfigure(self, event):
'''Reset the scroll region to encompass the inner frame'''
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
root=tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()
Can anyone help me solve this problem? Many thanks!

You could do this with pack() as well but I prefer to use grid()
What you need to do is place the button in root on row 0 and place the class frame on row 1 of root. This should fix your problem.
When using grid() we need to manage the weights of the rows and columns so you get the growing and shrinking behavior when we resize the window. We can do this with rowconfigute() and columnconfigute(). For this program we need to configure the weights of root.
To get the button to stay at the left we can use sticky = "w" inside of grid() to chose the side to stick to.
Take a look at the below code:
import tkinter as tk
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
self.frame = tk.Frame(self.canvas, background="#ffffff")
self.vsb = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.grid(row = 1, column = 1, sticky = "nsew")
self.canvas.grid(row = 1, column = 0, sticky = "nsew")
self.canvas.create_window((4,4), window=self.frame, anchor="nw", tags="self.frame")
self.frame.bind("<Configure>", self.onFrameConfigure)
self.populate()
def populate(self):
button = tk.Button(root, text='Prev')
button.grid(row=0, columns=1, sticky = "w")
for row in range(1,100):
tk.Label(self.frame, text="%s" % row, width=3, borderwidth="1", relief="solid").grid(row=row, column=0)
t="this is the second column for row %s" %row
tk.Label(self.frame, text=t).grid(row=row, column=1)
def onFrameConfigure(self, event):
'''Reset the scroll region to encompass the inner frame'''
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
root=tk.Tk()
root.columnconfigure(0, weight = 1)
root.rowconfigure(1, weight = 1)
Example(root).grid(row = 1)
root.mainloop()

Related

Tkinter labels not filling up in the Y direction after using pack even though fill=tk.Y was added

I have been looking at various sources but to no avail.
In my tkinter window, I have running Labels which start with "start" to 1 and all the way to 100. I have two problems:
My labels are not stretching out in the Y direction even though fill=tk.Y is called (for self.firstlabel.pack and x.pack), as evidenced by the green background. Why is this happening?
I've also tried to get the canvas height by first calling self.update() and then printing out the canvas height using print(self.canvas.winfo_height()). However the canvas height is still 1. Why is this the case?
Thank you all in advance for your answers!
import tkinter as tk
class Test(tk.Tk):
def __init__(self, tasks=None):
super().__init__()
self.title("Test")
# setting up container
container = tk.Frame(self, background="bisque")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
container.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.E, tk.W))
container.grid_columnconfigure(0, weight=1)
container.grid_rowconfigure(0, weight=3)
container.grid_rowconfigure(1, weight=1)
# two frames in container, container1 for labels, container2 for textbox
self.container1 = tk.Frame(container, background="yellow")
self.container2 = tk.Frame(container, background="blue")
self.container1.grid(row=0, sticky=(tk.N, tk.S, tk.E, tk.W))
self.container2.grid(row=1, sticky=(tk.N, tk.S, tk.E, tk.W))
self.canvas = tk.Canvas(self.container1, borderwidth=0, background="green")
self.frameinsidecanvas = tk.Frame(self.canvas, background="pink")
self.canvas.pack(fill="both", expand=True)
self.frameinsidecanvas.pack(fill="both", expand=True)
self.update()
print(self.canvas.winfo_height())
# setup scrollbar
self.horizontalscrollbar = tk.Scrollbar(self.container1, orient="horizontal", command=self.canvas.xview)
self.canvas.configure(xscrollcommand=self.horizontalscrollbar.set)
self.horizontalscrollbar.pack(side="bottom", fill="x")
self.canvas.create_window((0, 100), window=self.frameinsidecanvas, anchor="w")
self.textbox = tk.Text(self.container2, height=1)
self.textbox.pack(fill="both", expand=True)
# creating the instructional label
self.firstlabel = tk.Label(self.frameinsidecanvas, text="Start")
# self.tasks.append(self.firstlabel)
self.firstlabel.pack(side="left", fill=tk.Y, expand=True)
# showing the labels
for labels in range(0,100):
x = tk.Label(self.frameinsidecanvas, text=str(labels))
x.pack(side=tk.LEFT, fill=tk.Y, expand=True)
self.bind("<Configure>", self.on_frame_configure)
def on_frame_configure(self, event=None):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
test = Test()
test.wm_geometry("1100x500")
test.mainloop()
The problem is that at the start of the program the canvas height is equal to 1. After that the tkinter window is rendered it changes the value.
You can simply bind a canvas listner to <Configure> for get when the height changes and after modify the inner fram of consequence.
import tkinter as tk
# You want to display the labels in column or that each label had the height of the canvas?
class Test(tk.Tk):
def __init__(self, tasks=None):
super().__init__()
self.canvas_width = None
self.title("Test")
# setting up container
container = tk.Frame(self, background="bisque")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
container.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.E, tk.W))
container.grid_columnconfigure(0, weight=1)
container.grid_rowconfigure(0, weight=3)
container.grid_rowconfigure(1, weight=1)
# two frames in container, container1 for labels, container2 for textbox
self.container1 = tk.Frame(container, background="yellow")
self.container2 = tk.Frame(container, background="blue")
self.container1.grid(row=0, sticky=(tk.N, tk.S, tk.E, tk.W))
self.container2.grid(row=1, sticky=(tk.N, tk.S, tk.E, tk.W))
self.canvas = tk.Canvas(self.container1, borderwidth=0, background="black")
self.frameinsidecanvas = tk.Frame(self.canvas, background="pink")
self.canvas.pack(fill="both", expand=True)
self.frameinsidecanvas.pack(fill="both", expand=True)
# setup scrollbar
self.horizontalscrollbar = tk.Scrollbar(self.container1, orient="horizontal", command=self.canvas.xview)
self.canvas.configure(xscrollcommand=self.horizontalscrollbar.set)
self.horizontalscrollbar.pack(side="bottom", fill="x")
self.canvas.create_window((0, 100), window=self.frameinsidecanvas, anchor="w")
self.textbox = tk.Text(self.container2, height=1)
self.textbox.pack(fill="both", expand=True)
# creating the instructional label
self.firstlabel = tk.Label(self.frameinsidecanvas, text="start")
# self.tasks.append(self.firstlabel)
self.firstlabel.pack(side="left", fill=tk.Y)
# showing the labels
for label_index in range(0, 10):
x = tk.Label(self.frameinsidecanvas, text=label_index)
x.pack(side=tk.LEFT, fill=tk.Y, expand=True)
self.canvas.bind("<Configure>", self.update_width)
def update_width(self, e):
self.canvas_width = self.canvas.winfo_width()
self.canvas_height = self.canvas.winfo_height()
self.firstlabel.configure(height=self.canvas_height)
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
test = Test()
test.wm_geometry("1100x500")
test.mainloop()
For the first question,
If you want to fill the label widget, use firstlabel.pack(fill = 'x')
if you want to align the text on the left side, use firstlabel = tk.Label(frameinsidecanvas, text="Start", anchor = 'w') I hope you got the idea.
The below code will align the text on the left side and fill the label widget.
self.firstlabel = tk.Label(self.frameinsidecanvas, text="Start", anchor = 'w')
self.firstlabel.pack(fill=tk.Y, expand=True)

Tkinter Scrollbar Widget within frames

I need to add a scrollbar to a frame that shall be alongside other frames within a window. I only want to add the scrollbar to that particular frame but I am having trouble with it. I have read about the need to use tk.Canvas to house the scrollbar widget but I am still getting it wrong.
Picture of output. I would like the scrollbar only on the left frame.
Here is my example code:
import tkinter as tk
class Example:
def __init__(self, master):
self.master = master
self.canvas = tk.Canvas(master, borderwidth=0, background="#ffffff")
self.frame = tk.Frame(self.canvas, background="#ffffff")
self.vsb = tk.Scrollbar(master, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((4,4), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.bind("<Configure>", self.onFrameConfigure)
self.populate()
self.frame1 = tk.Frame(master, background="#ffffff",height=200,width=200)
self.frame1.pack()
def populate(self):
'''Put in some fake data'''
for row in range(100):
tk.Label(self.frame, text="%s" % row, width=3, borderwidth="1",
relief="solid").grid(row=row, column=0)
t="this is the second column for row %s" %row
tk.Label(self.frame, text=t).grid(row=row, column=1)
def onFrameConfigure(self, event):
'''Reset the scroll region to encompass the inner frame'''
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
root=tk.Tk()
app = Example(root)
root.mainloop()
If I were you I would strongly suggest looking up the Grid layout manager for tkinter so you can specify what row and column you want a widget to be on exactly. You can even specify expanding and shrinking behaviour.

Why is the Tkinter Scroll Bar only active when you hover over it?

I am using Tkinter to create a GUI and the scroll bar is only active when I put my mouse over it and scroll. Is there a way so I can scroll using my scroll wheel on the mouse when the mouse is not on the scroll bar and in the middle of the window?
import Tkinter as Tk
from Tkinter import StringVar
class Example(Tk.Frame):
def __init__(self, root):
Tk.Frame.__init__(self, root)
self.canvas = Tk.Canvas(root, width=200, heigh=200, borderwidth=0, background="#ffffff")
self.frame = Tk.Frame(self.canvas,background="#ffffff")
self.vsb = Tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((10,10), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.bind("<Configure>", self.OnFrameConfigure)
self.populate()
def populate(self):
#####Last Name######
labelText=StringVar()
labelDir=Tk.Label(self.frame, text='hello').grid(row=1, column=1)
labelDir
labelText=StringVar()
labelDir1=Tk.Label(self.frame, text='hello').grid(row=2, column=2)
labelDir1
labelText=StringVar()
labelDir2=Tk.Label(self.frame, text='hello').grid(row=1, column=1)
labelDir2
directory=StringVar(None)
self.can_lname =Tk.Entry(self.frame, text=directory,width=100).grid(row=4, column=4)
self.can_lname
def OnFrameConfigure(self, event):
'''Reset the scroll region to encompass the inner frame'''
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
if __name__ == "__main__":
root=Tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()

tkinter : Fill scrollable canvas with frame content

I've searched a lot but found no way to properly fill a scrollable canvas with this content frame. There is always extra space.
In the code below found on SO and adapted to a simple example, I want my DummyFrame components to take all the extra space left of the canvas.
import Tkinter as tk
class DummyFrame(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root, borderwidth=10, background="#ff0000")
tk.Label(self, text="DummyLabel").grid(row=0, column=0, sticky="wnes")
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
self.frame = tk.Frame(self.canvas, borderwidth=10, background="#000000")
self.vsb = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.grid(row=0, column=1, sticky="wnes")
self.canvas.grid(row=0, column=0, sticky="wnes")
self.canvas.create_window((0,0), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.columnconfigure(0, weight=1)
self.frame.rowconfigure(0, weight=1)
self.frame.bind("<Configure>", self.OnFrameConfigure)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.populate()
def populate(self):
for i_row in range(50):
#Component I want to take extra space when resizing window
DummyFrame(self.frame).grid(row = i_row+1, column = 0, sticky="we")
def OnFrameConfigure(self, event):
'''Reset the scroll region to encompass the inner frame'''
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
if __name__ == '__main__':
fenetre = tk.Tk()
fenetre.geometry('{}x{}'.format(800, 600))
Example(fenetre)
fenetre.columnconfigure(0, weight=1)
fenetre.rowconfigure(0, weight=1)
fenetre.mainloop()
This example is very strangely destined. So you have DummyFrames in a Frame in a canvas window which is in a canvas in a Frame. Its difficult to see what is happening here, and which widget dimension control most the resulting dimension the user sees. Nevertheless, if you want to make the DummyFrame as width as your root window (i.e. 800 px) just change the canvas window width:
self.canvas.create_window((0,0), window=self.frame, anchor="nw",
width=800,
tags="self.frame")
Finaly I found something fitted my needs using itemconfigure of the canvas:
import Tkinter as tk
class DummyFrame(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root, borderwidth=10, background="#ff0000")
tk.Label(self, text="DummyLabel").grid(row=0, column=0, sticky="wnes")
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
pass
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.canvas = tk.Canvas(root, borderwidth=0, background="#0000ff")
self.vsb = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.canvas.bind("<Configure>", self.OnCanvasConfigure)
self.vsb.grid(row=0, column=1, sticky="wnes")
self.canvas.grid(row=0, column=0, sticky="wnes")
self.frame = tk.Frame(self.canvas, borderwidth=10, background="#000000")
self.frame.columnconfigure(0, weight=1)
self.frame.rowconfigure(0, weight=1)
self.window = self.canvas.create_window((0,0), window=self.frame, anchor="nw",
tags="self.frame")
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.populate()
def populate(self):
for i_row in range(50):
#Component I want to take extra space when resizing window
DummyFrame(self.frame).grid(row = i_row+1, column = 0, sticky="we")
def OnCanvasConfigure(self, event):
self.canvas.itemconfigure(self.window, width=event.width)
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
if __name__ == '__main__':
fenetre = tk.Tk()
fenetre.geometry('{}x{}'.format(800, 600))
Example(fenetre)
fenetre.columnconfigure(0, weight=1)
fenetre.rowconfigure(0, weight=1)
fenetre.mainloop()

How to move a scroll bar canvas/frame without making the scroll bar grey

I am currently programming something for my Computing coursework and I am trying to incorporate scroll bars into it, at the moment I cannot find any website which really explains why after moving the frame of the window, the scroll bar stops working (becomes grayed out) and after moving the canvas of the window, the canvas becomes really small (not the size I want it to be anyway). Here's some example code:
import Tkinter as tk
from Tkinter import *
master.geometry("800x600+300+150")
class Example(tk.Frame):
def main(self):
self.canvas = tk.Canvas(master, borderwidth=0, background="#ffffff")
self.frame = tk.Frame(self.canvas, background="#ffffff")
self.vsb = tk.Scrollbar(self.canvas, orient="vertical", command=self.canvas.yview)
self.hsb = tk.Scrollbar(self.canvas, orient="horizontal", command=self.canvas.xview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.canvas.configure(xscrollcommand=self.hsb.set)
self.vsb.pack(side="right", fill="y")
self.hsb.pack(side="bottom", fill="x")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((4,4), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.bind("<Configure>", self.OnFrameConfigure)
self.populate()
def populate(self):
vars1 = StringVar()
vars1.set("junk")
for i in range(100):
tk.Entry(self.frame, text=vars1).grid(row=1*i, column=1)
def OnFrameConfigure(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
dave3 = Example()
dave3.main()
button = Button(master, text="Hi").place(x=0, y=0)
mainloop()
if I try to move the frame or canvas things happen that I don't want to, I just literally want the entire frame to move down so that the button does not overlap the canvas Entries and for the scroll bar to still work. If anyone can give a solution to this I would be extremely grateful, cheers.
P.s thanks to Bryan Oakley for getting me this far with his example code.
OK never mind I just figured it out, what I needed to do was create another frame and put the previous frame inside that one. After this I just configured that frame to the size I needed it to be. For any other newbies here's the code:
import Tkinter as tk
from Tkinter import *
master = Tk()
master.geometry("800x600+300+150")
class Example(tk.Frame):
def main(self):
self.framee = tk.Frame(master,background="red")
self.canvas = tk.Canvas(self.framee, borderwidth=0, background="#ffffff")
self.frame = tk.Frame(self.canvas, background="#ffffff")
self.vsb = tk.Scrollbar(self.canvas, orient="vertical", command=self.canvas.yview)
self.hsb = tk.Scrollbar(self.canvas, orient="horizontal", command=self.canvas.xview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.canvas.configure(xscrollcommand=self.hsb.set)
self.vsb.pack(side="right", fill="y")
self.hsb.pack(side="bottom", fill="x")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((4,4), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.bind("<Configure>", self.OnFrameConfigure)
self.framee.place(x=0, y=50, width=500, height=500)
self.populate()
def populate(self):
vars1 = StringVar()
vars1.set("junk")
for i in range(100):
tk.Entry(self.frame, text=vars1).grid(row=1*i, column=1)
def OnFrameConfigure(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
dave3 = Example()
dave3.main()
button = Button(master, text="Hi").place(x=0, y=0)
mainloop()

Categories