tkinter canvas object tag_bind - object

I can't understand why I am getting no reaction to the tag_bind in code below: just want to get a handle on the tag_bind in order to elaborate on the event handler later. Nothing happens, and tkinter does not complain either. Thks for help.
import tkinter as tk
root = tk.Tk()
size = input("Choose a grid size eg. 4, 7, 12 ...: ")
size = int(size)
def create_grid():
d = 20
x0 = 20
x1 = x0 + d
y0 = 20
y1 = y0 + d
for i in list(range(size)):
for j in list(range(size)):
row = i
col = j
id = cv.create_rectangle(x0, y0, x1, y1,
tags="ALL",fill="white")
x0 = x1
x1 += d
x0 = d
x1 = x0 + d
y0 = y1
y1 = y0 + d
id = cv.create_oval(24, 24, 36, 36, tags=("ALL","oval"),fill="yellow")
def oval_move(event):
print(event.keysym)
cv = tk.Canvas(root, bd=5, relief="groove")
cv.pack(fill="both", expand=True)
cv.bind('<Configure>', create_grid())
cv.focus_set() # probably not needed
cv.tag_bind("oval", '<KeyPress-Down>', oval_move)
root.mainloop()

"keyboard focus" is what allows tkinter to know which object to send key events to. By default items on a canvas do not have keyboard focus. So, the first problem is that your oval won't see the key release because it doesn't have keyboard focus.
The second problem is that the only items on a canvas that can accept keyboard focus are text items. It's impossible to attach keyboard events to individual canvas items other than text objects. If you want non-text objects to react to key bindings, you must put the bindings on the canvas as a whole and then give the canvas the keyboard focus.

Try using:
tcv.bind(...)
Instead of:
tcv.tag_bind(...)

Related

How to get canvas current size with guiZero

I am aiming for make GUI that changes depending on the canvas size. I need to be able to actively check the window size so i know when to display extras.
Using
Python 3.8.2
GuiZero
You can use tkinter event <Configure> on the canvas:
def on_resize(event):
print(app.height)
...
app.tk.bind('<Configure>', on_resize)
I was finally able to make something but it does throw and error after the app quits.
w=None
while True:
x=app.tk.winfo_height()
if x!=w:
print(x)
w=app.tk.winfo_height()
app.update()
I came across this problem when creating a digital etch-a-sketch, using a Raspberry Pi and two potentiometers as the horizontal and vertical controls. How to get the current size of the canvas? Annoyingly when you set height and width to "fill" and then try to interrogate these values all you get is "fill", which is no use if you're trying to determine the upper bounds of the available canvas. I dug down into the object hierarchy and discovered that .tk_winfo_height() and .tk.winfo_width() return integer values. For this purpose I've removed the code that reacts to the twiddling of the potentiometers and put a row of buttons at the bottom of the screen to control the vertical and horizontal movement.
from guizero import App, Box, Drawing, PushButton
x = 0
y = 0
def clear_screen():
drawing.clear()
def move_left():
global x, y
if x > 0 :
drawing.line(x, y, x - 1, y)
x = x - 1
def move_right():
global x, y
if x < drawing.tk.winfo_width() :
drawing.line(x, y, x + 1, y)
x = x + 1
def move_up():
global x, y
if y > 0 :
drawing.line(x, y, x, y - 1)
y = y - 1
def move_down():
global x, y
if y < drawing.tk.winfo_height() :
drawing.line(x, y, x, y + 1)
y = y + 1
app = App()
drawing = Drawing(app, height="fill", width="fill")
drawing.bg="white"
bbox = Box(app, align="bottom")
lbtn = PushButton(bbox, align="left", command=move_left, text="Left")
ubtn = PushButton(bbox, align="left", command=move_up, text="Up")
cbtn = PushButton(bbox, align="left", command=clear_screen, text="Clear")
rbtn = PushButton(bbox, align="left", command=move_right, text="Right")
dbtn = PushButton(bbox, align="left", command=move_down, text="Down")
app.display()

