tkinter maximum canvas size? - python

It seems I'm hitting some kind of preset maximum scrollable canvas size, that I didnt know about...
I've written a simple bare-bones iTunes replacement in Tkinter.
Since I like the album cover view, an album needs at least 200x200 px size, and I have A LOT of albums (~1600), it follows that I need a lot of space.
But I discovered that above a height ~ 35000px the window is unable to show them.
Here I written a sample code of it - it needs ImageMagick's convert, and around 15sec to run on my machine.
You can see that the window only shows 163 of the 170 squares...
from Tkinter import *
import subprocess
def main():
root = Tk()
root.geometry("%dx%d+0+0" % (1800,1000))
cv = Canvas(root)
vscrollbar = Scrollbar(root, orient=VERTICAL)
vscrollbar.pack(fill=Y, side=RIGHT)
vscrollbar.config(command=cv.yview)
cv.config(yscrollcommand=vscrollbar.set)
cv.configure(scrollregion=(0,0, 4000, 50000))
cv.pack(side=LEFT, fill=BOTH, expand=TRUE)
fcv=Frame(root)
cv.create_window(0, 0, anchor = "nw", window=fcv)
memimages=[]
for row_index in range(170):
a=subprocess.Popen("convert -size 200x200 -pointsize 22 -gravity center label:%d test.gif" % row_index, shell=True,
stdout=subprocess.PIPE,stderr=subprocess.PIPE)
output, errors = a.communicate()
iconimage = PhotoImage(file="test.gif")
b=Button(fcv,image=iconimage)
memimages.append(iconimage)
b.grid(row=row_index, column=0, sticky=N+S+E+W)
mainloop()
main()

I modified your code to show an image at specific pixel height locations, e.g. one at y=0, one at y=32000 and one at y=50000. The canvas is able to traverse from 0 all the way to 50,000 pixel height, and we can see the images as expected.
This means the canvas is able to scroll all the way to y=50000 pixels and the problem lies not with pixel height limitation of canvas but I am guessing it could be with the manner the button is placed into the frame of the canvas window or the placement of frame in the canvas window or the placement of the canvas window itself into the canvas.
You can run this revised code to see what I mean. Scroll all the way to the bottom. Hope this gives you more insight to troubleshoot your code.
from Tkinter import *
def main():
root = Tk()
root.geometry("%dx%d+0+0" % (1800,1000))
cv = Canvas(root)
vscrollbar = Scrollbar(root, orient=VERTICAL)
vscrollbar.pack(fill=Y, side=RIGHT)
vscrollbar.config(command=cv.yview)
cv.configure(yscrollcommand=vscrollbar.set)
cv.configure(scrollregion=(0,0, 4000, 50000))
cv.pack(side=LEFT, fill=BOTH, expand=TRUE)
iconimage = PhotoImage(file="monkey.gif")
testimage = cv.create_image(300, 0, image=iconimage)
testimage1 = cv.create_image(300, 32000, image=iconimage)
testimage2 = cv.create_image(300, 50000, image=iconimage)
mainloop()
main()
Update: After further testing, it does seems there is a limitation on the display height of the window formed by the Canvas.create_window() method. I added the code below, just before mainloop(), which attempts to create buttons and labels with image of 100x100 pixels. The max. no. of rows of buttons that could be displayed was 316+ while max. no. of rows of labels that could be displayed was 322+. If buttons and labels were created together, the max. no. of row that could be displayed was 316+. My conclusion appears to be identical to yours.
Sorry to not have been able to answer your question. However, I hope to support you with my answer, and recommend someone more knowledgeable explains why this behaviour is the case.
fcv=Frame(cv)
cv.create_window(0, 0, anchor = "nw", window=fcv)
iconimage = PhotoImage(file="monkey100.gif") # Image dimension is 100x100 pixels
for row_index in range(340):
b=Button(fcv,image=iconimage)
b.grid(row=row_index, column=0, sticky=N+S+E+W)
lb=Label(fcv,text=str(row_index), image=iconimage, compound=LEFT)
lb.grid(row=row_index, column=1, sticky=N+S+E+W)

I found a way out the problem.
the limit seens to come from create_window iteself.
So i create multiples windows and this works fine...
from tkinter import *
from PIL import Image, ImageTk, ImageDraw
root = Tk()
vsb = Scrollbar(root, orient=VERTICAL)
vsb.grid(row=0, column=1, sticky=N+S)
hsb = Scrollbar(root, orient=HORIZONTAL)
hsb.grid(row=1, column=0, sticky=E+W)
c = Canvas(root,yscrollcommand=vsb.set, xscrollcommand=hsb.set)
c.grid(row=0, column=0, sticky="news")
vsb.config(command=c.yview)
hsb.config(command=c.xview)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
w, h = 200,350
image={}
for i in range(0,200):
fr = Frame(c)
c.create_window(2, i*(h+2), window=fr)
image[i]=Image.new ('RGB', (w, h))
draw = ImageDraw.Draw(image[i])
draw.rectangle ((0,0,w,h), fill = (20,20,20) )
draw.text ((1,1), str(i), (255,255,255))
image[i]=ImageTk.PhotoImage(image[i])
btn=Button(fr, image=image[i])
btn.pack()
fr.update_idletasks()
c.config(scrollregion=c.bbox("all"))
root.mainloop()
quit()

