I have created a section of my app where the user is able to scroll through several widgets. My problem is that I can not figure out how to set the scrollable area (a tk.Frame) to resize its width to the parent frame. This is because the frame is placed into a canvas window, so grid() or pack() is never called, and therefore I can not use sticky=tk.EW. I should note that setting the canvas anchor=tk.CENTER also does not work.
Here's my code. self.workspace_frame refers to the parent frame:
# Initialize scrollbar
scrollbar = tk.Scrollbar(self.workspace_frame)
scrollbar.configure(
orient=tk.VERTICAL,
command=canvas.yview
)
# Initialize contents frame
contents = tk.Frame(canvas)
contents.configure(
#background=self.receded_color
background='red'
)
# Prevent resizing problems
contents.bind(
'<Configure>',
lambda event: canvas.configure(
scrollregion=canvas.bbox(tk.ALL)
)
)
# Configure canvas
canvas.create_window((0, 0), window=contents)
canvas.configure(
yscrollcommand=scrollbar.set,
background=self.receded_color,
highlightthickness=0
)
# Add elements to grid
canvas.grid(column=0, row=0, sticky=tk.NS)
scrollbar.grid(column=1, row=0, sticky=tk.NS)
Here is what my scrollbar and scroll area looks like. The red indicates the frame that I'm struggling to center: My App
Thanks.
If you want the red frame (contents) to have same width as its parent (canvas), you need to resize its width after its parent is resized:
canvas.bind('<Configure>', lambda e: contents.config(width=e.width))
Related
I'm writing an application in tkinter which has the following structure:
Notebook
Tab (frame on the notebook)
Canvas (on the frame)
SecondFrame (on the canvas) ← this is where I want to pack my buttons
MyButtons (on second frame)
While scrollbar is linked as expected to my canvas.
Lets say I have 20 buttons when running the program and by pressing any of them I want to destroy the last button
This is my code:
from tkinter import *
from tkinter.ttk import *
root = Tk()
root.geometry('300x300')
root.title('Update Scrollbar')
notebook = Notebook(root)
tab1 = Frame(notebook)
notebook.add(tab1, text='Home')
notebook.pack(expand=1, fill='both')
canvas = Canvas(tab1)
second_frame = Frame(canvas)
scrollbar = Scrollbar(tab1, orient='vertical', command=canvas.yview)
canvas.create_window((0, 0), window=second_frame, anchor='center')
scrollbar.pack(side='right', fill='y')
canvas.config(yscrollcommand=scrollbar.set)
canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox('all')))
canvas.pack()
# With this function I destroy the last button:
def delete_buttons():
list(second_frame.winfo_children())[-1].destroy()
second_frame.update_idletasks()
# Here I pack my buttons all with a command 'delete_buttons'
for i in range(20):
Button(second_frame, text='Button' + str(i), command=delete_buttons).pack()
root.mainloop()
However when clicking on a button, the last button is destroyed but an ugly space is created in the top and bottom of the window. (Because of a missing button that took space before I suppose.)
The space is appearing because of the anchor= option you're using to create the Canvas window object:
canvas.create_window((0, 0), window=second_frame, anchor='center')
Which causes the contents to be re-centered every time one of the Buttons is deleted.
To prevent some of the extra space resulting from the deletion from getting distributed to the top of the Canvas, simply anchor the window object to its NorthWest (upper left) corner like this:
canvas.create_window((0, 0), window=second_frame, anchor='nw')
Also note that second_frame.update_idletasks() call in the delete_buttons() function isn't necessary.
i want to make my chatroom app gui resizeable.
i have a canvas and on it a msg_frame where all the messages will be put in.
the canvas is set on the root with place() so it stays relative to the root window size.
i want to make the msg_frame resize relative to the canvas (or root) also.
so when i resize the root window, the messages wont show up like this (blue is msg_frame):
but stick to the right side (close to the scrollbar).
this is my code (removed styling for readability):
root = tk.Tk()
root.title("Chatroom")
root.geometry("1200x800")
chat_canvas = tk.Canvas(root, height=580, width=1160)
msg_frame = tk.Frame(chat_canvas, bg="blue", height=550, width=1160)
# scrollbar
canvas_sb = tk.Scrollbar(top_frame, orient='vertical', command=chat_canvas.yview)
chat_canvas.configure(yscrollcommand=canvas_sb.set)
# placing the scrollbar and canvas
chat_canvas.place(relwidth=0.98, relheight=1)
canvas_sb.place(relx=0.985, relheight=1)
create msg_frame window on canvas
chat_canvas.create_window((0, 0), window=msg_frame, anchor='nw', width=chat_canvas.winfo_reqwidth())
# resize canvas to fit to frame and update its scrollregion
def on_msg_frame_configure(event):
# set canvas size as new 'stretched' frame size
chat_canvas.configure(height=msg_frame.winfo_reqheight())
chat_canvas.configure(scrollregion=chat_canvas.bbox('all'))
# my (not working) attempt to resize the blue msg_frame
def on_top_frame_configure(event):
msg_frame.configure(width=top_frame.winfo_reqwidth(), height=top_frame.winfo_reqheight())
# binds to resize widgets when root size changes
msg_frame.bind(sequence='<Configure>', func=on_msg_frame_configure)
top_frame.bind(sequence='<Configure>', func=on_top_frame_configure) # <-- not working
after lots of tries i managed to get what i wanted. this is what i've done:
when creating a window, assign it a variable
canvas_frame = chat_canvas.create_window((0, 0), window=msg_frame, anchor='nw', tags="msg_frame")
then bind the widgets as follows, with these configure functions:
def on_chat_canvas_configure(event):
# set canvas size as new 'stretched' frame size
chat_canvas.itemconfig(canvas_frame, width=event.width)
canvas_scrollbar.pack_configure(side='right', fill='y')
chat_canvas.pack_configure(side='right', fill='both', expand=True)
def on_msg_frame_configure(event):
print('on msg frame')
chat_canvas.configure(scrollregion=chat_canvas.bbox('msg_frame'))
# binds
chat_canvas.bind('<Configure>', lambda e: on_chat_canvas_configure(e))
msg_frame.bind('<Configure>', lambda e: on_msg_frame_configure(e))
when the root window size changes, the the canvas' size changes also, and the configure event fires, calling on_chat_canvas_configure() function that changes the width of msg_frame on the canvas, while keeping the scrollbar and canvas' sizes relative to the root window.
when the size of msg_frame changes, the configure event of this widget fires and call on msg_frame_configure() function that simply updates the scrollregion of the canvas.
hope i explaned the logic correctly and clearly.
I am trying to make an application that displays a grid in the middle of the screen surrounded by two bars, a top bar and a bottom bar, which contain buttons for the user to press. These buttons should be able to display no matter where the user scrolls to on the grid and should not be cut off if the window is resized. I am struggling to configure the scrollbar to track the right area and to have the grid fall off the screen when the window is resized. Here is my code so far:
from tkinter import *
def add_row(event):
input_row = Entry(grid_frame, bd=1, text="", bg="white", relief="solid")
input_row.grid(row=grid_frame.rows, sticky=N+S+E+W)
Grid.rowconfigure(grid_frame, grid_frame.rows, weight=1)
grid_frame.rows = grid_frame.rows + 1
class GridFrame(Frame):
rows = 0
def __init__(self, root):
Frame.__init__(self, root, bd=1)
root = Tk(className="Main screen")
root.minsize(408, 80)
# size to quarter of screen
w, h = root.winfo_screenwidth() / 2, root.winfo_screenheight() / 2
root.geometry("%dx%d+0+0" % (w, h))
# grid_frame will resize and bars will not
Grid.rowconfigure(root, 1, weight=1)
Grid.columnconfigure(root, 0, weight=1)
myframe = Frame(root, bd=4, relief="groove")
myframe.grid(row=1, sticky=N + W + S + E)
canvas = Canvas(myframe)
grid_frame = GridFrame(canvas)
grid_frame.pack(fill=BOTH, expand=True)
grid_frame.bind("<Button-1>", add_row)
scrollbar = Scrollbar(myframe, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side=RIGHT, fill=Y)
canvas.pack(side=LEFT, fill=BOTH, expand=True)
topBar = Frame(root, grid_frame)
label = Label(topBar, text="Top Text")
label.pack()
topBar.grid(row=0, sticky=W+N+E+S)
bottomFrame = Frame(root, grid_frame)
label = Label(bottomFrame, text="Bottom Text")
label.pack()
bottomFrame.grid(row=2, sticky=E+S+W)
mainloop()
The scrollregion I want to track is the myframe/canvas/grid_frame combination I read to use from this post. The current functionality is that the scrollbar is never in an "active" state and rows added to the grid merely shrink the grid for it to fit within the display. To add a new row, click within the grid_frame region. Any help would be greatly appreciated! Here are some images of the current UI:
UI display with only a few rows
UI display with many more rows
There are two major problems with your code.
First, for the canvas to be able to scroll the inner frame, the inner frame must be a canvas object created with create_window. You're adding it to the canvas with pack, which means the canvas cannot scroll it.
To fix that, use create_window instead of pack:
canvas.create_window(0, 0, anchor="nw", window=grid_frame)
Second, you must reset the scrollregion attribute whenever the contents inside the canvas change. Normally this is done in a <Configure> event handler on the frame, but you can just as easily call it in your add_row function.
For example, add the following line to the end of add_row:
canvas.configure(scrollregion=canvas.bbox("all"))
With those two changes, the scrollbars will start to work as soon as the inner frame is taller than the canvas.
The above solves the problem of the inner window being able to scroll when you add items. In the specific example of this test program, you also have the problem that your binding is on the frame. At startup the frame has a size of 1x1 so it's a bit hard to click on. Moving the binding to the canvas will make this specific demo program work better.
I'm trying to do a scrollbar on the right side of the canvas ( the window ), but the scrollbar won't even show when plotting. What am I doing wrong?
from tkinter import *
# create canvas
root = Tk()
root.title("Aktieköp")
root.configure(background="white")
frame=Frame(root, width=1100, height=1000)
frame.grid(row=0, column=0)
canvas=Canvas(frame,bg="white",width=1100,height=1000)
# my photo
photo = PhotoImage(file="aktier.gif")
label0 = Label(frame, image = photo, bg="white"). grid(row=0, column=0)
# create scrollbar
scrollbar=Scrollbar(frame,orient=VERTICAL)
scrollbar.pack(side=RIGHT,fill=Y)
scrollbar.config(command=canvas.yview)
frame.config(width=1100,height=1000)
frame.config(yscrollcommand=scrollbar.set)
frame.pack(side=RIGHT,expand=True,fill=Y)
root.mainloop()
You use the layout managers wrong:
Do not use grid() and pack() to layout children of a widget:
frame.grid() and frame.pack() ==> root now uses two layout managers and frame needs to be mapped by two managers
label0.grid() and scrollbar.pack() ==> frame uses two layout managers
canvas is not mapped by grid or pack at all, so how you can see and draw on it I don't know.
I am trying to make a frame scrollable, and the only way I found to do this is making a scrollable canvas and adding a frame to it. This would work fine, if it worked for me.
I am able to create a scrollable canvas that works fine, but I can't seem to properly add a frame inside of it:
self.title = Label(root, text="Brnr", font=("Helvetica", 50), anchor = W, pady = 40, padx = 50)
self.title.pack (anchor = NW)
#creates title widget for title
self.frame = Frame(screen, bd =1)
self.frame.pack(fill = BOTH)
#Creates frame widget under which all other widgets will be kept
self.canvas = Canvas(self.frame, bd=1,scrollregion=(0,0, 1000, 1000), height = 600)
#creates canvas so that screen can be scrollable
self.scrollbar = Scrollbar(self.frame, command=self.canvas.yview)
#creates scrollbar
self.canvas.config(yscrollcommand=self.scrollbar.set)
#connects the scrollbar to the canvas
self.scrollbar.pack(side=RIGHT, fill=Y)
self.canvas.pack(expand=YES, fill=BOTH)
#packs the scrollbar and canvas so that they fill the remainder of the screen
self.frameC = Frame(bg = "red")
self.canvas.create_window(0,0, anchor = NW, window = self.frameC, width = 200, height = 200)
#creates window on the scrollable area to add other widgets
self.frameC.pack()
self.groupRec = LabelFrame(self.frameC, text ="Recommendations:", font=("Helvetica", 20))
self.groupRec.pack()
self.signupButton = Button(self.groupRec, text="Sign Up", width=10)
self.signupButton.pack(side=RIGHT)
#creates button to submit login
This gives me a scrollable, but empty, canvas, with none of the labelframe/button appearing.
By default, when you add a window to a canvas, the center of the window will be at the coordinates you give. Thus, the center of your frame will be at 0,0 which is the upper-left corner of the canvas. You can't see the widgets because they are outside the borders of the canvas.
The solution is to include anchor="nw" in the call to create_window, which will place the upper-left corner of your frame in the upper left corner of your canvas.
Don't forget to set the scroll region of the canvas to match the size of your frame. The easiest way to do that is with the command self.canvas.config(scrollregion=self.canvas.bbox("all")). You'll probably also need to add a binding to <Configure> on the canvas so that you can resize the inner frame when the user resizes the window. That's not always necessary, it depends a bit on exactly what you are trying to accomplish.
Here's a pro tip: to debug problems like this it's really helpful to temporarily give your frame and canvas different colors to more easily visualize what is happening.
Don't re-invent the wheel. Install Pmw (Python meta-widgets), assuming you are using Tkinter, http://pmw.sourceforge.net/ and use Pmw.ScrolledFrame.