Cannot remove 1px white strip - even after adding highlightthickness?

I followed this example since I was having issues removing white borders.
OP was told to add highlightthickness and change x0, y0 and y1 to 0. This worked all but for 1 px of white space/line? Any ideas? Thanks.
from tkinter import *
master = Tk()
master.configure(bg='#333333')
master.wm_attributes("-topmost", 1)
TopLevel.overrideredirect(True)
w = Canvas(master, width=150, height=40, bd=0, highlightthickness=0, relief='ridge',)
w.config()
w.pack(fill='both')
color = 100
x0 = 0
y0 = 0
x1 = 150
y1 = 0
while y0 < 20 :
r = color
g = color
b = color
rgb = r, g, b
Hex = '#%02x%02x%02x' % rgb
w.create_line(x0, y0, x1, y1,fill=str(Hex), width=1)
color = color - 2
y0 = y0 + 1
y1 = y1 + 1
color = 10
while y0 < 40 :
r = color
g = color
b = color
rgb = r, g, b
Hex = '#%02x%02x%02x' % rgb
w.create_line(x0, y0, x1, y1,fill=str(Hex), width=1)
color = color + 4
y0 = y0 + 1
y1 = y1 + 1
mainloop()
I changed the 4. and 7. line, because PyCharm gave me an error:
from tkinter import *
master = Tk()
tl = Toplevel() # delete me if the issue still remains :)
master.configure(bg='#333333')
master.wm_attributes("-topmost", 1)
tl.overrideredirect(True) # delete me if the issue still remains :)
...
if the issue still remains, try deleting 4. and 7. line.
Shouldn't make any problems

Function traceback says not receiving positional arguments

