Canvas resize using Configure with Tkinter - python

I want to create a Board Game with Python and Tkinter
I want it to has a resize-function but I have two canvases for the GUI. First one is the square-Board (Spielfeld), the second one is the place where I want to add the control buttons for the player (Panel)
So if I want to resize my Board using <Configure> in my Master Window, it shall draw the Canvas with the New Size (self.FensterGroesse)
The If-Case is working well when I pass the else -function in resize
but if I run the Programm with the else function it resizes itself until its 1px big. Not just the canvas, the whole window.
I know the problem is the Panel being one third as high as the Board and when self.Panel.config sets the new size <Configure> is activated again.
But I dont know how I can have these two sanvases, one is a square, the other is a rectangle with the same widht and the square bit just 0.3*height
from Tkinter import *
class GUI:
def resize(self, event):
if event.height > (event.width*1.3):
self.FensterGroesse = event.width-2
else:
self.FensterGroesse = int(event.height/1.3)-2
self.Spielfeld.config(height=self.FensterGroesse, width=self.FensterGroesse)
self.Panel.config(height=self.FensterGroesse*0.3, width=self.FensterGroesse)
self.Spielfeld.pack()
self.Panel.pack()
def __init__(self):
self.FensterGroesse = 400
self.tkinter = __import__("Tkinter")
self.Master = self.tkinter.Tk()
self.Spielfeld = self.tkinter.Canvas(self.Master, height=self.FensterGroesse,
width=self.FensterGroesse, bg='#ffdead')
self.Panel = self.tkinter.Canvas(self.Master, height=self.FensterGroesse*0.3,
width=self.FensterGroesse, bg='brown')
self.Spielfeld.pack()
self.Panel.pack()
self.Master.bind("<Configure>", self.resize)
self.Master.mainloop()
GUI()

Related

Tkinter widgets created in an Update function run by tk.after function do not create untill the aforementioned Update ends

