Canvas resizing with window - good way to do it? - python

i created a window with a canvas inside. The canvas contains an rectangle. Both change their size through a callback together with the window size.
So my beginner question is: It works fine, but is this a good or common way to do this? Or is there a more efficient/common way?
from tkinter import* #
#creating instance of tkinter
obj = Tk()
#Set title of our window form
obj.title("MyFirst Window - WOW")
#Set dimension of form
x_size = 1200
y_size = 600
obj.geometry(str(x_size)+"x"+str(y_size))
obj.update()
w = Canvas(obj, width=x_size, height=y_size)
w.place(x=0,y=obj.winfo_height()-100)
w.create_rectangle(0, 0, obj.winfo_width(), 100, fill="#476042")
def callback(event):
print(str(obj.winfo_width())+'x'+str(obj.winfo_height()))
w.config(width=obj.winfo_width(),height=obj.winfo_height())
w.place(x=0,y=obj.winfo_height()-100)
w.create_rectangle(0, 0, obj.winfo_width(), 100, fill="#476042")
window = obj
window.bind("<Configure>", callback)
obj.mainloop()

No, this is not a good way to have the canvas resize. You should almost never use place. grid and pack make it much easier to create widgets that automatically resize.
For example, if you want the canvas to always be 100 pixels tall and fill the full width of the window, you can add it to obj like this:
w = Canvas(obj, width=x_size, height=100)
w.pack(side="bottom", fill="x")
As for the green rectangle, you have no choice but to use a binding on <Configure> if you want the rectangle to grow and shrink along with the canvas.
However, your callback creates a new rectangle every time it's called instead of modifying the coordinates of the existing rectangle. This is a memory leak, because your program will use more and more memory the longer it runs and the more often the window is resized. w.create_rectangle will return an identifier; you can use that identifier to later modify the rectangle.
Here's a simplified version of your code. I've changed the variable names to make it a bit easier to comprehend.
from tkinter import *
window = Tk()
window.title("MyFirst Window - WOW")
x_size = 1200
y_size = 600
window.geometry(str(x_size)+"x"+str(y_size))
window.update_idletasks()
canvas = Canvas(window, width=x_size, height=100, background="bisque")
canvas.pack(side="bottom", fill="x")
rect = canvas.create_rectangle(0, 0, window.winfo_width(), 100, fill="#476042")
def callback(event):
canvas.coords(rect, 0, 0, canvas.winfo_width(), 100)
canvas.bind("<Configure>", callback)
window.mainloop()

Related

Tkinter Frame not appearing

