Why a button in a scrollable canvas doesn't move? (Python3 + tkinter) - python-3.x

I'm tring to write a program in python3 using the tkinter module. I've created a canvas widget with a y scrollbar, but as I try to add a button in the canvas and scroll the region, the button doesn't move. Here is the code:
# defining the tool bar
class toolBar(object):
def __init__(self, master):
''' creates the toolbar object '''
self.master = master
# creating the toolbarobject
self.toolbar = tk.Canvas(self.master, width=70, height=200, bg="lightgrey")
self.toolbar.grid(row=0, column=1, sticky="nwes", rowspan=2)
self.toolbar.configure(scrollregion=(0, 0, 0, 2000))
b1 = tk.Button(self.toolbar, text="Try")
b1.grid()
# creating the y scrollling
self.scroll_y = tk.Scrollbar(self.parent.master, orient="vertical", command=self.toolbar.yview)
self.scroll_y.grid(row=0, column=0, sticky="ns", rowspan=2)
self.toolbar.configure(yscrollcommand=self.scroll_y.set)
where the master is a tk.Tk() object passed to the class. Do you have any solution for this issue?
P.S.: I have another question: when I run my program, the canvas that contains the button fits the button width, is it possible to place the button whitout changing the canvas width?

The canvas can't scroll items added with pack, place, or grid. It will only scroll widgets added with the create_window method.

Related

The bar in my Scrollbar is missing from my python program using Tkinter GUI

I want to make a window to display data stored inside dictionaries. I have created a canvas, scrollbar and a frame inside the canvas to place my Text widgets to display the information. When I execute my code the layout is just as I want it, but the bar is missing from the scroll bar and I can't figure out why. Here is my code for the GUI portion:
root = Tk()
# create top part
top=Frame(root)
# create a canvas to scroll
holder = Canvas(top)
# create scroll bar
scroll = Scrollbar(top, orient=VERTICAL, command=holder.yview)
# configure scrollbar for canvas
holder.configure(yscrollcommand=scroll.set)
holder.bind('<Configure>', lambda e: holder.configure(scrollregion=holder.bbox("all")))
# create frame for content inside canvas and add it to the canvas
content = Frame(holder, relief=RAISED)
holder.create_window(0, 0, window=content, anchor='nw')
# create bottom part
bottom = Frame(root, relief=SUNKEN)
root.rowconfigure(0, weight=18)
root.rowconfigure(1, weight=1, minsize=50)
root.columnconfigure(0, weight=1)
holder.pack(side=LEFT, fill=BOTH, expand=1)
scroll.pack(side=RIGHT, fill=Y)
content.pack(fill=X)
top.grid(row=0, column=0, sticky='NSEW')
bottom.grid(row=1, column=0, sticky='NSEW')
num=0
for site in sites:
temp=Text(content, height = 5)
temp.configure(state= DISABLED)
temp.pack(fill=X, side=TOP, padx= 5, pady= 5)
siteBoxes.append(temp)
num += 1
root.mainloop()
here is a screenshot of what the result looks like
Program GUI screenshot
there are that many text boxes there just to test the scrollbar, the data display within the Text widgets haven't been written yet.
I found the culprit, it was the line content.pack(fill=X), since I already have it in the window of the canvas this line of code was causing the issue. How ever now the Text widget no longer spans all of the horizontal space, how would I fix this issue while still using .create_window()?

Why is the canvas scrollbar disabled in a tkinter window?

