I am using the following code to populate Entry widget on tkinter Frame:
import tkinter as tk
def populate(frame):
'''Put in some fake data'''
for row in range(100):
tk.Label(frame, text="%s" % row, width=3, borderwidth="1", relief="solid").grid(row=row, column=0)
tk.Entry(frame, width = 50).grid(row=row, column=1)
def onFrameConfigure(canvas):
'''Reset the scroll region to encompass the inner frame'''
canvas.configure(scrollregion=canvas.bbox("all"))
root = tk.Tk()
canvas = tk.Canvas(root, borderwidth=0)
frame = tk.Frame(canvas)
vsb = tk.Scrollbar(root, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=vsb.set)
vsb.pack(side="right", fill="y")
canvas.pack(side="left", fill="both", expand=True)
canvas.create_window((0,0), window=frame, anchor="nw")
frame.bind("<Configure>", lambda event, canvas=canvas: onFrameConfigure(canvas))
populate(frame)
root.mainloop()
Issue which I am facing in the above code is that when I am resizing the tkinter main window the Entry widget is not resizing itself automatically according to change in window size.
Can someone please help me out in solving this.
If you want to resize the entries when the tkinter window is resized, you need to:
resize frame when canvas is resized
def on_canvas_resized(event):
canvas.itemconfig('frame', width=event.width)
...
canvas.bind('<Configure>', on_canvas_resized)
...
canvas.create_window((0,0), window=frame, anchor="nw", tag='frame') # added tag
make Entry to fill the available space horizontally
def populate(frame):
'''Put in some fake data'''
for row in range(100):
tk.Label(frame, text="%s" % row, width=3, borderwidth="1", relief="solid").grid(row=row, column=0)
tk.Entry(frame, width = 50).grid(row=row, column=1, sticky='ew') # added sticky
...
frame.columnconfigure(1, weight=1) # make column 1 to fill available space horizontally
Related
I know how to add a scrollbar on a tkinter window, frame, canvas.
I also know how to do it on a listbox.
Problem is, I have a window that doesn't have any of those, and only use Label and Button:
from tkinter import *
test1 = 100
test2 = 100
test3 = 100
test4 = 100
root = Tk()
root.title("Program")
root.geometry('350x250')
# first group of labels & buttons
label = Label(root, text="test1")
label.grid(row=0, column=0, columnspan=2)
label = Label(root, text=test1)
label.grid(row=1, column=0, columnspan=2)
button = Button(root, text="Up")
button.grid(row=2, column=0)
button = Button(root, text="Down")
button.grid(row=2, column=1)
#
label = Label(root, text="test2")
label.grid(row=3, column=0, columnspan=2)
label = Label(root, text=test2)
label.grid(row=4, column=0, columnspan=2)
button = Button(root, text="Up")
button.grid(row=5, column=0)
button = Button(root, text="Down")
button.grid(row=5, column=1)
#
label = Label(root, text="test3")
label.grid(row=6, column=0, columnspan=2)
label = Label(root, text=test3)
label.grid(row=7, column=0, columnspan=2)
button = Button(root, text="Up")
button.grid(row=8, column=0)
button = Button(root, text="Down")
button.grid(row=8, column=1)
#
label = Label(root, text="test4")
label.grid(row=9, column=0, columnspan=2)
label = Label(root, text=test4)
label.grid(row=10, column=0, columnspan=2)
button = Button(root, text="Up")
button.grid(row=11, column=0)
button = Button(root, text="Down")
button.grid(row=11, column=1)
root.mainloop()
The above has a small window resolution on purpose, because, while it may work in maximizing the window, once there are too many Label's text or Button, then a Scrollbar will be needed. This is intended to test that.
How can I add a scrollbar to the above code?
You need a scrollable frame. See example here: https://gist.github.com/mp035/9f2027c3ef9172264532fcd6262f3b01
And for buttons and labels, instead of using root as parent, use the scrollable frame as parent. For example:
from tkinter import *
c1 = "#999999"
c2 = "#000000"
class ScrollFrame(Frame):
"""
A simple scrollable frame class for tkinter
Source: https://gist.github.com/mp035/9f2027c3ef9172264532fcd6262f3b01
"""
def __init__(self, parent):
# create a frame (self)
super().__init__(parent, background=c1)
# place canvas on self
self.canvas = Canvas(
self, bd=0, bg=c1, relief="flat", highlightthickness=0
)
# place a frame on the canvas, this frame will hold the child widgets
self.viewPort = Frame(self.canvas, background=c1)
self.viewPort.grid_columnconfigure(0, weight=1)
# place a scrollbar on self
self.vsb = Scrollbar(self, orient="vertical", command=self.canvas.yview)
# attach scrollbar action to scroll of canvas
self.canvas.configure(yscrollcommand=self.vsb.set)
# pack scrollbar to right of self
self.vsb.pack(side="right", fill="y")
# pack canvas to left of self and expand to fil
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas_frame = self.canvas.create_window(
(0, 0),
window=self.viewPort,
anchor="nw", # add view port frame to canvas
tags="self.viewPort",
)
# bind an event whenever the size of the viewPort frame changes.
self.viewPort.bind("<Configure>", self.onFrameConfigure)
self.canvas.bind("<Configure>", self.FrameWidth)
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
def _on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def FrameWidth(self, event):
canvas_width = event.width
self.canvas.itemconfig(self.canvas_frame, width=canvas_width)
def onFrameConfigure(self, event):
"""Reset the scroll region to encompass the inner frame"""
# whenever the size of the frame changes, alter the scroll region respectively.
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
tests = [100, 99, 98, 101]
root = Tk()
root.title("Program")
root.geometry('350x250')
scroll_frame = ScrollFrame(root)
for i, testi in enumerate(tests):
# grouping labels and buttons together in a subframe
# so that the row numbers of the labels and buttons
# are always 0 to 2 within the sub-frame
f1 = Frame(scroll_frame.viewPort)
# first group of labels & buttons
label = Label(f1, text=f"test{i}")
label.grid(row=0, column=0, columnspan=2)
label = Label(f1, text=testi)
label.grid(row=1, column=0, columnspan=2)
button = Button(f1, text="Up")
button.grid(row=2, column=0)
button = Button(f1, text="Down")
button.grid(row=2, column=1)
# defining this group to have 2 columns with equal weight
f1.grid_columnconfigure(0, weight=1)
f1.grid_columnconfigure(1, weight=1)
# expand this sub-frame horizontally to it's parent, sticking to West and East of parent frame
f1.grid(sticky="we", ipadx=2)
# adding a separator
Frame(f1, height=1, background=c2).grid(
sticky="we", pady=5, padx=5, columnspan=2
)
# expand the scroll_frame in all 4 directions to fill the parent frame, sticking to West, East, North and South of parent frame
scroll_frame.grid(sticky="wens", row=0, column=0)
# set root frame only has 1 column (filled by scroll_frame)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
I recently stumbled on an easier way to do that (also use less code to do so):
import tkinter as tk
root = tk.Tk()
text = tk.Text(wrap="none")
vsb = tk.Scrollbar(orient="vertical", command=text.yview)
text.configure(yscrollcommand=vsb.set)
vsb.pack(side="right", fill="y")
text.pack(fill="both", expand=True)
for i in range(30):
test1 = "test" + str(i)
test2 = "Button" + str(i)
c = tk.Label(text=test1)
k = tk.Label(text=i)
b = tk.Button(text=test2)
d = tk.Button(text=test2)
text.window_create("end", window=c)
text.insert("end", "\n")
text.window_create("end", window=k)
text.insert("end", "\n")
text.window_create("end", window=b)
text.window_create("end", window=d)
text.insert("end", "\n")
text.configure(state="disabled")
root.mainloop()
This is based on this answer. It doesn't use a canvas.
There is also another similar answer to the one I accepted on this post, here.
As you can see in the picture I want the canvas scrollbar to go all the way right... In a few words I want the canvas to cover all the red bg that the frame 2 has... Any ideas? Here is the code: (reference to the code bellow about the scrollbar code you will find here)
upFrame = Frame(window, bg='lightgray')
upFrame.grid(row=1, column=0, sticky='nesw')
midFrame = Frame(window, bg='red')
midFrame.grid(row=2, column=0, sticky='nesw')
bottomFrame = Frame(window, bg='lightgray')
bottomFrame.grid(row=3, column=0, sticky='nesw')
window.grid_rowconfigure(1, weight = 0)
window.grid_columnconfigure(0, weight = 1)
window.grid_rowconfigure(1, weight = 0)
container = Frame(midFrame)
canvas = Canvas(container)
scrollbar = Scrollbar(container, orient="vertical", command=canvas.yview)
scrollable_frame = Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(
scrollregion=canvas.bbox("all")
)
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
for i in range(50):
Label(scrollable_frame, text="Sample scrolling label").pack()
container.grid(row=2, column=0, sticky='nesw')
canvas.pack(side='left', fill='both', expand=True)
scrollbar.pack(side="right", fill="y")
Also It would be helpfull if you guys could tell me how to make the scrollbar move with the mouse wheel
Next time please add a complete reproducible example including the imports and mainloop so it is easier to follow.
Make your canvas scrollable
(source: tkinter: binding mousewheel to scrollbar):
# bind scrolling to mousewheel
def _scroll_canvas(event):
canvas.yview_scroll(-1*(int(event.delta/100)), "units")
canvas.bind_all("<MouseWheel>", _scroll_canvas)
Stick your scrollbar on the right side
You dont need another frame just for the Canvas, i removed your container Frame and put the Canvas directly into your midFrame. The red background can be removed also.
Same goes for your scrollbar - put directly into your midFrame
For the red portion i created a new sub-frame called red_frame (at the bottom)
If you want the scrollbar to appear on the right side of the screen, use pack with the "right" option, and for all other sub-frames use the "left" option. Everything else is managed by the order in which you pack the widgets.
Full code:
from tkinter import Frame, Canvas, Scrollbar, Tk, Label
window = Tk()
upFrame = Frame(window, bg='lightgray')
upFrame.grid(row=1, column=0, sticky='nesw')
midFrame = Frame(window, bg='red')
midFrame.grid(row=2, column=0, sticky='nesw')
bottomFrame = Frame(window, bg='lightgray')
bottomFrame.grid(row=3, column=0, sticky='nesw')
window.grid_rowconfigure(1, weight = 0)
window.grid_columnconfigure(0, weight = 1)
window.grid_rowconfigure(1, weight = 0)
canvas = Canvas(midFrame)
scrollbar = Scrollbar(midFrame, orient="vertical", command=canvas.yview)
scrollable_frame = Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(
scrollregion=canvas.bbox("all")
)
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
# bind scrolling to mousewheel
def _scroll_canvas(event):
canvas.yview_scroll(-1*(int(event.delta/100)), "units")
canvas.bind_all("<MouseWheel>", _scroll_canvas)
for i in range(50):
Label(scrollable_frame, text="Sample scrolling label").pack()
# here you decide in which order your widgets appear: canvas, red frame and on the right - your scrollbar
canvas.pack(side='left', fill='both')
red_frame = Frame(midFrame, bg="red")
red_frame.pack(side="left")
scrollbar.pack(side="right", fill="y")
window.mainloop()
I am using tk.Canvas's .create_window to place buttons on my canvas, to allow a 'scrolling' effect, where I can cycle through a list of buttons:
example
As seen in the gif, there is a scrollbar on the right which allows scrolling vertically of the canvas, and the canvas created windows of buttons can be cycled through. However, as seen, the buttons overlap the scrollbar. Is it possible for the windows to be set to appear only within the canvas widget? Changing the master of the button does not have an effect:
button = tk.Button(self, image=self.data[i].image, anchor='nw', width=400, height=72, highlightthickness=0, bd=0, relief='flat', bg='#dfdbda', compound='left', text="thing", fg='black', font='System, 10')
buttonwindow = self.canvas.create_window(5, 10, anchor='nw', window=button)
No matter which configuration options are given, the window still seems to pop out of the canvas widget. Why does it do this? Is there a better alternative to create_window, where I can put buttons in a canvas widget?
The solution is simple: don't put the scrollbar inside the canvas. Use the same master for the scrollbar as you do for the canvas. Also, the buttons need to be a child of the canvas.
Since you didn't provide enough code to create a working example, here's a contrived example:
import tkinter as tk
class ButtonScroller(tk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.canvas = tk.Canvas(self, bg="lightgray", bd=2, relief="groove")
self.vsb = tk.Scrollbar(self, 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.buttons = []
self.data = []
#property
def count(self):
return len(self.buttons)
def add_button(self, image, text):
bbox = self.canvas.bbox("all") or (0,0,0,0)
x, y = 4, bbox[3]+5
# use a fake image to keep the example simple...
self.data.append(image)
button = tk.Button(self.canvas, image=image, anchor='nw', width=400, height=72,
highlightthickness=0, bd=0, relief='flat', bg='#dfdbda',
compound='left', text=text, fg='black', font='System, 10')
self.buttons.append(button)
self.canvas.create_window(x, y, anchor="nw", window=button)
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def add_button():
# create a dummy image to simplify this example
image = tk.PhotoImage(width=64, height=64)
n = buttonframe.count + 1
text = f"Button #{n}"
buttonframe.add_button(image, text)
root = tk.Tk()
buttonframe = ButtonScroller(root, bd=1, relief="raised")
toolbar = tk.Frame(root)
toolbar.pack(side="top", fill="x")
buttonframe.pack(side="top", fill="both", expand=True)
add_button = tk.Button(toolbar, text="Add", command=add_button)
add_button.pack(side="left", padx=2, pady=2)
root.mainloop()
Solution I found (the scrollbar was never inside the canvas):
I put the set the master of the buttons to the canvas, which restricted them purely to that widget.
I am trying to get the ScrollBar to apply only to the LeftFrame.
When I use the Canvas setup the ScrollBar applies to the whole window.
Is there better way to setup a selectable long-list table?
Should I be using a listbox instead?
import tkinter as tk
root = tk.Tk()
root.geometry("1000x600")
# root.resizable(width=False, height=False)
def printname(event, i):
print ("my name is", i)
namelabel.configure(text="test" + str(i))
def onFrameConfigure(canvas):
'''Reset the scroll region to encompass the inner frame'''
canvas.configure(scrollregion=canvas.bbox("all"))
canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
leftFrame = tk.Frame(canvas, background="#ffffff", width=700, height=500)
vsb = tk.Scrollbar(root, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=vsb.set)
vsb.pack(side="left", fill="y")
canvas.pack(side="left", fill="both", expand=True)
canvas.create_window((1,1), window=leftFrame, anchor="ne")
rightFrame = tk.Frame(root, background="#ffffff", width=300)
# vsb = tk.Scrollbar(root, orient="vertical", command=canvas.yview)
# canvas.configure(yscrollcommand=vsb.set)
# vsb.pack(side="right", fill="y")
canvas.pack(side="left", fill="both", expand=True)
canvas.create_window((1,2), window=rightFrame, anchor="nw")
# rightFrame = tk.Frame(root, width=400)
# rightFrame.pack()
leftFrame.bind("<Configure>", lambda event, canvas=canvas: onFrameConfigure(canvas))
textlabel = tk.Label(leftFrame, text="Player List")
textlabel.grid(row=0)
for i in range(50):
if (i % 2):
w = tk.Text(leftFrame, height=1, fg="white", bg="black")
else:
w = tk.Text(leftFrame, height=1, fg="black", bg="white")
w.insert(1.0, "test" + str(i))
w.configure(relief="flat")
w.configure(state="disabled")
w.bind("<Button-1>", lambda event, a=i: printname(event, a))
w.grid(row=7+i)
namelabel = tk.Label(rightFrame, text="Test", fg="white", bg="black")
namelabel.grid(row=1)
root.mainloop()
The short answer is that if you don't want something to scroll, don't put it in the canvas. Scrolling a canvas means every item added to the canvas will scroll. There's no way to avoid that. You can't scroll half of a widget.
Is there better way to setup a selectable long-list table?
Probably, though it depends on the data you're wanting to show. If it's a list of short strings, a Listbox is probably a better choice. If it's blocks of text, a single text widget with some tags and custom bindings might be better than multiple widgets.
I'm making a Tkinter GUI that allows you to query a database and will display results in a scrollable frame. However, when you produce the results the scrollbar will not adjust to match the new size of the frame. How can I get the scrollbar to be able to display all of the results? I've put together a quick and dirty version of the code to demonstrate the problem I'm having.
import tkinter as tk
def Lookup():
list = frame_buttons.grid_slaves()
for l in list:
l.destroy()
for x in range(1000):
tk.Label(frame_buttons, text="test", background="white").grid(row=x)
root = tk.Tk()
root.grid_rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
frame_main = tk.Frame(root, bg="white")
frame_main.grid(row = 0,sticky='news')
frame_input = tk.Frame(frame_main, background = "white")
frame_input.grid(row=0, column=0, pady=(5, 0), sticky='nw')
tk.Button(frame_input, text="Search", fg="black", background = "grey",command=Lookup).grid(row=3, column=0,sticky='nw')
# Create a frame for the canvas with non-zero row&column weights
frame_canvas = tk.Frame(frame_main)
frame_canvas.grid(row=1, column=0, pady=(5, 0), sticky='nw')
frame_canvas.grid_rowconfigure(0, weight=1)
frame_canvas.grid_columnconfigure(0, weight=1)
# Set grid_propagate to False to allow 5-by-5 buttons resizing later
frame_canvas.grid_propagate(False)
# Add a canvas in that frame
canvas = tk.Canvas(frame_canvas, bg="gray")
canvas.grid(row=0, column=0, sticky="news")
# Link a scrollbar to the canvas
vsb = tk.Scrollbar(frame_canvas, orient="vertical", command=canvas.yview)
vsb.grid(row=0, column=1, sticky='ns')
canvas.configure(yscrollcommand=vsb.set)
frame_buttons = tk.Frame(canvas, bg="gray")
canvas.create_window((0, 0), window=frame_buttons, anchor='nw')
for x in range(15):
tk.Label(frame_buttons, text="blah", background = "white").grid(row=x)
frame_buttons.update_idletasks()
frame_canvas.config(width=500, height=100)
canvas.config(scrollregion=canvas.bbox("all"))
root.mainloop()
this initially puts 20 labels in the scroll region, which is enough to activate the scrollbar. Then when you click search it replaces those 20 lables with 1000 test labels. But only the first 20 will be viewable.
You need to reset the scrollregion whenever the frame changes size and the items are redrawn.
The normal way to do that is to bind to the <Configure> event of the frame so that it happens automatically as the frame grows and shrinks.
Example:
def reset_scrollregion(event):
canvas.config(scrollregion=canvas.bbox("all"))
...
frame_buttons.bind("<Configure>", reset_scrollregion)