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()
Related
i have made a scrollbar and it works perfect but the size of the scrollbar doesn't match the size of the canvas. i tryed removing the scrollbar.place but it puts the scrollbar in the upper left corner and reallt short. my question is how do i change the length of the scrollbar. thank you very much.
the code:
canvas = tkinter.Canvas(self.main_window, width=CANVAS_WIDTH, height=CANVAS_HEIGHT)
canvas.grid()
canvas.place(relx=0.5, rely=0.2, anchor=tkinter.N)
scrollbar = tkinter.Scrollbar(self.main_window, orient=tkinter.VERTICAL, command=canvas.yview)
scrollbar.grid(row=3, column=1, sticky=tkinter.N + tkinter.S + tkinter.E)
scrollbar.place(relx=1, rely=0.5, anchor=tkinter.E)
canvas.configure(yscrollcommand=scrollbar.set, scrollregion=canvas.bbox("all"))
frame = tkinter.Frame(canvas)
canvas.create_window((0,0), window=frame, anchor=tkinter.NW)
frame.bind("<Configure>", lambda event: canvas.configure(scrollregion=canvas.bbox("all")))
After making the following changes:
remove canvas.place(...) and scrollbar.place(...)
put canvas at the same row (3) of scrollbar
set the width and height of frame
Then the height of scrollbar will be the same of canvas.
canvas = tkinter.Canvas(self.main_window, width=CANVAS_WIDTH, height=CANVAS_HEIGHT)
canvas.grid(row=3, column=0, sticky='nsew') # put in same row of scrollbar
#canvas.place(relx=0.5, rely=0.2, anchor=tkinter.N)
scrollbar = tkinter.Scrollbar(self.main_window, orient=tkinter.VERTICAL, command=canvas.yview)
scrollbar.grid(row=3, column=1, sticky=tkinter.N + tkinter.S + tkinter.E)
#scrollbar.place(relx=1, rely=0.5, anchor=tkinter.E)
canvas.configure(yscrollcommand=scrollbar.set)
# give frame a size longer then the canvas to enable scrolling
frame = tkinter.Frame(canvas, width=CANVAS_WIDTH, height=CANVAS_HEIGHT*2)
canvas.create_window((0,0), window=frame, anchor=tkinter.NW)
frame.bind("<Configure>", lambda event: canvas.configure(scrollregion=canvas.bbox("all")))
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
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)
I have a canvas with an image I'd like to use for a background. My problem is that when I position the button to the correct place, and I try to scroll down, the button moves with the screen instead of staying where I want it to on the canvas.
frame = Frame(self)
frame.pack()
mapImg = PhotoImage(file='Fo4-pip-map.png')
canvas = Canvas(frame, width=2048, height=2048, scrollregion=(0,0,2048,2048))
canvas.create_image(0,0, image=mapImg, anchor='nw')
canvas.image = mapImg
xscrollbar = Scrollbar(frame, orient=HORIZONTAL)
xscrollbar.pack(side=BOTTOM, fill=X, anchor='s')
yscrollbar = Scrollbar(frame, orient=VERTICAL)
yscrollbar.pack(side=RIGHT, fill=Y, anchor='e')
xscrollbar.config(command=canvas.xview)
yscrollbar.config(command=canvas.yview)
canvas.config(xscrollcommand=xscrollbar.set, yscrollcommand=yscrollbar.set)
canvas.pack(side=LEFT, expand=True, fill=BOTH)
vaultImg = PhotoImage(file='Vault.png')
vaultImg = vaultImg.zoom(5)
vaultImg = vaultImg.subsample(32)
vault111Button = Button(canvas, width=30, height=30, borderwidth=0, image=vaultImg,
command=lambda: controller.show_frame('Vault111'))
vault111Button.image = vaultImg
vault111Button.place(x=150, y=100)
The place geometry manager places children at a fixed offset from their parent - in particular, the scrolling of a canvas doesn't apply to them. Get rid of that last line, and instead use:
canvas.create_window(150, 100, window=vault111Button)