tkinter put scrollbar on canvas at bottom position - python

Question on scrollbar positions on my tkinter canvas. I have a frame with 3 canvas widgets. Courtesy to this post for the idea. I added a horizontal scrollbar and each canvas has a 50+ column 500+ row pandas dataframe. The load is not very fast but that isn't an objective.
New rows will be added to the bottom of each dataframe. This new row needs a validation. So instead of scrolling down every time, it would be great if the scrollbar / or canvas shows the bottom part.
See below the code where the 3x canvas and 3x scrollbars (x+y) are defined.
def createBox(window):
list_ = ['df1', 'df2', 'df3'] # 3 dataframes
for i in range(3):
mybox = LabelFrame(window, padx=5, pady=4)
mybox.grid(row=i, column=0)
createWindow(mybox, list_[i], i)
def createWindow(box, lt_actual, i):
canvas = Canvas(box, borderwidth=0)
frame = Frame(canvas)
vsbY = Scrollbar(box, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=vsbY.set, width=1200, heigh=200)
vsbY.pack(side="right", fill="y")
vsbX = Scrollbar(box, orient="horizontal", command=canvas.xview)
canvas.configure(xscrollcommand=vsbX.set, width=1200, heigh=200)
vsbX.pack(side="bottom", fill="x")
#canvas.yview_moveto(1) - no effect
#canvas.yview_moveto(1.0) - no effect
canvas.pack(side="left", fill="both", expand=True)
canvas.create_window((4,4), window=frame, anchor="nw", tags="frame")
# be sure that we call OnFrameConfigure on the right canvas
frame.bind("<Configure>", lambda event, canvas=canvas: OnFrameConfigure(canvas))
I read on this forum and on some info (effbot) pages that i should use the moveto() / yview_moveto() command option but so far this doesn't seem to work.
Question 1. Should I put the y-scrollbar to the bottom or should i put the canvas view to the bottom.
Question 2. Can you provide some guidance on how to use the moveto or should I follow a different approach?
Thanks so much!

The yview_moveto method of the canvas is indeed the right function to use. Its argument is the fraction of the total height of the canvas that you want off-screen. So using 0 as argument shows the top of the canvas and 1, the bottom.
Reference:
Tkinter.Canvas.yview_moveto-method
Adjusts the canvas to the given Scroll offset.
Offset '0.0' is the beginning of the scrollregion, '1.0' the end.
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root)
canvas.grid(row=0, column=0)
canvas.create_oval(0, 0, 20, 20, fill='red')
canvas.create_oval(0, 800, 20, 820, fill='blue')
ys = tk.Scrollbar(root, orient='vertical', command=canvas.yview)
ys.grid(row=0, column=1, sticky='ns')
# configure scrolling
canvas.configure(yscrollcommand=ys.set, scrollregion=canvas.bbox('all'))
# show bottom of canvas
canvas.yview_moveto('1.0')
root.mainloop()
By the way, I don't see any difference between putting the y-scrollbar to the bottom or putting the canvas view to the bottom because the two are linked. But I guessed you wanted to know whether to do it using a method of the scrollbar or of the canvas, and I gave the answer above.

I have found that it is necessary to use idle_tasks before moving the scroll tab:
self.canvas.update_idletasks()
self.canvas.yview_moveto(0)

Related

Python tkinter window resizes, but widgets never change or move

