Get height/width of tkinter Canvas - python-3.x

I am searching for this for ages now. Is it so hard to get the height/width of a Canvas in tkinter?
I want to do something like this:
c = Tk.Canvas(self, heigth=12, width=12)
c.create_oval(0, 0, self.height, self.width)
So that I draw a circle with the circumference of the width/height of my canvas.
How come I can't find any attribute like width/height of that canvas?
c.winfo_width and c.winfo_height wouldn't work, because that only gives me error.
Can you help me? This is really irritating, since even in the constructor there is the attribute heightand width...

Use winfo_reqwidth and winfo_reqheight:
root = tk.Tk()
c = tk.Canvas(root, height=120, width=120)
c.create_oval(0, 0, c.winfo_reqheight(), c.winfo_reqwidth())
c.pack()
root.mainloop()

You have to call <tkinter.Tk>.update and <tkinter.Canvas>.pack before the winfo_height like this:
import tkinter as tk
root = tk.Tk()
c = tk.Canvas(root, height=120, width=120)
c.pack()
root.update()
c.create_oval(0, 0, c.winfo_height(), c.winfo_width())
root.mainloop()
Also parts of this code are stolen from #hussic.
<tkinter.Tk>.update makes sure that all tkinter tasks are done. Those tasks can be things like making sure that the geometry manager was reserved the space for the widgets and the widgets are drawn on the screen. Calling <tkinter.Widget>.update where Widget can be any tkinter widget is the same as calling <tkinter.Tk>.update as it will call the same tcl function.

Related

Improving performance on a tkinter scrollable canvas with many widgets inside