Related

Tkinter. Scrollbar is not working on Canvas

I am newbie in programming, don't hate me pls :)
Why scroll is not working on my canvas widget?
I added loop with 30 rows and I cannot scroll down.
Its look like it because of create_text() method or maybe not.
I've written code for example below.
from tkinter import *
root = Tk()
root.geometry('200x150')
frame = Frame(root)
yscrollbar = Scrollbar(frame, orient=VERTICAL)
yscrollbar.pack(fill=Y, side=RIGHT)
canvas = Canvas(frame,
yscrollcommand=yscrollbar.set,
bg='white')
canvas.pack(fill=BOTH)
yscrollbar.config(command=canvas.yview)
n=12
for i in range(1,31):
canvas.create_text(10,n,text=i)
n+=12
frame.pack()
root.mainloop()
Scrolling is not responsive because you need to tell the canvas to limit the scrolling to a given area.
You can use the bbox method to get a bounding box for a given object, or a group of objects.
canvas.bbox(ALL) returns the bounding box for all objects on the canvas.
Link: http://effbot.org/zone/tkinter-scrollbar-patterns.htm you can check other methods to do this in this link
Here is the working code:
from tkinter import *
root = Tk()
root.geometry('200x150')
frame = Frame(root)
yscrollbar = Scrollbar(frame, orient=VERTICAL)
yscrollbar.pack(fill=Y, side=RIGHT)
canvas = Canvas(frame,
yscrollcommand=yscrollbar.set,
bg='white')
canvas.pack(fill=BOTH)
yscrollbar.config(command=canvas.yview)
n=12
for i in range(1,31):
canvas.create_text(10,n,text=i)
n+=12
frame.pack()
# Add this line to tell the canvas the area over to scroll
canvas.config(scrollregion=canvas.bbox(ALL))
root.mainloop()

Python Tkinter - How to make a child frame with image inside a set width

I'm working on a program, which I'm building using python's tkinter gui library.
My 2 Problems
I'm trying to make a frame which will house a screenshot of the selected website. I have the frame and image created, but the frame is not anchoring to the right side of the window as expected. When the label element inside the frame has an image the frame is the expected length
But, when the selected site does not have an image yet the frame is not stretching all the way to the right side of the window.
Below is the portion of my code where I set up the image container/frame and respective image.
self.frame_ImageContainer = Frame(self.tab_Details, width=320, height=180, bg='black', bd=2)
self.frame_ImageContainer.grid(column=4, row=0, rowspan=11, columnspan=3, sticky=(N, S, E, W))
self.button_TakeScreenshot = Button(self.frame_ImageContainer, text='Take Screenshot', command=self.fn_RunScraper)
self.button_TakeScreenshot.grid(column=1, row=5, sticky=(E, W))
self.widget_Image = Label(self.frame_ImageContainer, compound='top')
self.widget_Image.grid(column=0, row=0, rowspan=12, columnspan=3, sticky=N)
self.frame_ImageContainer.grid_columnconfigure(0, weight=1)
self.frame_ImageContainer.grid_columnconfigure(1, weight=1)
self.frame_ImageContainer.grid_columnconfigure(2, weight=1)
self.frame_ImageContainer.grid_rowconfigure(0, weight=1)
self.frame_ImageContainer.grid_rowconfigure(1, weight=1)
self.frame_ImageContainer.grid_rowconfigure(2, weight=1)
self.frame_ImageContainer.grid_rowconfigure(3, weight=1)
self.frame_ImageContainer.grid_rowconfigure(4, weight=1)
self.frame_ImageContainer.grid_rowconfigure(5, weight=1)
self.frame_ImageContainer.grid_rowconfigure(6, weight=1)
self.frame_ImageContainer.grid_rowconfigure(7, weight=1)
self.frame_ImageContainer.grid_rowconfigure(8, weight=1)
self.frame_ImageContainer.grid_rowconfigure(9, weight=1)
self.frame_ImageContainer.grid_rowconfigure(10, weight=1)
self.frame_ImageContainer.grid_rowconfigure(11, weight=1)
The Take Screenshot button will not center both vertically & horizontally. I've attempted to center the Take Screenshot button in the middle of the frame, but it only applies vertically currently. I've also tried using .place(relx=.5, rely=.5, anchor=CENTER), but when I try that the frame/image doesn't even show up on the screen.
What I'm trying to accomplish
Making the image frame a set width
Showing a Take Screenshot button in the center of the image frame when a site does not have a screenshot yet.
In Question Form
How can I make the frame & image stay a consistent width & height (320x180)?
How can I center the Take Screenshot button both vertically and horizontally inside the Image frame?
If you want a frame to have a fixed size then simply unset its propagation for the layout manager its children use, to discard the size change based on its children's size demands, while defining setting its width and height:
frame = tk.Frame(..., width=320, height=180, ...)
#frame.grid_propagate(False) # uncomment if children use pack
frame.pack_propagate(False) # uncomment if children use grid
For centering a widget one easy way is using place:
widget.place(relx=.5, rely=.5, anchor='center')
A Minimal, Complete, and Verifiable example that accomplishes both:
from PIL import Image, ImageTk
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
if __name__ == '__main__':
root = tk.Tk()
frame = tk.Frame(root, width=320, height=180, bg='#f48024')
img = Image.new('RGB', (640, 360), color=(0, 121, 152))
frame.image = ImageTk.PhotoImage(img)
frame_s_label = tk.Label(frame, image=frame.image)
button = tk.Button(frame, text="Button")
button.place(relx=.5, rely=.5, anchor='center')
frame.grid()
#frame.grid_propagate(False) # uncomment if children use pack
frame.pack_propagate(False) # uncomment if children use grid
frame_s_label.pack()
tk.mainloop()
How can I make the frame & image stay a consistent width & height (320x180)?
If you want the frame to be a specific size, start by giving it a specific size. Next, either use place to add widgets to it, or turn geometry propagation off if using grid or pack
# if using pack:
self.frame_ImageContainer.pack_propagate(False)
# if using grid:
self.frame_ImageContainer.grid_propagate(False)
How can I center the Take Screenshot button both vertically and horizontally inside the Image frame?
The easiest way is to use place, since it won't affect the size of its master, and you can provide relative coordinates.
self.button_TakeScreenshot.place(relx=.5, rely=.5, anchor="center")