Python beginner. I placed a scrollbar widget in window and that works, but no matter what I do I can't get the scrollbox widget to change size. Could go with a larger scrollbox or for it to resize when the window resizes, but can't figure out how to force either to happen. Tried lots of different solutions, but feels like the grid and canvas are defaulting to a size and can't figure out how to change that. Help would be appreciated. Code is below:
import tkinter as tk
from tkinter import ttk
import os
import subprocess
class Scrollable(tk.Frame):
"""
Make a frame scrollable with scrollbar on the right.
After adding or removing widgets to the scrollable frame,
call the update() method to refresh the scrollable area.
"""
def __init__(self, frame, width=16):
scrollbar = tk.Scrollbar(frame, width=width)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=True)
self.canvas = tk.Canvas(frame, yscrollcommand=scrollbar.set)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=self.canvas.yview)
self.canvas.bind('<Configure>', self.__fill_canvas)
# base class initialization
tk.Frame.__init__(self, frame)
# assign this obj (the inner frame) to the windows item of the canvas
self.windows_item = self.canvas.create_window(0,0, window=self, anchor=tk.NW)
def __fill_canvas(self, event):
"Enlarge the windows item to the canvas width"
canvas_width = event.width
self.canvas.itemconfig(self.windows_item, width = canvas_width)
def update(self):
"Update the canvas and the scrollregion"
self.update_idletasks()
self.canvas.config(scrollregion=self.canvas.bbox(self.windows_item))
root = tk.Tk()
root.title("application")
root.geometry('750x800')
dbEnvs = ['a','b']
x = 1
header = ttk.Frame(root)
body = ttk.Frame(root)
footer = ttk.Frame(root)
header.pack(side = "top")
body.pack()
footer.pack(side = "top")
#setup Environment selection
envLabel = tk.Label(header, text="Environment:")
envLabel.grid(row=0,column=0,sticky='nw')
dbselection = tk.StringVar()
scrollable_body = Scrollable(body, width=20)
x = 1
for row in range(50):
checkboxVar = tk.IntVar()
checkbox = ttk.Checkbutton(scrollable_body, text=row, variable=checkboxVar)
checkbox.var = checkboxVar # SAVE VARIABLE
checkbox.grid(row=x, column=1, sticky='w')
x += 1
scrollable_body.update()
#setup buttons on the bottom
pullBtn = tk.Button(footer, text='Pull')
pullBtn.grid(row=x, column=2, sticky='ew')
buildBtn = tk.Button(footer, text='Build')
buildBtn.grid(row=x, column=3, sticky='ew')
compBtn = tk.Button(footer, text='Compare')
compBtn.grid(row=x, column=4, sticky='ew')
root.mainloop()
have tried anchoring, changing the window base size and multiple other things (8 or 19 different items, plus reading lots of posts), but they normally involve packing and since I used grids that normally and ends with more frustration and nothing changed.
If you want the whole scrollbox to expand to fill the body frame, you must instruct pack to do that using the expand and fill options:
body.pack(side="top", fill="both", expand=True)
Another problem is that you're setting expand to True for the scrollbar. That's probably not something you want to do since it means the skinny scrollbar will be allocated more space than is needed. So, remove that attribute or set it to False.
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=False)
tip: when debugging layout problems, the problems are easier to visualize when you temporarily give each widget a unique color. For example, set the canvas to one color, body to another, the instance of Scrollable to another, etc. This will let you see which parts are visible, which are growing or shrinking, which are inside the borders of others, etc.

Python Tkinter Scrollbar panning upwards instead of downwards also alignment issue while using Scrollbar