I wrote a short piece of code in tkinter in python to see if I could make a frame appear in my window. Here is the code below:
from tkinter import *
root = Tk()
root.title("Window")
root.state("zoomed")
root.config(bg="white")
winHeight = int(root.winfo_height())
winWidth = int(root.winfo_width())
controlFrame = Frame(root, bg="red")
controlFrame.pack()
root.mainloop()
I created one full-sized window with a background colour of white. The frame inside it is supposed to be red. However, when I run this code, I do not see any red. I am sure I packed it and everything.
I'd love to help you out on this one...
There's just a slight detail that you might not notice right now but the frame, in fact, is present in the window, but it's too small to see. By this I mean you haven't specified the height and width of the frame that you have placed in the window. Here's the fixed version:
from tkinter import *
root = Tk()
root.title("Window")
root.state("zoomed")
root.config(bg="white")
winHeight = int(root.winfo_height())
winWidth = int(root.winfo_width())
controlFrame = Frame(root, bg="red", height = 700, width = 700)
controlFrame.pack()
root.mainloop()
What this will do is just set the height and width of the frame to 700px, so you will get a square frame of red colour.
I hope this answer was satisfactory.
The answer is pretty simple, you don't have any other widget in your frame, it's empty for now, so its size is 0 pixel (or 1, I don't remember). That's why you don't see it in your window.

How to find out the width of a tkinter window without an update?

I want to center a tkinter window on the screen, which can be done with:
root.geometry(f"+{(root.winfo_screenwidth()-root.winfo_width())//2}+"
f"{(root.winfo_screenheight()-root.winfo_height())//2}")
This is using the screen width and the width of the window to calculate the upper left corner. However, in order to find out the window width, I have to run root.update() as shown in the following example, which leads to the window showing up at a wrong position for a tiny moment.
import tkinter as tk
root = tk.Tk()
for i in range(20):
tk.Label(root, text='Hello World! '*5).pack()
# without the following line, the window dimensions are not being calculated
root.update()
root.geometry(f"+{(root.winfo_screenwidth()-root.winfo_width())//2}"
f"+{(root.winfo_screenheight()-root.winfo_height())//2}")
root.mainloop()
To avoid this, I can think of two solutions:
defining the window size in pixels, which means that the window size does not adjust automatically anymore, and
doing something like root.update() without the window being visible.
I don't know how to avoid the call to update(), but you could initially make the window completely transparent, which would prevent it from even momentarily showing up in the wrong position — thereby granting you the opportunity to position it properly and manually making it visible.
import tkinter as tk
root = tk.Tk()
# This changes the alpha value (how transparent the window should be).
# It ranges from 0.0 (completely transparent) to 1.0 (completely opaque).
root.attributes("-alpha", 0.0)
for i in range(20):
tk.Label(root, text='Hello World! '*5).pack()
tk.Button(root, text='Quit', command=root.quit).pack()
root.update() # Allow the window's size to be calculated,
# Move it so it's centered on the screen.
root.geometry(f"+{(root.winfo_screenwidth()-root.winfo_width())//2}"
f"+{(root.winfo_screenheight()-root.winfo_height())//2}")
# Now make it visible.
root.attributes("-alpha", 1.0)
root.mainloop()
The simplest way to achieve a clean window display is shown in the following code.
withdraw the master immediately, create widgets, update master before centering window and finally deiconify.
Works every time.
import tkinter as tk
def screencenter(o):
w, h = o.winfo_width(), o.winfo_height()
x = int((o.winfo_screenwidth() - w) / 2)
y = int((o.winfo_screenheight() - h) / 2)
o.geometry(f'{w}x{h}+{x}+{y}')
master = tk.Tk()
master.withdraw()
# create whatever widgets you need
master.update()
screencenter(master)
master.deiconify()
master.mainloop()
What you're looking for is root.withdraw() and root.deiconify().
The former will hide your window from view and the latter will show it.
I've included a full example below.
from tkinter import Tk
def show_it():
height = root.winfo_height()
width = root.winfo_width()
root.geometry(f"+{(s_width - width)//2}+"
f"{(s_height - height)//2}")
# show it again
root.deiconify()
root = Tk()
s_height = root.winfo_screenheight()
s_width = root.winfo_screenwidth()
root.withdraw()
# hide the window
root.after(200, show_it)
root.mainloop()

Canvas does not show as expected

I'm a newbie to coding, so I assume this is simply a logic error on my end, so forgive me for my inabilities. I am intending for an initial canvas to appear to act as a sort of splash screen for my program, but rather than appearing, the canvas does not show at all, and simply stays invisible until the second canvas is called (after 4 seconds).
root = tk.Tk()
loadScreen = tk.Canvas(root, height = 700, width = 700, bg = "Black")
loadImage = PhotoImage(file="LoadScreen.gif")
load = loadScreen.create_image(2, 2, anchor = NW, image=loadImage)
loadScreen.pack()
root.after(4000, loadScreen.pack_forget())
Usually you would call mainloop() to make sure your windows are updating.
Internally, mainloop() calls
tk.update_idletasks()
tk.update()
I think you can use these to show the window.

Tkinter canvas flickering

I'm attempting to implement a simple Pong game in Python using Tkinter, but unfortunately I'm having some major issues with flickering. I'm using a Canvas widget that covers the entire window and I'm drawing rectangles on said canvas many times per second. When I do this all drawn rectangles flicker regularly while the game is running, disappearing for a fraction of a second before appearing again.
A simple example of the logic I use for drawing in my game can be seen below, by running it you should be able to see the flickering in action:
from tkinter import *
import threading, time
def loop():
FRAME_TIME = 1 / 60
while True:
render()
time.sleep(FRAME_TIME)
def render():
canvas.delete(ALL)
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill='black')
WIDTH = 800
HEIGHT = 600
root = Tk()
canvas = Canvas(root, width=800, height=600)
canvas.pack()
threading.Thread(target=loop, daemon=True).start()
root.mainloop()
Any ideas as to what is causing it?
The threading is totally not needed for your script (The Tkinter doesn't love the threading).
You should use the widget.after() instead of infinite for loop.
I guess you should define the canvas and the rectangle on the canvas and in a function you should move the other widgets. In this case you shouldn't delete/recreate the widgets.
The black "background" (rectangle) is static and it is not updated during the script running. An oval widget has been created on the canvas (create_oval()) and this widget moves in the render function (randomly change the X-Y positions between -3 and 3).
The canvas.after(10, render) means to call again-and-again the render function in every 10 secs. So actually it is an animation and the oval will move always on your canvas.
Example:
from tkinter import *
import random
def render():
canvas.move(oval, random.randint(-3, 3), random.randint(-3, 3))
canvas.after(10, render)
WIDTH = 800
HEIGHT = 600
root = Tk()
canvas = Canvas(root, width=800, height=600)
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill="black")
oval = canvas.create_oval(400, 400, 410, 410, fill="red")
canvas.pack()
render()
root.mainloop()
GUI:
I think it is a good starting point for you to implement the complete pong game.