Canvas on a frame not being displayed

I have a Canvas inside a Frame in tkinter. The frame has a background color and the canvas too. But seemingly the frame background overrides the canvas color.
How can I increase the transparency of the frame background such that the canvas is visible?
import Tkinter
import tkMessageBox
from Tkinter import *
top = Tkinter.Tk()
frame = Frame(top, width=1000, height=1000, background="bisque")
frame.pack()
bottomframe = Frame(top, width=1000, height=1000, background="red")
bottomframe.pack( side = BOTTOM )
def creatLayers(no_of_layers, max_nodes_in_each_layer, frame1=bottomframe):
print 'here2'
listLayerRect=[]
listDelimiterRect=[]
#The canvas is created here.
mainCanvas=Tkinter.Canvas(frame1, bg="white", height=1000, width=1000)
frame1.pack(side=LEFT)
for i in range (0,no_of_layers):
print 'here3'
x=15*i
#rectangles that are being drawn on the canvas.
mainCanvas.create_polygon(x,0,x+10,0,x+10,1000,x,1000, outline='gray', fill='gray', width=2)
# listLayerRect.append(Tkinter.Canvas(frame1, bg="blue", height=1000, width=30))
# listDelimiterRect.append(Tkinter.Canvas(frame1, bg="yellow", height=1000, width=30))
L1 = Label(frame, text="Layers")
E1 = Entry(frame, bd =8)
L2 = Label(frame, text="Layers2")
def helloCallBack(E=E1,):
# tkMessageBox.showinfo( "Hello Python", "Hello World")
k=int(E.get())
print 'here'
print k
creatLayers(k,k)
B = Tkinter.Button(frame, text ="Enter", command = helloCallBack)
B.pack(side=LEFT)
#L1.pack(side=LEFT)
E1.pack(side=LEFT)
#L2.pack(side=LEFT)
top.mainloop()
So, basically, when you enter a number in the box and press Enter, a canvas gets created in the red part (frame) and a grid pattern should be drawn on that canvas. Essentially, there are 2 frames, the top frame contains the button and the entry box, the lower frame should be able to draw stuff inside on the canvas created within.
The reason why the canvas is not displayed is because you're not telling it to be displayed inside frame1, i.e. you forgot to pack (or grid, or place) it, so just do in the meantime:
...
mainCanvas=Tkinter.Canvas(frame1, bg="white", height=1000, width=1000)
mainCanvas.pack()
...
Now depending on what you really want to achieve from the layout point of view, you may need to think better how to use pack, grid and pack.
Here's the result after the correction above (on Mac OS X, Sierra)
Before clicking Enter
After clicking Enter
In general, just remember that a frame will have a empty body if it doesn't contain any widget with a certain specified size.