Issue begins at def displayAnswer
import tkinter as tk
#icftk stands for incompressible-flow toolkit
"""This program is being built to aid in getting all
parameters of a flow given certain initial conditions"""
#CREATES THE WINDOW
root = tk.Tk()
root.title("Incompressible Fluid Toolkit")
class flow:
"""This class contains all necessary parameters needed to define a flow that i
incompressible, 1D-Steady, idiabatic and encounters no energy gain or loss"""
def __init__(self,vel,diameter,density,viscosity,massflow = 0, Re = 0, newdia = 1, jetforce = 0, newvel = 0):
"""initialize a fluid with given basic measured properties"""
self.vel = vel
self.diameter = diameter
self.density = density
self.viscosity = viscosity
self.massflow = massflow # mass flow rate
self.Re = Re #Reynolds Number
self.newdia = newdia # downstream diameter for velocity change
self.jetforce = jetforce # force the stream can produce normal to a surface
self.newvel = newvel # new velocity after a cross sectional area change
def reynolds(self):
"""This function calculates reynolds
Pass ro, v, D and mu in the same unit system, in that order"""
self.Re = (self.diameter*self.vel*self.density)/(self.viscosity)
print(f"The Reynolds number for this flow is {self.Re}")
def mdot(self):
"""This function finds the mass flowrate of a flow"""
flowarea = 3.14159*(self.diameter**2) / 4
self.massflow = self.density*self.vel*flowarea
print(f"The mass flowrate is {self.massflow}")
def streamforce(self):
"""This function gives the max force that the fluid jet can apply
normal to a surface perpendicular to the flow"""
self.jetforce = self.massflow*self.vel
print(f"The maximum force the jet can apply is {self.jetforce}")
def velchange(self):
"""This function is used to determine the velocity change of
a flow when there is a change in cross sectional area of the pipe"""
newarea = 3.14159 * (self.newdia**2) / 4
self.newvel = self.massflow/(self.density*newarea)
print(f"At the location of the area change, there is a velocity change from {self.vel} to {self.newvel}")
#ALL ABOVE FUNCTIONS HAVE BEEN CONFIRMED TO WORK WITH GIVEN TEST CONDITIONS BELOW
#use test case velocity = 18.64, diameter = 0.017, density = 1.23, and viscosity = 0.0000184
#Display Entry Boxes
velo = tk.Label(root, text="Flow Velocity") # Create a text label
velo.grid(row = 0, column = 0, pady = 10) # Pack it into the window, padding determines how mach space is around a window element
veloent = tk.Entry()
veloent.grid(row = 0, column = 1, pady = 10)
diam = tk.Label(root, text="Pipe Diameter") # Create a text label
diam.grid(row = 1, column = 0, pady = 10) # Pack it into the window, padding determines how mach space is around a window element
diament = tk.Entry()
diament.grid(row = 1, column = 1, pady = 10)
dens = tk.Label(root, text="Fluid Density") # Create a text label
dens.grid(row = 2, column = 0, pady = 10) # Pack it into the window, padding determines how mach space is around a window element
densent = tk.Entry()
densent.grid(row = 2, column = 1, pady = 10)
visc = tk.Label(root, text="Fluid Viscosity") # Create a text label
visc.grid(row = 3, column = 0, pady = 10) # Pack it into the window, padding determines how mach space is around a window element
viscent = tk.Entry()
viscent.grid(row = 3, column = 1, pady = 10)
#Display answers at the bottom of the window
def displayAnswer(veloent,diament,densent,viscent):
ve = float(veloent)#gets velocity entry and turns it into a float
di = float(diament)#gets diameter entry and turns it into a float
de = float(densent)#gets density entry and turns it into a float
vi = float(viscent)#gets viscosity entry and turns it into a float
fluid = flow(ve,di,de,vi)
fluid.reynolds()
fluid.mdot()
fluid.streamforce()
reynoldsanswer = tk.Label(root, text = "f{fluid.reynolds}")
reynoldsanswer.grid(row = 5)
mdotanswer = tk.Label(root, text = "f{fluid.mdot}")
mdotanswer.grid(row = 6)
streamforceanswer = tk.Label(root, text = "f{fluid.streamforce}")
streamforceanswer.grid(row = 7)
calculatebutton = tk.Button(root,command = displayAnswer)
calculatebutton.grid(row = 4)
root.mainloop()
I am new to tkinter, trying to get experience designing simple GUI. I am using a button to initiate a calculation to obtain values about an incompressible flow. When the button is pressed, the console throws this error.
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/tkinter/__init__.py", line 1705, in __call__
return self.func(*args)
TypeError: displayAnswer() missing 4 required positional arguments: 'veloent', 'diament', 'densent', and 'viscent'
Similarly, if I try to convert the Entry into a float from a string outside of the function, the console throws a cannot convert string to float error.
Honestly not sure if all of the code is even right, but I'll cross these bridges one at a time.
Any insight is appreciated.
Best,
T
The problem is that your function requires parameters, but never uses them. When you call the function from a button, the button will not by default pass any options. That's why you get missing 4 required positional arguments - the function requires four, the button passes zero.
Since your function is actually doing the right thing and fetching the values it needs, there's no need to pass in the parameters. The simple fix is to simply remove them from the definition of the function:
def displayAnswer():
ve = float(veloent)#gets velocity entry and turns it into a float
di = float(diament)#gets diameter entry and turns it into a float
de = float(densent)#gets density entry and turns it into a float
vi = float(viscent)#gets viscosity entry and turns it into a float
...

how to make tkinter <Enter> event work when <button-1> is pressed?