Creating Frame inside a Canvas

I am trying to make a frame scrollable, and the only way I found to do this is making a scrollable canvas and adding a frame to it. This would work fine, if it worked for me.
I am able to create a scrollable canvas that works fine, but I can't seem to properly add a frame inside of it:
self.title = Label(root, text="Brnr", font=("Helvetica", 50), anchor = W, pady = 40, padx = 50)
self.title.pack (anchor = NW)
#creates title widget for title
self.frame = Frame(screen, bd =1)
self.frame.pack(fill = BOTH)
#Creates frame widget under which all other widgets will be kept
self.canvas = Canvas(self.frame, bd=1,scrollregion=(0,0, 1000, 1000), height = 600)
#creates canvas so that screen can be scrollable
self.scrollbar = Scrollbar(self.frame, command=self.canvas.yview)
#creates scrollbar
self.canvas.config(yscrollcommand=self.scrollbar.set)
#connects the scrollbar to the canvas
self.scrollbar.pack(side=RIGHT, fill=Y)
self.canvas.pack(expand=YES, fill=BOTH)
#packs the scrollbar and canvas so that they fill the remainder of the screen
self.frameC = Frame(bg = "red")
self.canvas.create_window(0,0, anchor = NW, window = self.frameC, width = 200, height = 200)
#creates window on the scrollable area to add other widgets
self.frameC.pack()
self.groupRec = LabelFrame(self.frameC, text ="Recommendations:", font=("Helvetica", 20))
self.groupRec.pack()
self.signupButton = Button(self.groupRec, text="Sign Up", width=10)
self.signupButton.pack(side=RIGHT)
#creates button to submit login
This gives me a scrollable, but empty, canvas, with none of the labelframe/button appearing.
By default, when you add a window to a canvas, the center of the window will be at the coordinates you give. Thus, the center of your frame will be at 0,0 which is the upper-left corner of the canvas. You can't see the widgets because they are outside the borders of the canvas.
The solution is to include anchor="nw" in the call to create_window, which will place the upper-left corner of your frame in the upper left corner of your canvas.
Don't forget to set the scroll region of the canvas to match the size of your frame. The easiest way to do that is with the command self.canvas.config(scrollregion=self.canvas.bbox("all")). You'll probably also need to add a binding to <Configure> on the canvas so that you can resize the inner frame when the user resizes the window. That's not always necessary, it depends a bit on exactly what you are trying to accomplish.
Here's a pro tip: to debug problems like this it's really helpful to temporarily give your frame and canvas different colors to more easily visualize what is happening.
Don't re-invent the wheel. Install Pmw (Python meta-widgets), assuming you are using Tkinter, http://pmw.sourceforge.net/ and use Pmw.ScrolledFrame.

Categories