Python -> Labels cant be shown to Frame

Labels can't be shown into the leftFrame. I'm quite new to Python GUI. My code kinda goes like this:
from tkinter import *
root = Tk()
mainFrame = Frame(root, width=700, height=500)
mainFrame.pack()
leftFrame = Frame(mainFrame, bg="#c2c3c4")
leftFrame.place(relheight=1, relwidth=0.34, anchor=W)
label1 = Label(leftFrame, text="Label1")
label2 = Label(leftFrame, text="Label2")
label1.grid(columnspan=2, sticky=W, pady=(20, 0))
label2.grid(columnspan=3, sticky=W, pady=(5, 0))
root.mainloop()
In this particular case, you don't see the labels because they are off the screen. leftFrame has an anchor of W, which means that the vertical center of leftFrame is at 0,0. In other words, half of the frame is above the visible portion of the window.
A quick fix to prove this out is to use an anchor of NW instead of W, which will cause the upper-left corner of the frame to be at the upper-left corner of its parent.
However, I strongly encourage you to not use place at all. It has its uses, but really should rarely be used. You end up having to do a lot of work yourself, and the result is usually not very responsive to changes in fonts, resolutions, or window sizes.

Adding widgets over canvas in tkinter

I want to put a small image and other widgets over a canvas on which an image is displayed. I've tried options such ascompound and other things.
Background picture is fine and the small image that I want to put over the background image shows fine but it's always top or bottom of the window. I want it to be placed over any area of background image. I've tried many options of all the geometry manager (pack, grid, place) but none of them works. Please help, here's my code :
from Tkinter import *
root = Tk()
root.iconbitmap('E:/a.ico')
root.title('Unick Locker')
canvas = Canvas(root, width=730, height=600)
canvas.grid()
bgImg = PhotoImage(file="E:/a.gif")
canvas.create_image(370, 330, image=bgImg)
login = PhotoImage(file="E:/login.gif")
lo = Label(root, image=login)
lo.grid()
root.mainloop()
In order to add any widgets over or the foreground of any background image or canvas, the row and column values of all the widgets must be same as of the background image. so, my above mentioned program would be like this :
from Tkinter import *
root = Tk()
root.iconbitmap('E:/a.ico')
root.title('Unick Locker')
canvas = Canvas(root, width=730, height=600)
canvas.grid(row=0, column=0)
bgImg = PhotoImage(file="E:/a.gif")
canvas.create_image(370, 330, image=bgImg)
login = PhotoImage(file="E:/login.gif")
lo = Label(root, image=login)
lo.grid(row=0, column=0)
root.mainloop()
I tried putting the same row and column values to the widgets in grid() methods which I wanted to put over the image, and it worked fine as I wanted :-)
Have you considered using the paste method, which lets you define the position of the pasted image through a box argument?
See http://effbot.org/imagingbook/imagetk.htm.
Please also take a look at this thread: Tkinter, overlay foreground image on top of a background image with transparency, which seems very similar to your issue.
You are looking to draw the widgets over the canvas, this means you must specify the canvas as the parent widget, not the root as you did. For that, modify lo = Label(root, image=login) to lo = Label(canvas, image=login)
Also, do not forget to specify the rows and columns where you want to position the different widgets. This means you need to write, for example, lo.grid(row=0, column=0) instead of lo.grid(). For the moment you do not see big problems because you have only one label widget. But if you try to add an other widget without mentioning the exact positions (rows and columns) you will get unexpected results.
This question isn't about images at all, it's just a basic layout problem. You'll have the same issues with or without images. The problem is simply that you aren't giving any options to grid, so it naturally puts things at the top. Tkinter also has the behavior that a containing widget (eg: your canvas) will shrink or expand to exactly fit its contents.
Here's a version that creates several widgets over a background image. Notice the use of options to pack and grid, and the use of grid_rowconfigure and grid_columnconfigure to specify how extra space is allocated.
from Tkinter import *
root = Tk()
canvas = Canvas(root, width=730, height=600)
canvas.pack(fill="both", expand=True)
bgImg = PhotoImage(file="E:/a.gif")
canvas.create_image(370, 330, image=bgImg)
l1 = Label(canvas, text="Hello, world")
e1 = Entry(canvas)
t1 = Text(canvas)
l1.grid(row=0, column=0, sticky="ew", padx=10)
e1.grid(row=1, column=1, sticky="ew")
t1.grid(row=2, column=2, sticky="nsew")
canvas.grid_rowconfigure(2, weight=1)
canvas.grid_columnconfigure(2, weight=1)
root.mainloop()

Categories