I am using TKinter to build a GUI for my python application.
I want to add a scrollbar to the main screen because I have a lot of data to display but very less space.
Currently I use the lines:
root=Tk()
root.state("zoomed")
#...rest of the code... (Relevant parts mentioned below:)
root.mainloop()
to take maximum advantage of available screen space but even that is not cutting it for me.
Here is John Elder(CEO of Codemy.com)'s code that I use to add a scrollbar:
# Create A Main Frame
main_frame = Frame(root)
main_frame.pack(fill=BOTH, expand=1)
# Create A Canvas
my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
# Add A Scrollbar To The Canvas
my_scrollbar = ttk.Scrollbar(main_frame, orient=VERTICAL, command=my_canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
# Configure The Canvas
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion = my_canvas.bbox("all")))
def _on_mouse_wheel(event):
my_canvas.yview_scroll(-1 * int((event.delta / 100)), "units")
my_canvas.bind_all("<MouseWheel>", _on_mouse_wheel)
# Create ANOTHER Frame INSIDE the Canvas
encompasser = Frame(my_canvas, bg=mainbg)
# Add that New frame To a Window In The Canvas
my_canvas.create_window((0,0), window=encompasser, anchor="n")
#Adding the scroll bar part ends here...
encompasser=Frame(my_canvas,bg=mainbg, borderwidth=0)
encompasser.pack(fill=BOTH, expand=1)
#second_frame = LabelFrame(my_canvas, borderwidth=0, bg=mainbg).pack()
header=LabelFrame(encompasser,bg=mainbg, borderwidth=0)
header.pack()
greet=Label(header,font="Arial 20",text="Prescription Generator V4.0.0", bg=mainbg, fg="black").pack(pady=10)
utilitiesF=LabelFrame(header,bg=mainbg, borderwidth=0)
utilitiesF.pack()
settingLF=LabelFrame(utilitiesF,bg=mainbg, borderwidth=0)
megaF=LabelFrame(encompasser,bg=mainbg, borderwidth=0)
megaF.pack()
dateF=LabelFrame(utilitiesF, padx=10, pady=10, bg=mainbg, borderwidth=0)
dateF.grid(row=0, column=1)
date=Label(dateF,font="Arial 14",text="Date:", bg=mainbg).grid(row=0,column=0, sticky=E)
edate=Entry(dateF, width=9,borderwidth=0, font="Arial 14")
edate.insert(0,today)
edate.grid(row=0, column=1, padx=5)
my_tree_frame=LabelFrame(header, padx=10, pady=10, bg=subbg, borderwidth=0)
my_tree_frame.pack()
Dormant scrollbar added
After Adding the scroll bar to the canvas and opening the canvas in a frame, I add things to the frame called "encompasser".
Problem: when I click on the button: Show Database, The following Treeview opens up:
Treeview opens up, moving the rest of my project down
And when the rest of my project moves down, the scroll bar just does not scroll down.
Scroll Bar stuck and not working on moving it up or down
What am I doing wrong here?
I know one solution is to open the treeview widget as a Toplevel() but I want it to be opened within the same page. Please answer my question, thank you!
One more thing: the entire alignment gets messed up when I try to pack that frame which comprises of all my widgets inside the encompasser. The whole project shifts towards the left hand side and aesthetically it does not look nice.
Showing what happened when I removed the frame called "encompasser"
First, you have created another frame using the same variable name encompasser:
encompasser=Frame(my_canvas,bg=mainbg, borderwidth=0)
encompasser.pack(fill=BOTH, expand=1)
The above two lines should be removed.
Second, you should update the scrollregion when the internal frame encompasser is resized, not my_canvas.
Remove the line:
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion = my_canvas.bbox("all")))
and add the below line after creating encompasser:
encompasser.bind('<Configure>', lambda e: my_canvas.configure(scrollregion=my_canvas.bbox("all")))

tkinter: dynamically add checkboxes to grid and read values individually

I would like to dynamically add checkboxes to a tkinter grid and then have the user check some of them and afterwards see, which checkboxes were checked.
Here's the code:
top = tkinter.Tk()
var2 = tkinter.IntVar()
main()
top.mainloop()
# in main
for feat in feats:
counter += 1
tkinter.Checkbutton(top, text=str(merkmal) + " ist " +
str(label), command=lambda: testResult(),
variable=var2).grid(row=counter, sticky="W")
# callback
def testResult():
print(var2)
Two problems:
a) I need a vertical and horizontal scrollbar on my grid
I have looked at other posts and seen that I need to add a canvas and then add the scrollbar to the canvas but I havent gotten it to work. Here's my attempt:
w = tkinter.Canvas(top)
scrollbar = tkinter.Scrollbar(top, orient="vertical",command=w.yview)
scrollbar.grid(row=0,column=1,sticky='ns')
w.config(yscrollcommand=scrollbar.set, xscrollcommand=scrollbar.set)
w.grid(row=0,column=0,sticky='nsew')
and then addind the checkboxes to w instead of top. The vertical scrollbar shows, but I cant scroll.
b) When I check one of the boxes, all of them get checked. How can I make it so that each checkbox can be checked individually?
I am new to tkinter and really don't have to learn too much into it, just need this to work somehow.
Thanks for any help!
EDIT
canvas = tkinter.Canvas(top, bg='#FFFF12', width=300, height=900, scrollregion=(0, 0, 900, 900))
hbar = tkinter.Scrollbar(top, orient="horizontal")
hbar.pack(side="bottom", fill="x")
hbar.config(command=canvas.xview)
vbar = tkinter.Scrollbar(top, orient="vertical")
vbar.pack(side="right", fill="y")
vbar.config(command=canvas.yview)
canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set)
canvas.pack(side="left", expand=True, fill="both")
It shows the scrollbar, the vertical scrollbar moves a bit, but the content doesnt move. When I attach everything to a frame, then the scrollbar doesnt move either.