I am trying to create a scrollable canvas in tkinter, filled with a grid of smaller square canvases. I have no problem setting this up, but the result is very slow when scrolling. Any advice on how to speed things up? I have considered, but have yet to try to implement:
Using pages of many widgets rather than one scrollable canvas. This would solve the problem, but I'd like to use a scrolling solution if possible.
When scrolling, have the canvas "snap" to the nearest row of squares. The scrolling does not need to be continuous, e.g. we don't need to see half a square at a time.
Change the canvas to only contain the squares that are currently being viewed. All windows that aren't being viewed can be destroyed and created again later. I am unclear if this approach will even help, but a similar approach worked well for a large image on a different project I've worked on.
It could be possible that this lag is just a hardware issue on my end. Unfortunately, I'm not sure how to test this.
Here is the reprex, any help is appreciated! Thank you.
import tkinter as tk
import numpy as np
root = tk.Tk()
outside = tk.Frame(root, bg='green')
outside.pack(expand=True, fill='both')
def get_box(): # box is 16x16 pixels
colors = ['red', 'blue', 'green', 'yellow']
color = np.random.choice(colors, 1)[0]
ret = tk.Canvas(canvas, bg=color, width=16, height=16, highlightthickness=0)
return ret
canvas = tk.Canvas(outside, width=16*30, bg='gray20', highlightthickness=0). # 30 columns
canvas.pack(fill='y', expand=True, side='left')
scroll_y = tk.Scrollbar(outside, orient="vertical", command=canvas.yview) # Create scrollbar
canvas.configure(scrollregion=canvas.bbox('all'), yscrollcommand=scroll_y.set)
scroll_y.pack(fill='y', side='right', expand=True)
for i in range(600): # create many boxes. ideally, ~4000 once things are running smoothly
box = get_box()
canvas.create_window(16 * (i % 30), 16 * (i //30), anchor='nw', window=box)
canvas.update_idletasks()
canvas.configure(scrollregion=canvas.bbox('all'), yscrollcommand=scroll_y.set)
root.mainloop()

aligning stuff with pack() tkinter

I know you can align a button to the left with pack(side="left"), and you can also do right top and bottom, but how would i align stuff on a new line just below a set of buttons above it?
I tried using grid but no matter how much i googled it I couldn't find out how to fix it.
What you'd want to do is create different frames, each with their own positioning logic. Take a look at this:
import tkinter
root = tkinter.Tk()
frame = tkinter.Frame(root)
frame.pack()
bottomframe = tkinter.Frame(root)
bottomframe.pack(side=tkinter.BOTTOM)
tkinter.Button(frame, text="left").pack(side=tkinter.LEFT)
tkinter.Button(frame, text="middle").pack(side=tkinter.LEFT)
tkinter.Button(frame, text="right").pack(side=tkinter.LEFT)
# what you want
tkinter.Button(bottomframe, text="top").pack(side=tkinter.TOP)
tkinter.Button(bottomframe, text="under").pack(side=tkinter.TOP)
root.mainloop()

Why the tkinter window does not open when using the method "grid()"?

from tkinter import *
# ==================================================Settings=======================================================
root = Tk()
root.title("Video Youtube Downloader") # set up the title and size.
root.configure(background='black') # set up background.
root.minsize(800, 500)
root.maxsize(800, 500)
# ==================================================Frames=======================================================
top = Frame(root, width=800, height=50, bg='yellow').pack(side=TOP)
bottom = Frame(root, width=800, height=50, bg='red').pack(side=BOTTOM)
left = Frame(root, width=550, height=450, bg='black').pack(side=LEFT)
right = Frame(root, width=250, height=450, bg='blue').pack(side=RIGHT)
# ==================================================Buttons=======================================================
btn_clear_url = Button(right, text="Clear Url", font=('arial', 10, 'bold')).grid(row=1, columns=1)
I am trying to add buttons to the right Frame, but for some reason when I run the program the IDE shows that it is running but there is
no window. When I delete .grid(row=1, columns=1) the window appears.
How can I fix this bug, and add btn_clear_url button to the right Frame?
First of all, you need to invoke Tk's mainloop at the end of your code (you can see here why).
Another problem is chaining method calls like that. You're actually assigning return value of the last call to the variable, which is None in case of grid() and pack() - therefore, all your variables end up having None as the value. You need to separate widget instantiating call and grid or pack call and put each on its own line.
Other than that, you're setting both minsize and maxsize to the very same size - if you're really just trying to make your window not resizable, set the size with:
root.geometry('800x500') # for instance
...and after that configure resizable attribute:
root.resizable(width=False, height=False)
Also, I suggest you get rid of from tkinter import *, since you don't know what names that imports. It can replace names you imported earlier, and it makes it very difficult to tell where names in your program are supposed to come from. Use import tkinter as tk instead.
When you place widget in a tk window you cannot use grid and pack at the same time in the same window, so you use should use pack () instead of grid()
The problem starts with this line of code:
right = Frame(root, width=250, height=450, bg='blue').pack(side=RIGHT)
With that, right is set to None because .pack(side=RIGHT) returns None.
Next, you do this:
btn_clear_url = Button(right, ...)).grid(row=1, columns=1)
Because right is None, it's the same as Button(root, ...). Since you are already using pack for a widget in root, you can't also use grid.
The solution -- and best practice -- is to separate widget creation from widget layout:
top = Frame(root, width=800, height=50, bg='yellow')
bottom = Frame(root, width=800, height=50, bg='red')
left = Frame(root, width=550, height=450, bg='black')
right = Frame(root, width=250, height=450, bg='blue')
top.pack(side=TOP)
bottom.pack(side=BOTTOM)
left.pack(side=LEFT)
right.pack(side=RIGHT)
With the above, right will be properly set to the frame instance, and adding a widget to right and using grid will no longer fail.

Scrollbar on canvas

I'm currently trying to implement a scrollbar on a canvas, since I learned that I can't do it instantly on a frame. I can make it appear, but I cannot actually make it work. I'm still a beginner when it comes to python and tkinter and the previous posts in this matter haven't helped me that much. Here's my code(I'm open to advice on anything else I've done that's considered bad practice as well):
from tkinter import *
class myApp():
def __init__(self,root):
myApp.f2=Frame(root)
myApp.f2.pack()
myApp.canv=Canvas(self.f2)
myApp.canv.pack()
myApp.f1=Frame(self.canv)
myApp.f1.pack(side=LEFT, fill=BOTH, expand=TRUE)
myApp.scroll=Scrollbar(self.f1,orient=VERTICAL,
command=myApp.canv.yview)
myApp.scroll.grid(row=0,column=6)
myApp.canv.config(yscrollcommand=myApp.scroll.set)
I have to use grid for the rest of the widgets, that I haven't included here.
i don't really understand how the bind works, but here's the code i use for a scrollbar in a Toplevel, it's not from me but i don't remember where i found it (i think it's on stackoverflow, you should search more, i'm sure you will find something). it should work but you can scroll the bar only if you hover it with the mouse though
Toplevel = tk.Toplevel(self)
#create canvas to make a scrollbar
canvas = tk.Canvas(Toplevel, borderwidth=0)
#create frame which will contains your widgets
frame = tk.Frame(canvas)
#create and pack your vsb to the Toplevel and link it to the canvas yview
vsb = tk.Scrollbar(Toplevel, 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((5,5), window=frame, anchor="nw")
#i don't understand this line
frame.bind("<Configure>", lambda event, canvas=canvas: canvas.configure(scrollregion=canvas.bbox("all")))
#add your widgets to the frame
...
PS : Don't use from tkinter import * you can(and will) have name collisions, use import tkinter or import tkinter as tk
Edit : this question is my source.

In python3 tkinter, the wigdet frame doesn't show in interface

I use the same format of frame but it doesn't show in the interface, hope someone could tell me the solution, thanks.
class Interface(Frame):
def __init__(self,parent=None):
Frame.__init__(self,parent)
self.master.title("measurement")
self.grid()
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = Frame(self,relief=GROOVE,bg='white')
self.master.Frame1.grid(column=1,row=9)
self.can =Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = Canvas(self.master, width=150, height=120, background='snow')
ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = Tk()
window.resizable(False, False)
Interface(window).mainloop()
I can't figure out why you have 2 Canvas's, but the problem is that you aren't placing them on their respective parents. I cut out a lot of the code that seemed unnecessary and restructured your code to make it more logical:
class Interface(Frame):
def __init__(self, parent):
self.parent = parent
super().__init__(self.parent)
self.Frame1 = Frame(self, relief=GROOVE)
self.Frame1.grid()
self.canvas = Canvas(self.Frame1, bg="ivory", width=200, height=150)
self.canvas.grid()
self.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
root = Tk()
# Tk configurations are not relevant to
# the Interface and should be done out here
root.title('Measurement')
root.geometry('700x400+100+50')
root.resizable(False, False)
Interface(root).pack()
root.mainloop()
i think I don't really understand your problem, you don't see your frame because you don't have any widget in it, that's all
import tkinter as tk
class Interface(tk.Frame):
def __init__(self,parent=None):
tk.Frame.__init__(self,parent)
self.master.title("measurement")
self.grid(row=0, column=0)
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = tk.Frame(self,relief='groove',bg='white')
self.master.Frame1.grid(column=1,row=9)
labelExemple =tk.Label(self.master.Frame1, text="Exemple")
labelExemple.grid(row=0,column=0)
self.can = tk.Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = tk.Canvas(self.master, width=150, height=120, background='snow')
self.ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = tk.Tk()
window.resizable(False, False)
Interface(window).mainloop()
PS : use import tkinter as tk instead of from tkinter import *
There are several problems with those few lines of code, almost all having to do with the way you're using grid:
you aren't using the sticky option, so widgets won't expand to fill the space they are given
you aren't setting the weight for any rows or columns, so tkinter doesn't know how to allocate unused space
you aren't using grid or pack to put the canvases inside of frames, so the frames stay their default size of 1x1
The biggest problem is that you're trying to solve all of those problems at once. Layout problems are usually pretty simple to solve as long as you're only trying to solve one problem at a time.
Start by removing all of the widgets from Interface. Then, give that frame a distinctive background color and then try to make it fill the window (assuming that's ultimately what you want it to do). Also, remove the root.resizable(False, False). It's rarely something a user would want (they like to be able to control their windows), plus it makes your job of debugging layout problems harder.
Once you get your instance of Interface to appear, add a single widget and make sure it appears too. Then add the next, and the next, adding one widget at a time and observing how it behaves.

Resources