I want to fill rectangles just like in paint. When mouse button is pressed I want every rectangle I enter to be filled and otherwise I want no event to take place.
Here Is my code:
from tkinter import Canvas
import tkinter
_width = 50
_height = 50
_size = 8
root = tkinter.Tk()
root.title("draw me a lovely matrix")
canv = Canvas(root, width=_width * _size, height=_height * _size)
class Wrapper:
btn1d = False
def set_btn1d(value):
print(value)
Wrapper.btn1d = value
def toggle_color(rect):
print('called')
if Wrapper.btn1d:
color = canv.itemcget(rect, 'fill')
canv.itemconfig(rect, fill=("#aaa" if color == '#fff' else '#fff'))
rects = []
canv.bind('<ButtonPress-1>', lambda e, value=True: set_btn1d(value))
canv.bind('<ButtonRelease-1>', lambda e, value=False: set_btn1d(value))
for i in range(_size):
for j in range(_size):
rect = canv.create_rectangle(_width * j, _height * i, _width * (j + 1), _height * (i + 1), fill="#fff", width=0)
rects.append(rect)
canv.tag_bind(rect, '<Enter>', lambda e, rect=rect: toggle_color(rect))
canv.pack()
root.mainloop()
The problem is that when I press the mouse button only the cell in which the mouse was pressed detects the entrance of mouse pointer(also the one in which mouse will be released at the end)
Any beneficial general advice about my code would be of course much appreciated.
There's no need to use <Enter> at all. Try:
from tkinter import Canvas
import tkinter
_width = 50
_height = 50
_size = 8
root = tkinter.Tk()
root.title("draw me a lovely matrix")
canv = Canvas(root, width=_width * _size, height=_height * _size)
rects = []
# B1-Motion activates every time the mouse is moved with button 1 held down
# See https://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
# We fill every item that the mouse comes in contact with while button 1 is held
# The tag tied to the item the event was called on, "current", points to the
# item under the pointer when the button was pressed, i.e. the first box.
# Only the x and y properties change, so we configure the item closest to those.
def motion(event):
canv.itemconfig(canv.find_closest(event.x, event.y), fill="#aaa")
canv.bind("<B1-Motion>", motion)
for i in range(_size):
for j in range(_size):
rects.append(canv.create_rectangle(_width * j, _height * i, _width * (j + 1), _height * (i + 1), fill="#fff", width=0))
# don't need to bind anything to <Enter>
canv.pack()
root.mainloop()
Unfortunately, there's no possible solution using <Enter>. The following non-working code would theoretically be correct if <Enter> worked the way you wanted it to.
import tkinter
_width = 50
_height = 50
_size = 8
root = tkinter.Tk()
root.title("draw me a lovely matrix")
canv = tkinter.Canvas(root, width=_width * _size, height=_height * _size)
rects = []
# This javascript-like approach doesn't work in tkinter because
# <Enter> and <Leave> events aren't called while any mouse buttons
# are held.
def fill(tag):
canv.itemconfig(tag, fill="#aaa")
def enter(event):
fill("current")
def mousedown(event):
fill("current")
canv.tag_bind("all", "<Enter>", enter, add="+")
def mouseup(event):
canv.tag_unbind("all", "<Enter>")
canv.bind("<Button-1>", mousedown)
canv.bind("<ButtonRelease-1>", mouseup)
for i in range(_size):
for j in range(_size):
rects.append(canv.create_rectangle(_width * j, _height * i, _width * (j + 1), _height * (i + 1), fill="#fff", width=0))
canv.pack()
root.mainloop()
Tested in python 3.8.2, tkinter 8.6

How to successfully detect when two moving labels touch each other in tkinter?

Here is my current detection code, but it simply doesn't work.
def collDetect(xE, yE):
global a, b
if xE in range(a - 20, a + 20) and yE in range(b - 20, b + 20):
uLose = Label(bg = "red", text = "YOU LOSE", font = "comic 48", fg = "black")
uLose.place(x = 35, y = 100)
mover.place_forget()
ball.place_forget()
return False
xE and yE are the x and y co-ordinates for the enemy label that you are supposed to avoid and a and b are the x and y for the player's label.
This function runs every time the movement subroutine for the enemy label loops.
I have tried a few different methods for this detection and I am beginning to suspect that it is just a limitation of tkinter, although I am probably wrong.
What is wrong here? Is it my code, or is tkinter not quick enough to calculate it in time?

Resources