Python Tkinter - multiple listboxes and scrollbars

having issues with getting the scrollbars set correctly. The following code renders 2 listboxes, one on top of the other, and 2 scrollbars. However, scrollbar spans the entire height of both boxes, and scrollbar2 is only the 2nd listbox (lb2). I need scrollbar and lb to be the same height, and scrollbar2 and lb2 to be the same height. Here is a screenshot of what I currently have http://tinyurl.com/mxo9llb
frame = Frame(app,bd=2,relief=SUNKEN)
scrollbar = Scrollbar(frame, orient="vertical")
lb = Listbox(frame, width=30, height=10, yscrollcommand=scrollbar.set)
scrollbar.config(command=lb.yview)
scrollbar.pack(side="right", fill="y")
lb.pack(side="top",fill="both", expand=True)
scrollbar2 = Scrollbar(frame, orient="vertical")
lb2 = Listbox(frame, width=30, height=10, yscrollcommand=scrollbar2.set)
scrollbar2.config(command=lb2.yview)
scrollbar2.pack(side="right",fill="y")
lb2.pack(side="top",fill="both", expand=True)
for item in ad_members:
lb.insert(END, item)
for item in ad_members:
lb2.insert(END, item)
frame.pack(side='right',padx=15)
For such a layout, you're better off using grid rather than pack. You can't do what you want using pack unless you add some extra frames to help with the layout.
Using pack, you need to create two additional frames: one for the top half and one for the bottom half. Then, within the frame you can put each scrollbar on the right and the listbox on the left. Finally, pack the frames on top of each other.
Using grid, you would put each listbox/scrollbar pair on a different row, with the listbox in column zero and the scrollbar in column one.

table display in tkinter with scroll bars

I'm writing a small GUI in python3/tkinter. What I want to do is generate a window with a table of data (like a spreadsheet) and have the table be scrollable both horizontally and vertically. Right now I'm just trying to display data, so I'm using a grid of Labels. The data display works fine but I can't get the scroll bars to act correctly. Here is the relevant part of my code; the class that this is in inherits from tk.Toplevel
frame = self.frame = tk.Frame(self)
self.frame.grid(row=1, columnspan=2, padx=2, pady=2, sticky=tk.N+tk.E+tk.S+tk.W)
self.text_area = tk.Canvas(self.frame, background="black", width=400, height=500, scrollregion=(0,0,1200,800))
self.hscroll = tk.Scrollbar(self.frame, orient=tk.HORIZONTAL, command=self.text_area.xview)
self.vscroll = tk.Scrollbar(self.frame, orient=tk.VERTICAL, command=self.text_area.yview)
self.text_area['xscrollcommand'] = self.hscroll.set
self.text_area['yscrollcommand'] = self.vscroll.set
self.text_area.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
self.hscroll.grid(row=1, column=0, sticky=tk.E+tk.W)
self.vscroll.grid(row=0, column=1, sticky=tk.N+tk.S)
self._widgets = []
for row in range(rows):
current_row = []
for column in range(columns):
label = tk.Label(self.text_area, text="",
borderwidth=0, width=width)
label.grid(row=row, column=column, sticky="nsew", padx=1, pady=1)
current_row.append(label)
self._widgets.append(current_row)
The table displays OK and the scrollbars appear but are not functional:
Any ideas?
You have a couple of problems. First, you can't use grid to place labels in the canvas and expect them to scroll. When you scroll a canvas, only the widgets added with create_window will scroll. However, you can use grid to put the labels in a frame, and then use create_window to add the frame to the canvas. There are several examples of that technique on this site.
Second, you need to tell the canvas how much of the data in the canvas should be scrollable. You use this by setting the scrollregion attribute of the canvas. There is a method, bbox which can give you a bounding box of all of the data in the canvas. Usually it's used like this:
canvas.configure(scrollregion=canvas.bbox("all"))
An option I have used is the leave the labels static and changed the data when the scrollbar is used. This is easy for vertical scrolling but if the columns are of different widths does not work for horizontal scrolling - it would require resizing the labels.

Categories

Resources