I have a problem with a tkinter application that is being made using python 3. I tried to create a canvas widget, without using a frame container and I tried to add two scrollbar to my canvas, one vertical and another orizontal, here is the code:
class drawCavnas(object):
def __init__(self, master, width=500, height=500):
''' build the canvas object '''
# class attributes
self.master = master
self.cWidth = width
self.cHeight = height
# creating the canvas object
self.canvas = tk.Canvas(self.master, width=self.cWidth, height=self.cHeight, bg="green")
self.canvas.grid(row=0, column=1, sticky="nwes")
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
# creating the scrolling
self.scroll_x = tk.Scrollbar(self.master, orient="horizontal", command=self.canvas.xview)
self.scroll_x.grid(row=1, column=1, sticky="ew")
self.scroll_y = tk.Scrollbar(self.master, orient="vertical", command=self.canvas.yview)
self.scroll_y.grid(row=0, column=2, sticky="ns")
self.canvas.configure(yscrollcommand=self.scroll_y.set, xscrollcommand=self.scroll_x.set)
where the master is the window created using tk.Tk(). The problem here is that once I've ran the program and once the window is created the canvas toolbars' are disabled and I can't interact with them. I tried to resize the window but the canvas resize as well. So do you have a solution?
Why is the canvas scrollbar disabled in a tkinter window?
You must tell what part of the larger virtual canvas you want to be scrollable. You've set the scroll region to be bounded by the objects in the canvas (self.canvas.configure(scrollregion=self.canvas.bbox("all"))). However, you've done that before you draw anything in the canvas so tkinter thinks there is nothing to scroll.
If you have a 500x500 canvas, but want to be able to draw on some larger area, you can hard-code the scrollregion.
For example, this will let you scroll around in a 2000x2000 area:
self.canvas.configure(scrollregion=(0,0,2000,2000)

Adding a Scrollbar to a Tkinter Graph of data which goes off the bottom for the screen [duplicate]

I am using Python to parse entries from a log file, and display the entry contents using Tkinter and so far it's been excellent. The output is a grid of label widgets, but sometimes there are more rows than can be displayed on the screen. I'd like to add a scrollbar, which looks like it should be very easy, but I can't figure it out.
The documentation implies that only the List, Textbox, Canvas and Entry widgets support the scrollbar interface. None of these appear to be suitable for displaying a grid of widgets. It's possible to put arbitrary widgets in a Canvas widget, but you appear to have to use absolute co-ordinates, so I wouldn't be able to use the grid layout manager?
I've tried putting the widget grid into a Frame, but that doesn't seem to support the scrollbar interface, so this doesn't work:
mainframe = Frame(root, yscrollcommand=scrollbar.set)
Can anyone suggest a way round this limitation? I'd hate to have to rewrite in PyQt and increase my executable image size by so much, just to add a scrollbar!
Overview
You can only associate scrollbars with a few widgets, and the root widget and Frame aren't part of that group of widgets.
There are at least a couple of ways to do this. If you need a simple vertical or horizontal group of widgets, you can use a text widget and the window_create method to add widgets. This method is simple, but doesn't allow for a complex layout of the widgets.
A more common general-purpose solution is to create a canvas widget and associate the scrollbars with that widget. Then, into that canvas embed the frame that contains your label widgets. Determine the width/height of the frame and feed that into the canvas scrollregion option so that the scrollregion exactly matches the size of the frame.
Why put the widgets in a frame rather than directly in the canvas? A scrollbar attached to a canvas can only scroll items created with one of the create_ methods. You cannot scroll items added to a canvas with pack, place, or grid. By using a frame, you can use those methods inside the frame, and then call create_window once for the frame.
Drawing the text items directly on the canvas isn't very hard, so you might want to reconsider that approach if the frame-embedded-in-a-canvas solution seems too complex. Since you're creating a grid, the coordinates of each text item is going to be very easy to compute, especially if each row is the same height (which it probably is if you're using a single font).
For drawing directly on the canvas, just figure out the line height of the font you're using (and there are commands for that). Then, each y coordinate is row*(lineheight+spacing). The x coordinate will be a fixed number based on the widest item in each column. If you give everything a tag for the column it is in, you can adjust the x coordinate and width of all items in a column with a single command.
Object-oriented solution
Here's an example of the frame-embedded-in-canvas solution, using an object-oriented approach:
import tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff")
self.frame = tk.Frame(self.canvas, background="#ffffff")
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.canvas.create_window((4,4), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.bind("<Configure>", self.onFrameConfigure)
self.populate()
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"))
if __name__ == "__main__":
root=tk.Tk()
example = Example(root)
example.pack(side="top", fill="both", expand=True)
root.mainloop()
Procedural solution
Here is a solution that doesn't use a class:
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)
t="this is the second column for row %s" %row
tk.Label(frame, text=t).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, background="#ffffff")
frame = tk.Frame(canvas, background="#ffffff")
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((4,4), window=frame, anchor="nw")
frame.bind("<Configure>", lambda event, canvas=canvas: onFrameConfigure(canvas))
populate(frame)
root.mainloop()
Make it scrollable
Use this handy class to make the frame containing your widgets scrollable. Follow these steps:
create the frame
display it (pack, grid, etc)
make it scrollable
add widgets inside it
call the update() method
import tkinter as tk
from tkinter import ttk
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=False)
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))
Usage example
root = tk.Tk()
header = ttk.Frame(root)
body = ttk.Frame(root)
footer = ttk.Frame(root)
header.pack()
body.pack()
footer.pack()
ttk.Label(header, text="The header").pack()
ttk.Label(footer, text="The Footer").pack()
scrollable_body = Scrollable(body, width=32)
for i in range(30):
ttk.Button(scrollable_body, text="I'm a button in the scrollable frame").grid()
scrollable_body.update()
root.mainloop()
Extends class tk.Frame to support a scrollable Frame
This class is independent from the widgets to be scrolled and can be used to replace a standard tk.Frame.
import tkinter as tk
class ScrollbarFrame(tk.Frame):
"""
Extends class tk.Frame to support a scrollable Frame
This class is independent from the widgets to be scrolled and
can be used to replace a standard tk.Frame
"""
def __init__(self, parent, **kwargs):
tk.Frame.__init__(self, parent, **kwargs)
# The Scrollbar, layout to the right
vsb = tk.Scrollbar(self, orient="vertical")
vsb.pack(side="right", fill="y")
# The Canvas which supports the Scrollbar Interface, layout to the left
self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff")
self.canvas.pack(side="left", fill="both", expand=True)
# Bind the Scrollbar to the self.canvas Scrollbar Interface
self.canvas.configure(yscrollcommand=vsb.set)
vsb.configure(command=self.canvas.yview)
# The Frame to be scrolled, layout into the canvas
# All widgets to be scrolled have to use this Frame as parent
self.scrolled_frame = tk.Frame(self.canvas, background=self.canvas.cget('bg'))
self.canvas.create_window((4, 4), window=self.scrolled_frame, anchor="nw")
# Configures the scrollregion of the Canvas dynamically
self.scrolled_frame.bind("<Configure>", self.on_configure)
def on_configure(self, event):
"""Set the scroll region to encompass the scrolled frame"""
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
Usage:
class App(tk.Tk):
def __init__(self):
super().__init__()
sbf = ScrollbarFrame(self)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
sbf.grid(row=0, column=0, sticky='nsew')
# sbf.pack(side="top", fill="both", expand=True)
# Some data, layout into the sbf.scrolled_frame
frame = sbf.scrolled_frame
for row in range(50):
text = "%s" % row
tk.Label(frame, text=text,
width=3, borderwidth="1", relief="solid") \
.grid(row=row, column=0)
text = "this is the second column for row %s" % row
tk.Label(frame, text=text,
background=sbf.scrolled_frame.cget('bg')) \
.grid(row=row, column=1)
if __name__ == "__main__":
App().mainloop()