I intend to make a Py code which creates a tkinter dot that turns on a key press and deletes on a key press of couple keys.
The dot already is functional but i need it switch on and off on certain keypresses/mouse clicks which means i need an outside tkinter.mainloop() Update function.
The Update function with a while in it to constantly check if conditions to turn it off/on are present. But the Tkinter widget Somehow gets applied to the screen Only when the function nds. Like widget could be created but it will only take effect when function ends. And i need to turn it off/on dynamically.
I have tried to use a tkinter.after() with additional one at the end of called function only to find out an error of Recursion depth. What i expected to happen was that the function would be called over and over again, instead it runs that function like a while loop. I also have tried Asyncio.run() but it would result not making it visible till the function ends at least once. And I need to change it dynamically.
from tkinter import *
from tkinter import Canvas
from winsound import Beep
from time import sleep
import asyncio
import keyboard
import mouse
root = Tk()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
class tk_Dot():
def __init__(self,x=-1,y=-1,radius=4,color="red"):
self.x = x
if x == -1:
self.x = width/2-radius//2
print(self.x)
self.y = y
if y == -1:
self.y = height/2+radius//2
print(self.y)
self.radius=radius
self.color = color
self.lines = []
self.count = 1
def line(self,i):
return canvas.create_line(self.x, self.y-i, self.x+self.radius, self.y-i, fill=self.color)
def create(self):
self.lines = []
for i in range(0,self.radius):
self.lines.append(self.line(i))
def delete(self):
for i in range(0,self.radius):
canvas.delete(self.lines[i])
canvas.dtag(self.lines[i])
opacity_of_tk_window = 1 # From 0 to 1 0 meaning completely transparent 1 meaning everything created in canvas will give the color it was given
root.attributes('-alpha',opacity_of_tk_window)
# Invisible Tkinter window label
root.overrideredirect(True)
# Makes Transparent background
transparent_color = '#f0f0f0'
root.wm_attributes('-transparent', transparent_color)
canvas = Canvas()
# Rectangle filled with color that is specified above as the transparent color so practically making transparent background
canvas.create_rectangle(0, 0, width, height, fill=transparent_color)
canvas.pack(fill=BOTH, expand=1)
radius = 2
radius = 1+radius\*2
# Create a dot class
game_dot = tk_Dot(width/2-radius//2+1,height/2+1+radius//2,radius,"Red")
# Create a Dot at the middle of the calorant crosshair
# game_dot.create()
# Delete the dot
# game_dot.delete()
def Update():
game_dot.create()
print("Dot should be visible by now")
print("Is it?")
sleep(5) #sec
print("Oh yeah after the function ends.") # the problem
def Delete():
game_dot.delete()
root.geometry('%dx%d+%d+%d' % (width, height, -2,-2))
# Tkinter window always on top
root.attributes('-topmost',True)
root.after(1000,Update())
root.mainloop()

Control location on screen where new windows open with graphics.py

I have a python program that deploys a windows via graphics.py. The initial window opened by the GraphWin class opens in the top left corner of the screen. Subsequent calls to GraphWin cascade from the upper left to the lower right.
I'd like to control the placement of each window. (Example: Have all the windows open in a grid-layout so I can create a dashboard.)
I think there is no such method in graphics.py right now.
Ref: The Book and webpage.
If you want to stick to using graphics.py, I suggest creating a dashboard by dividing a single window into different slots.
This option does exist in Tkinter library. Please refer to this answer for more information on that.
graphics.py doesn't provide a way for you to control the location of instances of its GraphWin class. However the fact that it's built on top of Python's Tk GUI toolkit module named tkinter means that sometimes you can work around its limitations by looking at its source code to see how things operate internally.
For example, here's a snippet of code from the module (version 5.0) showing the beginning of GraphWin class' definition from the graphics.py file:
class GraphWin(tk.Canvas):
"""A GraphWin is a toplevel window for displaying graphics."""
def __init__(self, title="Graphics Window",
width=200, height=200, autoflush=True):
assert type(title) == type(""), "Title must be a string"
master = tk.Toplevel(_root)
master.protocol("WM_DELETE_WINDOW", self.close)
tk.Canvas.__init__(self, master, width=width, height=height,
highlightthickness=0, bd=0)
self.master.title(title)
self.pack()
master.resizable(0,0)
self.foreground = "black"
self.items = []
self.mouseX = None
self.mouseY = None
self.bind("<Button-1>", self._onClick)
self.bind_all("<Key>", self._onKey)
self.height = int(height)
self.width = int(width)
self.autoflush = autoflush
self._mouseCallback = None
self.trans = None
self.closed = False
master.lift()
self.lastKey = ""
if autoflush: _root.update()
As you can see it's derived from a tkinter.Canvas widget which has an attribute named master which is a tkinter.Toplevel widget. It then initializes the Canvas base class and specifies the newly created Toplevel window as its parent.
The size and position of a Toplevel window can be controlled by calling its geometry() method as described in the linked documentation. This method expects to be passed a "geometry string" argument in a certain format ('wxh±x±y').
This mean you can take advantage of how this implementation detail in order to put it anywhere you want it and as well as resize if desired.
Here's an example of doing that:
from graphics import *
def main():
win = GraphWin("My Circle", 100, 100)
# Override size and position of the GraphWin.
w, h = 300, 300 # Width and height.
x, y = 500, 500 # Screen position.
win.master.geometry('%dx%d+%d+%d' % (w, h, x, y))
c = Circle(Point(50,50), 10)
c.draw(win)
win.getMouse() # pause for click in window
win.close()
if __name__ == '__main__':
main()
My desktop while script is running:

Setting a background image within a set Tkinter animation framework

I am currently working on a fruit ninja project for a class. Everything functionally works fine, however, when I try to put in a background image for the game runs extremely slow. In order for the game to look polished, I need everything to work smoothly while having the background of the game show. Other solutions I have come across and have tried to understand simply do not work or the file never ends up running.
FYI: I am working in python 2.7.
I have tried some other suggestions for adding a background, such as using a label function, however, when I try to implement it I get a variety of errors and it just does not seem to work in my animation framework.
def run(width=300, height=300):
def redrawAllWrapper(canvas, data):
canvas.delete(ALL)
canvas.create_rectangle(0, 0, data.width, data.height,
fill='white', width=0)
redrawAll(canvas, data)
canvas.update()
def mousePressedWrapper(event, canvas, data):
mousePressed(event, data)
redrawAllWrapper(canvas, data)
def keyPressedWrapper(event, canvas, data):
keyPressed(event, data)
redrawAllWrapper(canvas, data)
def timerFiredWrapper(canvas, data):
timerFired(data)
redrawAllWrapper(canvas, data)
# pause, then call timerFired again
canvas.after(data.timerDelay, timerFiredWrapper, canvas, data)
# Set up data and call init
class Struct(object): pass
data = Struct()
data.width = width
data.height = height
data.timerDelay = 10 # milliseconds
root = Tk()
root.resizable(width=False, height=False) # prevents resizing window
init(data)
# create the root and the canvas
canvas = Canvas(root, width=data.width, height=data.height)
canvas.configure(bd=0, highlightthickness=0)
canvas.pack()
# set up events
root.bind("<Button-1>", lambda event:
mousePressedWrapper(event, canvas, data))
root.bind("<Key>", lambda event:
keyPressedWrapper(event, canvas, data))
timerFiredWrapper(canvas, data)
# and launch the app
root.mainloop() # blocks until window is closed
print("bye!")
run(1200, 700)
with my current framework, I write all the necessary code within the init, timerFired, redrawAll, keyPressed, and mousePressed functions above this run function.
With my current implementation of the background. I use PhotoImage on a 1200 x 700 gif file and draw the image across the whole screen in the redrawAll function (which is called every 10 milliseconds). Without drawing this one image, my game runs very smoothly, however, upon drawing the image in redrawAll, the game lags significantly, so I do know the source of the lag is drawing the background image.
Here is the line of code that draws it in redrawAll:
canvas.create_image(data.width//2, data.height//2, image = data.background)
Is this only because I do it in redrawAll which continuously draws the image every time the function is called making it slow? Is simply having an image that large in Tkinter making it slow? What is the source?
This there a way to simply draw the image once on the background and have it never change? Or is there any way to not have lag? I just find it odd. Again, this is in python 2.7 on a Mac.
Thanks!
You don't have to remove and add again all elements to refresh screen. You can move elements and canvas will draw it correctly
This code create 1000 small rectangles and move them randomly on background.
Tested with Python 3.7 but on 2.7 should work too.
With 5_000 rectangles it slows down but it still works good (but not perfect). With 10_000 it slows down too much.
from Tkinter import *
from PIL import Image, ImageTk
import random
IMAGE_PATH = 'background.jpg'
class Struct(object):
pass
def run(width=300, height=300):
def init(data):
# create 1000 rectangles in random position
for _ in range(1000):
x = random.randint(0, data.width)
y = random.randint(0, data.height)
data.data.append(canvas.create_rectangle(x, y, x+10, y+10, fill='red'))
def mousePressedWrapper(event, canvas, data):
#mousePressed(event, data)
pass
def keyPressedWrapper(event, canvas, data):
#keyPressed(event, data)
pass
def timerFiredWrapper(canvas, data):
# move objects
for rect_id in data.data:
x = random.randint(-10, 10)
y = random.randint(-10, 10)
canvas.move(rect_id, x, y)
# pause, then call timerFired again
canvas.after(data.timerDelay, timerFiredWrapper, canvas, data)
# Set up data and call init
data = Struct()
data.width = width
data.height = height
data.timerDelay = 10 # milliseconds
data.data = [] # place for small red rectangles
root = Tk()
root.resizable(width=False, height=False) # prevents resizing window
# create the root and the canvas
canvas = Canvas(root, width=data.width, height=data.height)
canvas.configure(bd=0, highlightthickness=0)
canvas.pack()
#canvas.create_rectangle(0, 0, data.width, data.height, fill='white', width=0)
img = Image.open(IMAGE_PATH)
img = img.resize((data.width, data.height))
photo = ImageTk.PhotoImage(img)
canvas.create_image(0, 0, image=photo, anchor='nw')
init(data) # init after creating canvas because it create rectangles on canvas
# set up events
root.bind("<Button-1>", lambda event:
mousePressedWrapper(event, canvas, data))
root.bind("<Key>", lambda event:
keyPressedWrapper(event, canvas, data))
timerFiredWrapper(canvas, data)
# and launch the app
root.mainloop() # blocks until window is closed
print("bye!")
run(1200, 700)

Feasible with Tkinter 'scrollbar'

I have a base program I was playing with this morning I wrote up a few years back. It only had horizontal scrolling and zooming. I managed to get it so I could scroll(one axis at a time) and zoom on both axis. I'm wanting to create a program that will allow me to move around the screen in both x and y directions at the same time(like Google Earth where you can hold down the down and left key at the same time to move to lower left). It would be nice to do it without having the scroll bars on the screen as well. I don't want to have to go back and forth and click on the appropriate scroll bar to be able to scroll in that axis. Currently to change which axis I'm scrolling in I have to click on the opposite axis.
I tried the program at [http://www.tkdocs.com/tutorial/canvas.html#scrolling][1] but I already have the capability of doing that. It doesn't allow me to scroll both directions simultaneously and if I want to change which direction I'm scrolling without having to click on the opposite axis.
Is there a way of doing what I'm trying to do with Tkinter or should I look elsewhere and if so, where?
Thanks.
edit:
With the code Bryan posted below I added in the following code to try to get it two work with the keyboard versus only the mouse. I would like the be able to use the cursor keys to move the image around versus the mouse. I have a nastily touch sensitive mouse on this computer, that has a mind of its own and as a result I would like to stick with the keyboard. Plus, given the naturedness of this darn project I have to leave all option open or else I know I will regret it sometime before this entire project gets finished.
self.canvas.bind("<Left>", self.on_press)
self.canvas.bind("<Right>", self.on_press)
I also tried directing it to self.on_motion and neither one accepted the cursor keys.
Yes, this is possible. There's nothing preventing you from directly calling the canvas xview and yview methods with any arguments you want.
You first need to create bindings that tracks the clicking and the motion of the mouse. In the bound function you can compute the direction that the mouse moved, then use the results to call both the xview and yview methods of the widget at the same time.
Here's an example:
import tkinter as tk
import random
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.canvas = tk.Canvas(self, background="bisque", width=400, height=400)
self.canvas.pack(fill="both", expand=True)
self.canvas.configure(scrollregion=(-1000, -1000, 1000, 1000))
self.canvas.bind("<ButtonPress-1>", self.on_press)
self.canvas.bind("<B1-Motion>", self.on_motion)
# the following two values cause the canvas to scroll
# one pixel at a time
self.canvas.configure(xscrollincrement=1, yscrollincrement=1)
# finally, draw something on the canvas so we can watch it move
for i in range(1000):
x = random.randint(-1000, 1000)
y = random.randint(-1000, 1000)
color = random.choice(("red", "orange", "green", "blue", "violet"))
self.canvas.create_oval(x, y, x+20, y+20, fill=color)
def on_press(self, event):
self.last_x = event.x
self.last_y = event.y
def on_motion(self, event):
delta_x = event.x - self.last_x
delta_y = event.y - self.last_y
self.last_x = event.x
self.last_y = event.y
self.canvas.xview_scroll(-delta_x, "units")
self.canvas.yview_scroll(-delta_y, "units")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

How to make a window fullscreen in a secondary display with tkinter?

I know how to make a window fullscreen in the "main" display, but even when moving my app's window to a secondary display connected to my PC, when I call:
self.master.attributes('-fullscreen', True)
to fullscreen that window, it does so in the "main" display and not in the secondary one (the app's window disappears from the secondary display and instantly appears in the "main" one, in fullscreen).
How can I make it fullscreen in the secondary display?
This works on Windows 7: If the second screen width and height are the same as the first one, you can use win1 or win2 geometry of the following code depending its relative position(leftof or rightof) to have a fullscreen in a secondary display:
from Tkinter import *
def create_win():
def close(): win1.destroy();win2.destroy()
win1 = Toplevel()
win1.geometry('%dx%d%+d+%d'%(sw,sh,-sw,0))
Button(win1,text="Exit1",command=close).pack()
win2 = Toplevel()
win2.geometry('%dx%d%+d+%d'%(sw,sh,sw,0))
Button(win2,text="Exit2",command=close).pack()
root=Tk()
sw,sh = root.winfo_screenwidth(),root.winfo_screenheight()
print "screen1:",sw,sh
w,h = 800,600
a,b = (sw-w)/2,(sh-h)/2
Button(root,text="Exit",command=lambda r=root:r.destroy()).pack()
Button(root,text="Create win2",command=create_win).pack()
root.geometry('%sx%s+%s+%s'%(w,h,a,b))
root.mainloop()
Try:
from Tkinter import *
rot = Tk()
wth,hgh = rot.winfo_screenwidth(),rot.winfo_screenheight()
#take desktop width and hight (pixel)
_w,_h = 800,600 #root width and hight
a,b = (wth-_w)/2,(hgh-_h)/2 #Put root to center of display(Margin_left,Margin_top)
def spann():
def _exit():
da.destroy()
da = Toplevel()
da.geometry('%dx%d+%d+%d' % (wth, hgh,0, 0))
Button(da,text="Exit",command=_exit).pack()
da.overrideredirect(1)
da.focus_set()#Restricted access main menu
Button(rot,text="Exit",command=lambda rot=rot : rot.destroy()).pack()
but = Button(rot,text="Show SUB",command=spann)
but.pack()
rot.geometry('%sx%s+%s+%s'%(_w,_h,a,b))
rot.mainloop()
""" Geometry pattern 'WxH+a+b'
W = Width
H = Height
a = Margin_left+Margin_Top"""
Super simple method working in 2021
This works even if both displays are different resolutions. Use geometry to offset the second display by the width of the first display. The format of the geometry string is <width>x<height>+xoffset+yoffset:
root = tkinter.Tk()
# specify resolutions of both windows
w0, h0 = 3840, 2160
w1, h1 = 1920, 1080
# set up a window for first display, if wanted
win0 = tkinter.Toplevel()
win0.geometry(f"{w0}x{h0}+0+0")
# set up window for second display with fullscreen
win1 = tkinter.Toplevel()
win1.geometry(f"{w1}x{h1}+{w0}+0") # <- this is the key, offset to the right by w0
win1.attributes("-fullscreen", True)
As long as you know the width of the first display, this will work fine. The X system TK runs on puts the second monitor to the right of the first one by default.
Windows, Python 3.8
In this solution, pressing F11 will make the window fullscreen on the current screen.
Note that self.root.state("zoomed") is Windows specific according to doc.
self.root.overrideredirect(True) is weird in Windows and may have unwanted side effects. For instance I've had issues related to changing screen configuration with this option active.
#!/usr/bin/env python3
import tkinter as tk
class Gui:
fullScreen = False
def __init__(self):
self.root = tk.Tk()
self.root.bind("<F11>", self.toggleFullScreen)
self.root.bind("<Alt-Return>", self.toggleFullScreen)
self.root.bind("<Control-w>", self.quit)
self.root.mainloop()
def toggleFullScreen(self, event):
if self.fullScreen:
self.deactivateFullscreen()
else:
self.activateFullscreen()
def activateFullscreen(self):
self.fullScreen = True
# Store geometry for reset
self.geometry = self.root.geometry()
# Hides borders and make truly fullscreen
self.root.overrideredirect(True)
# Maximize window (Windows only). Optionally set screen geometry if you have it
self.root.state("zoomed")
def deactivateFullscreen(self):
self.fullScreen = False
self.root.state("normal")
self.root.geometry(self.geometry)
self.root.overrideredirect(False)
def quit(self, event=None):
print("quiting...", event)
self.root.quit()
if __name__ == '__main__':
Gui()

Categories