I try to open a new window where there will be a list of check box and will be scrollable.
But, my list is not scrolled.
this is my code:
class PageCanvas1(tk.Toplevel):
def __init__(self, parent):
global arr
global users
arr = {}
tk.Toplevel.__init__(self, parent)
self.title('Canvas')
self.geometry('400x600')
canvas = tk.Canvas(self, bg='white', scrollregion=(0, 0, 400, 20000))
canvas.pack(fill='both', expand=True)
vbar = tk.Scrollbar(canvas, orient='vertical')
vbar.pack(side='right', fill='y')
vbar.config(command=canvas.yview)
canvas.config(yscrollcommand=vbar.set)
canvas.create_text(5, 0, anchor='nw', text="Choose users: ")
for i in range(1000):
arr[i] = tk.IntVar()
Checkbutton(canvas, text=str(i), variable=arr[i]).pack()#.grid(row=i, sticky=W)
root = Tk()
b_choose = Button(root, text='choose users', height=3, width=15, bg="turquoise", command=(lambda arr=ents: PageCanvas(root)))
I can not find any answer to this specific thing, that a new window must be opened! I would be happy to help!
import Tkinter as tk
class PageCanvas1(tk.Toplevel):
def __init__(self, parent):
global arr # why use global? set it as an attribute?
global users # same as above?
arr = {}
tk.Toplevel.__init__(self, parent)
self.title('Canvas')
self.geometry('400x600')
canvas = tk.Canvas(self, bg='white', scrollregion=(0, 0, 400, 20000))
canvas.pack(fill='both', expand=True)
vbar = tk.Scrollbar(canvas, orient='vertical')
vbar.pack(side='right', fill='y')
vbar.config(command=canvas.yview)
canvas.config(yscrollcommand=vbar.set)
canvas.create_text(5, 0, anchor='nw', text="Choose users: ")
# we need a container widget to put into the canvas
f = tk.Frame(canvas)
# you need to create a window into the canvas for the widget to scroll
canvas.create_window((200, 0), window=f, anchor="n")
for i in range(0, 1000):
arr[i] = tk.IntVar()
# widget must be packed into the container, not the canvas
tk.Checkbutton(f, text=str(i), variable=arr[i]).pack()#.grid(row=i, sticky=W)
if __name__ == "__main__":
app = PageCanvas1(None)
app.mainloop()
You can only scroll objects in a canvas if they are added to the canvas via the create_window method, and not with grid, pack, or place.
Related
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'd like to make a scrollable frame which fills entire cell in app window. When the window is resized the widgets in the frame should stay centered. Since frames are not scrollable I used canvas and placed a frame in its window. Scroll region was associated with the frame. However I cannot make the frame expand and fill entire canvas area.
I tried to create a window in canvas for the frame having entire canvas width and associate the change of width of the window with canvas configuration event. However I get a wired result. The frame occupies only the right part of the window. When I expand the window it moves to the left. I colored the canvas in yellow and the frame in green to make things visible.
Thanks for any help!
import tkinter as tk
class FrameWithScrollBar(tk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.canvas = tk.Canvas(self, bg='yellow')
self.frame = tk.Frame(self.canvas, bg='green')
self.scrollbar = tk.Scrollbar(self, orient='vertical',
command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.BOTH)
self.frame.pack(fill=tk.BOTH, expand=True)
self._frame_id = self.canvas.create_window(
self.canvas.winfo_reqwidth(), 0,
anchor='n',
window=self.frame)
self.frame.bind('<Configure>', self.onFrameConfigure)
self.canvas.bind('<Configure>', self.onCanvasConfigure)
def onFrameConfigure(self, event):
self.canvas.configure(scrollregion=self.frame.bbox('all'))
def onCanvasConfigure(self, event):
width = event.width
self.canvas.itemconfigure(self._frame_id, width=width)
if __name__ == '__main__':
root = tk.Tk()
fws = FrameWithScrollBar(root)
buttons = list()
for i in range(5):
for j in range(25):
button = tk.Button(fws.frame, text='Button ' + str(i) + ','+str(j))
button.grid(row=j, column=i, sticky='wesn')
tk.Grid.columnconfigure(fws.frame, j, weight=1)
fws.pack(expand=True, fill=tk.BOTH)
root.mainloop()
Thanks, stovfl!
Working code in case someone would need it
import tkinter as tk
class FrameWithScrollBar(tk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.canvas = tk.Canvas(self, bg='yellow')
self.frame = tk.Frame(self.canvas, bg='green')
self.scrollbar = tk.Scrollbar(self, orient='vertical',
command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.BOTH)
self.frame.pack(fill=tk.BOTH, expand=True)
self._frame_id = self.canvas.create_window(
self.canvas.winfo_width(), 0,
anchor='nw',
window=self.frame)
self.frame.bind('<Configure>', self.onFrameConfigure)
self.canvas.bind('<Configure>', self.onCanvasConfigure)
def onFrameConfigure(self, event):
self.canvas.configure(scrollregion=self.frame.bbox('all'))
def onCanvasConfigure(self, event):
width = event.width
self.canvas.itemconfigure(self._frame_id, width=self.canvas.winfo_width())
if __name__ == '__main__':
root = tk.Tk()
fws = FrameWithScrollBar(root)
buttons = list()
for i in range(5):
for j in range(25):
button = tk.Button(fws.frame, text='Button ' + str(i) + ','+str(j))
button.grid(row=j, column=i, sticky='wesn')
tk.Grid.columnconfigure(fws.frame, i, weight=1)
fws.pack(expand=True, fill=tk.BOTH)
root.mainloop()
I'm trying to make the buttons on my program to perform an action but I'm not 100% how to do that. I've created the buttons hopefully their correct, but just need some advice on how to make them work ! so when i click the button "add rect" it should add a random rectangle in a random position vice versa for remove.
from tkinter import *
import random
root = Tk()
class Recta:
def __init__(self, height, width):
self.height=60
self.width=80
def randomRects(self,canvas):
w = random.randrange(80)
h = random.randrange(60)
canvas.create_rectangle(0,0,h,w,fill='green')
def create_buttons(self,canvas):
frame = Frame(root, bg='grey', width=400, height=40)
frame.pack(fill='x')
frame = Frame(root, bg='grey', width=400, height=40)
frame.pack(fill='x')
button1 = Button(frame, text='Add Rect')
button1.pack(side='left', padx=10)
button2 = Button(frame, text='Remove Rect')
button2.pack(side='left')
def removeRects(self,canvas):
self.myRect = canvas.create_rectangle(0, 0, w, h, fill='green')
canvas.delete(self.myRect)
c = Canvas(root)
c.pack()
tes = Recta(10,20)
tes.randomRects(c)
tes.create_buttons(1)
root.mainloop()
Your code needed serious reorganization.
Here is something that works to add rectangles. you did not provide a remove rectangle method, so i let you write it - at this moment, the delete button calls randomRect; you likely will need to keep track of the rectangles you create in a collection of some sort in order to be able to remove them.
from tkinter import *
import random
root = Tk()
class Recta:
def __init__(self, height=60, width=80):
self.height = height
self.width = width
self.create_buttons()
self.canvas = Canvas(root)
self.canvas.pack()
def create_buttons(self):
self.frame = Frame(root, bg='grey', width=400, height=40)
self.frame.pack(fill='x')
self.button1 = Button(self.frame, text='Add Rect', command=self.randomRects)
self.button1.pack(side='left', padx=10)
self.button2 = Button(self.frame, text='Remove Rect', command=self.randomRects)
self.button2.pack(side='left')
def randomRects(self):
w = random.randrange(300)
h = random.randrange(200)
self.canvas.create_rectangle(0, 0, w, h, fill='green')
tes = Recta()
root.mainloop()
Based on the example from Dynamically changing scrollregion of a canvas in Tkinter, I am trying to implement a Frame where you can add and delete entries in a scrollable Frame using tkinter. My Problem is that the Frame holding items does not resize after deleting entries. When adding entries, it resizes correctly. I call update_layout() in both cases:
from tkinter import *
class ScrollableContainer(Frame):
"""A scrollable container that can contain a number of messages"""
def __init__(self, master, **kwargs):
Frame.__init__(self, master, **kwargs) #holds canvas & scrollbars
# configure row and col to take additional space if there is some
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
# create canvas
self.canv = Canvas(self, bd=0, highlightthickness=0)
# create scrollbars
self.hScroll = Scrollbar(self, orient='horizontal',
command=self.canv.xview)
self.hScroll.grid(row=1, column=0, sticky='we')
self.vScroll = Scrollbar(self, orient='vertical',
command=self.canv.yview)
self.vScroll.grid(row=0, column=1, sticky='ns')
# set postiotion of canvas in (self-)Frame
self.canv.grid(row=0, column=0, sticky='nsew')
self.canv.configure(xscrollcommand=self.hScroll.set,
yscrollcommand=self.vScroll.set)
# create frame to hold messages in canvas
self.frm = Frame(self.canv, bd=2, bg='gray') #holds messages
self.frm.grid_columnconfigure(0, weight=1)
# create empty tkinter widget (self.frm) on the canvas
self.canv.create_window(0, 0, window=self.frm, anchor='nw', tags='inner')
# update layout
self.update_layout()
# on change of size or location this event is fired. The event provides new width an height to callback function on_configure
self.canv.bind('<Configure>', self.on_configure)
self.widget_list = []
# update and resize layout
def update_layout(self):
print('update')
self.frm.update_idletasks()
self.canv.configure(scrollregion=self.canv.bbox('all'))
self.size = self.frm.grid_size()
# resize canvas and scroll region depending on content
def on_configure(self, event):
print('on_configure')
# get new size of canvas
w,h = event.width, event.height
# get size of frm required to display all content
natural = self.frm.winfo_reqwidth()
self.canv.itemconfigure('inner', width= w if w>natural else natural)
self.canv.configure(scrollregion=self.canv.bbox('all'))
# add new entry and update layout
def add_message(self, text):
print('add message')
# create var to represent states
int_var = IntVar()
cb = Checkbutton(self.frm, text=text, variable=int_var)
cb.grid(row=self.size[1], column=0, padx=1, pady=1, sticky='we')
self.widget_list.append(cb)
self.update_layout()
# delete all messages
def del_message(self):
print('del message')
for it in self.widget_list:
it.destroy()
self.update_layout()
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
sc = ScrollableContainer(root, bd=2, bg='black')
sc.grid(row=0, column=0, sticky='nsew')
def new_message():
test = 'Something Profane'
sc.add_message(test)
def del_message():
sc.del_message()
b = Button(root, text='New Message', command=new_message)
b.grid(row=1, column=0, sticky='we')
del_b = Button(root, text='Del Message', command=del_message)
del_b.grid(row=2, column=0, sticky='we')
root.mainloop()
I was working on something similar so I took my code and merged it with yours for the answer.
Here is a scrollingFrame class that will add scrollbars and remove them whenever the box is resized. Then there is a second class for your message list that will tell the scrollingFrame to readjust itself as necessary whenever items are added/deleted.
class scrollingFrame(Frame):
def __init__(self, parentObject, background):
Frame.__init__(self, parentObject, background = background)
self.canvas = Canvas(self, borderwidth=0, background = background, highlightthickness=0)
self.frame = Frame(self.canvas, background = background)
self.vsb = Scrollbar(self, orient="vertical", command=self.canvas.yview, background=background)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.grid(row=0, column=1, sticky=N+S)
self.hsb = Scrollbar(self, orient="horizontal", command=self.canvas.xview, background=background)
self.canvas.configure(xscrollcommand=self.hsb.set)
self.hsb.grid(row=1, column=0, sticky=E+W)
self.canvas.grid(row=0, column=0, sticky=N+S+E+W)
self.window = self.canvas.create_window(0,0, window=self.frame, anchor="nw", tags="self.frame")
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.frame.bind("<Configure>", self.onFrameConfigure)
self.canvas.bind("<Configure>", self.onCanvasConfigure)
def onFrameConfigure(self, event):
#Reset the scroll region to encompass the inner frame
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def onCanvasConfigure(self, event):
#Resize the inner frame to match the canvas
minWidth = self.frame.winfo_reqwidth()
minHeight = self.frame.winfo_reqheight()
if self.winfo_width() >= minWidth:
newWidth = self.winfo_width()
#Hide the scrollbar when not needed
self.hsb.grid_remove()
else:
newWidth = minWidth
#Show the scrollbar when needed
self.hsb.grid()
if self.winfo_height() >= minHeight:
newHeight = self.winfo_height()
#Hide the scrollbar when not needed
self.vsb.grid_remove()
else:
newHeight = minHeight
#Show the scrollbar when needed
self.vsb.grid()
self.canvas.itemconfig(self.window, width=newWidth, height=newHeight)
class messageList(object):
def __init__(self, scrollFrame, innerFrame):
self.widget_list = []
self.innerFrame = innerFrame
self.scrollFrame = scrollFrame
# Keep a dummy empty row if the list is empty
self.placeholder = Label(self.innerFrame, text=" ")
self.placeholder.grid(row=0, column=0)
# add new entry and update layout
def add_message(self, text):
print('add message')
self.placeholder.grid_remove()
# create var to represent states
int_var = IntVar()
cb = Checkbutton(self.innerFrame, text=text, variable=int_var)
cb.grid(row=self.innerFrame.grid_size()[1], column=0, padx=1, pady=1, sticky='we')
self.widget_list.append(cb)
self.innerFrame.update_idletasks()
self.scrollFrame.onCanvasConfigure(None)
# delete all messages
def del_message(self):
print('del message')
for it in self.widget_list:
it.destroy()
self.placeholder.grid()
self.innerFrame.update_idletasks()
self.scrollFrame.onCanvasConfigure(None)
deviceBkgColor = "#FFFFFF"
root = Tk() # Makes the window
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.wm_title("Title") # Makes the title that will appear in the top left
root.config(background = deviceBkgColor)
myFrame = scrollingFrame(root, background = deviceBkgColor)
myFrame.grid(row=0, column=0, sticky=N+S+E+W)
msgList = messageList(myFrame, myFrame.frame)
def new_message():
test = 'Something Profane'
msgList.add_message(test)
def del_message():
msgList.del_message()
b = Button(root, text='New Message', command=new_message)
b.grid(row=1, column=0, sticky='we')
del_b = Button(root, text='Del Message', command=del_message)
del_b.grid(row=2, column=0, sticky='we')
root.mainloop() #start monitoring and updating the GUI
So I've got a custom widget that inherits from frame and contains a canvas and a scrollbar, and a custom widget that also inherits from frame that I want to dynamically add to the canvas, resizing the scrollregion as necessary. Here's my code:
class MessageItem(Frame):
"""A message to be contained inside a scrollableContainer"""
def __init__(self, message, **kwds):
Frame.__init__(self, **kwds)
self.text = Label(self, text = message)
self.text.grid(column = 0, row = 0, sticky = N+S+E+W)
class scrollableContainer(Frame):
"""A scrollable container that can contain a number of messages"""
def initContents(self):
"""Initializes a scrollbar, and a canvas that will contain all the items"""
#the canvas that will contain all our items
self.canv = Canvas(self)
self.canv.grid(column = 0, row = 0, sticky = N+S+W)
#force Tkinter to draw the canvas
self.canv.update_idletasks()
#use the values from the canvas being drawn to determine the size of the scroll region
#note that currently, since the canvas contains nothing, the scroll region will be the same as
#the size of the canvas
geometry = self.canv.winfo_geometry()
xsize, ysize, xpos, ypos = parse_geometry_string(geometry)
self.canv['scrollregion'] = (0, 0, xsize, ysize)
#the scrollbar for that canvas
self.vscroll = Scrollbar(self, orient = VERTICAL, command = self.canv.yview )
self.vscroll.grid(column = 1, row = 0, sticky = N+S+E)
self.canv["yscrollcommand"] = self.vscroll.set
def __init__(self, **kwds):
Frame.__init__(self, **kwds)
#initialize the widget's contents
self.grid(sticky = N+S+E+W)
self.pack()
self.initContents()
#initialize the list of contents so we can append to it
self.contents = []
def addMessage(self, message):
#Add the message to the list of contents
self.contents.append(MessageItem(message))
#Add the message to the grid
self.contents[(len(self.contents) - 1)].grid(column = 0, row = (len(self.contents) - 1))
#set the new scrollable region for the canvas
scrollregionlist = self.canv['scrollregion'].split()
oldRegion = int(scrollregionlist[3])
newRegion = oldRegion + parse_geometry_string(self.contents[
(len(self.contents) - 1)].winfo_geometry())[3]
self.canv['scrollregion'] = (int(scrollregionlist[0]), int(scrollregionlist[1]),
int(scrollregionlist[2]), newRegion)
The problem I'm experiencing is that self.canv['scrollregion'] appears to disappear outside of init. In the addMessage method, in the line:
scrollregionlist = self.canv['scrollregion'].split()
The scrollregion property on self.canv returns an empty string, which I can verify by putting a
print self.canv['scrollregion']
immediately before that line
You sure a Text widget wouldn't suffice here?
anyway,
from Tkinter import *
class MessageItem(Frame):
"""A message to be contained inside a scrollableContainer"""
def __init__(self, master, message, **kwds):
Frame.__init__(self, master, **kwds)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.text = Label(self, text=message, anchor='w', bg='gold')
self.text.grid(row=0, column=0, sticky='nsew')
class scrollableContainer(Frame):
"""A scrollable container that can contain a number of messages"""
def __init__(self, master, **kwargs):
Frame.__init__(self, master, **kwargs) #holds canvas & scrollbars
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.canv = Canvas(self, bd=0, highlightthickness=0)
self.hScroll = Scrollbar(self, orient='horizontal',
command=self.canv.xview)
self.hScroll.grid(row=1, column=0, sticky='we')
self.vScroll = Scrollbar(self, orient='vertical',
command=self.canv.yview)
self.vScroll.grid(row=0, column=1, sticky='ns')
self.canv.grid(row=0, column=0, sticky='nsew')
self.canv.configure(xscrollcommand=self.hScroll.set,
yscrollcommand=self.vScroll.set)
self.frm = Frame(self.canv, bd=2, bg='green') #holds messages
self.frm.grid_columnconfigure(0, weight=1)
self.canv.create_window(0, 0, window=self.frm, anchor='nw', tags='inner')
self.messages = []
for i in range(20):
m = MessageItem(self.frm, 'Something Profound', bd=2, bg='black')
m.grid(row=i, column=0, sticky='nsew', padx=2, pady=2)
self.messages.append(m)
self.update_layout()
self.canv.bind('<Configure>', self.on_configure)
def update_layout(self):
self.frm.update_idletasks()
self.canv.configure(scrollregion=self.canv.bbox('all'))
self.canv.yview('moveto','1.0')
self.size = self.frm.grid_size()
def on_configure(self, event):
w,h = event.width, event.height
natural = self.frm.winfo_reqwidth()
self.canv.itemconfigure('inner', width= w if w>natural else natural)
self.canv.configure(scrollregion=self.canv.bbox('all'))
def add_message(self, message):
m = MessageItem(self.frm, message, bd=2, bg='red')
m.grid(row=self.size[1], column=0, padx=2, pady=2, sticky='we')
self.messages.append(m)
self.update_layout()
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
sc = scrollableContainer(root, bd=2, bg='black')
sc.grid(row=0, column=0, sticky='nsew')
def new_message():
test = 'Something Profane'
sc.add_message(test)
b = Button(root, text='New Message', command=new_message)
b.grid(row=1, column=0, sticky='we')
root.mainloop()