expand scrollable ttk.notebook in python3 / gui tkinter

coding a scrollable frame with a ttk.notebook inside works.
But the scrollbar / notebook has a fixed size. How can I change it?
from tkinter import Canvas, Scrollbar, Button, Tk
from tkinter.ttk import Frame, Notebook
class VerticalScrolledFrame(Frame):
"""A pure Tkinter scrollable frame that actually works!
* Use the 'interior' attribute to place widgets inside the scrollable frame
* Construct and pack/place/grid normally
* This frame only allows vertical scrolling
"""
def __init__(self, parent, *args, **kw):
Frame.__init__(self, parent, *args, **kw)
# create a canvas object and a vertical scrollbar for scrolling it
vscrollbar = Scrollbar(self, orient='vertical')
vscrollbar.pack(fill='y', side='right', expand='false')
canvas = Canvas(self, bd=0, highlightthickness=0,
yscrollcommand=vscrollbar.set)
canvas.pack(side='left', fill='both', expand='true')
vscrollbar.config(command=canvas.yview)
# reset the view
canvas.xview_moveto(0)
canvas.yview_moveto(0)
# create a frame inside the canvas which will be scrolled with it
self.interior = interior = Frame(canvas)
interior_id = canvas.create_window(0, 0, window=interior,
anchor='nw')
# track changes to the canvas and frame width and sync them,
# also updating the scrollbar
def _configure_interior(event):
# update the scrollbars to match the size of the inner frame
size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
canvas.config(scrollregion="0 0 %s %s" % size)
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the canvas's width to fit the inner frame
canvas.config(width=interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
def _configure_canvas(event):
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the inner frame's width to fill the canvas
canvas.itemconfigure(interior_id, width=canvas.winfo_width())
canvas.bind('<Configure>', _configure_canvas)
root = Tk()
class Overview:
def __init__(self):
#mainframe to make a scrollable window
self.mainframe = VerticalScrolledFrame(root)
self.mainframe.grid(row=0, column=0)
# create a notebook
self.TNotebook_Overview = Notebook(self.mainframe.interior)
self.TNotebook_Overview.grid(row=0, column=0)
self.TNotebook_Overview.configure(takefocus="")
self.Frame_Overview = Frame(self.TNotebook_Overview)
self.TNotebook_Overview.add(self.Frame_Overview)
self.TNotebook_Overview.tab(0, text="Table", compound="left",underline="-1", )
buttons = []
for i in range(30):
buttons.append(Button(self.Frame_Overview, text="Button " + str(i)))
buttons[-1].grid(column=0, row=i)
if __name__ == "__main__":
ov = Overview()
root.title('Overview Items Database')
root.geometry('800x800+10+10')
root.configure(background="#4C7274")
root.grab_set()
root.mainloop()
I expect an expanded scrollable notebook/frame filled entire Tk() window.
Because the "ai" of stackoverflow don't allow use code from another thread, here is code https://pastebin.com/ykJGViAz
Inside init, do this instead
self.mainframe.grid(row=0, column=0, sticky="nsew")
self.TNotebook_Overview.grid(row=0, column=0, sticky="nsew")
This tells the frame to fill as much space as it needs
Just add this in the third line of init in the class overview:
self.mainframe.pack(fill = BOTH, expand = 1)

How to to fit scrollbar approriately?

I am new to tkinter and python3. I have worked on creating a scrollbar for a frame that is a child of a canvas which is also a child of Toplevel(). The scrollbar buttons function well but the bar/box itself stretches from top to bottom and cannot move. Furthermore, using the scroll buttons, the user can scroll way beyond the content (where there nothing to view).
Here is the code.
#! /usr/bin/env python3
from tkinter import *
from filegroups import typeGroups
app = Tk()
types_window = Toplevel(app)
types_window.wm_title('Types')
yscrollbar = Scrollbar(types_window, orient=VERTICAL)
yscrollbar.grid(row=0, column=1, sticky=N+S)
canvas = Canvas(types_window,
width = 300,
height = 500,
yscrollcommand=yscrollbar.set)
canvas.grid(row=0,column=0)
canvas.config(scrollregion=canvas.bbox("all"))
yscrollbar.config(command=canvas.yview)
frame = Frame(canvas)
canvas.create_window(0,0,anchor=NW,window=frame)
for key in sorted(typeGroups.keys()):
options_frame = LabelFrame(frame, text=key)
options_frame.grid(padx=5, pady=10)
for item in typeGroups[key]:
item_button = Checkbutton(options_frame,
text=item)
item_button.grid()
app.mainloop()
You need to update the canvas scrollregion after filling the frame with labels by adding these two lines just before app.mainloop():
canvas.update_idletasks()
canvas.config(scrollregion=canvas.bbox("all"))
The call to update_idletasks is needed to ensure that the canvas content is updated before we ask for the bounding box.

Resources