I want to make a shape move left if I leftclick my mouse , and go right if I click right. The following code is not working. What have I done wrong ?
from tkinter import *
import time
tk =Tk()
canvas = Canvas(tk, width=500, height=500,)
tk.title("Drawing")
xbat = 0
def clickL(event):
xbat =- 1
print(xbat)
def clickR(event):
xbat =+ 1
print(xbat)
canvas.bind("<Button-1>", clickL)
canvas.bind("<Button-3>", clickR)
canvas.pack()
bat = canvas.create_rectangle(150, 500, 300, 480, fill="black")
while True:
canvas.move(bat,xbat,0)
tk.update()
time.sleep(0.01)
canvas.mainloop()
Try this
from tkinter import *
tk =Tk()
canvas = Canvas(tk, width=500, height=500,)
tk.title("Drawing")
def clickL(event):
xbat = -1
print(xbat)
xmove(bat,xbat)
def clickR(event):
xbat = 1
print(xbat)
xmove(bat,xbat)
def xmove(object,x):
canvas.move(object,x,0)
canvas.bind("<Button-1>", clickL)
canvas.bind("<Button-3>", clickR)
canvas.pack()
bat = canvas.create_rectangle(150, 500, 300, 480, fill="black")
canvas.mainloop()
It is typically bad practice to have a while True loop in tkinter code, it doesn't give the GUI time to update. Instead I've changed you code so that each click event calls a separate xmove function.
An alternative approach could be to change the coordinate of the object when the button is clicked then have a periodic update function which re-draws/moves the objects to their update positions. This periodic update could be achieved with the tkinter .after method.
Related
I want to pack some func. in a single widget so that I can interact with that particular widget using bind func.
There's Frame widget which packs up widgets in it and canvas.create_window func. in Canvas widget which also does the same as Frame.
Following program generates sticman after every 3 sec. And when user click the stickman, it disappears.
I tried using Frame to pack functions 🙁...
from Tkinter import *
root = Tk()
hitbox = Frame(root, height = 100, width= 100)
hitbox.pack()
can = Canvas(hitbox, height= 450, width= 1000) # Canvas inside hitbox
can.pack()
def stickman (a,b,c,d):
# Code that makes stickman according to coordinates
def HP(p,q):
# Code that makes Progressbar widget inside hitbox which act as healthbar
counter = 0
def init():
if counter == 10:
pass
else:
counter +=1
stickman(100,100,130,130)
HP(90, 120)
root.after(3000, init) # Stickman respawns after 3 seconds
def kill():
hitbox.destroy()
hitbox.bind('<Button-1>', kill)
root.mainloop()
Stickman respawns after every 3 seconds but bind func. with frame does not seems to be working when running code. Stickman doesn't disappears when clicked.
I think what you are asking for is to destroy the Tkinter frame when Button 1 or the left button on the mouse is clicked. The stickman doesn't disappear because the frame isn't actively listening for the key click unlike a label or an entry field would do.
So, there are 2 easy fixes for this problem:
1. Binding to Root Window
Binding the keybind to the root window would solve the problem mostly because the root window is always actively listening for keybinds unlike the frame or an input field. The only problem with this approach is that the user can click anywhere on the window to destroy the frame.
2. Binding to the Stickman Itself
Binding the keybind to the stickman itself is the cleanest approach because it would be just the same as the first solution but this time the user can only click on the stickman to destroy the frame. This is probably the solution you were looking for. To implement this solution just replace the root.bind('<Button-1>', kill) with stickman.bind('<Button-1>', kill) (or whatever the name is of your stickman is) after defining the stickman but before packing it.
I atatched a modified version of your code down below for the first option:
from tkinter import *
root = Tk()
def kill(event=None):
hitbox.destroy()
root.bind('<Button-1>', kill)
hitbox = Frame(root, height = 100, width= 100)
hitbox.pack()
can = Canvas(hitbox, height= 450, width= 1000) # Canvas inside hitbox
can.pack()
def stickman (a,b,c,d):
pass
# Code that makes stickman according to coordinates
def HP(p,q):
pass
# Code that makes Progressbar widget inside hitbox which act as healthbar
counter = 0
def init():
if counter == 10:
pass
else:
counter +=1
stickman(100,100,130,130)
HP(90, 120)
root.after(3000, init) # Stickman respawns after 3 seconds
root.mainloop()
Why does the time.sleep() work before the window of tkinter opens?
Code:
import tkinter
import time
window = tkinter.Tk()
window.geometry("500x500")
window.title("Holst")
holst = tkinter.Canvas(window, width = 450, height = 450, bg = "white")
holst.place(x = 25, y = 25)
x = 30
y = 50
d = 30
circle = holst.create_oval(x, y, x+d, y+d, fill = "red")
time.sleep(2)
holst.move(circle, 50, 40)
You asked that why is time.sleep() is called before the windows loads
because you hopefully called the window.mainloop() at the last of the code which loads the window and keep maintain the tkinter window
The time.sleep() function code executes before the window.mainloop() function so it was stop the execution and sleeps before the window could load
A nice approach will be to call the time.sleep() in a if statement.
The Tk instance requires you to run it's mainloop function in order to take control of the main process thread.
Your code is calling time.sleep() on the main process thread, which blocks the GUI from doing anything. If you want to be able to do things with the GUI while waiting (such as drawing the window, moving it around, or drawing other things to it) then you would need to extend Tk to have the UI handle the callback using self.after()
Here's a simple example of how you would extend the Tk class to achieve what you want.
import tkinter
class TkInstance(tkinter.Tk):
def __init__(self):
tkinter.Tk.__init__(self)
#Set up the UI here
self.canvas = tkinter.Canvas(self, width = 450, height = 450, bg = "white")
self.canvas.place(x = 25, y = 25) #Draw the canvas widget
#Tell the UI to call the MoveCircle function after 2 seconds
self.after(2000, self.MoveCircle) #in ms
def MoveCircle(self):
x = 30
y = 50
d = 30
circle = self.canvas.create_oval(x, y, x+d, y+d, fill = "red")
self.canvas.move(circle, 50, 40) #Draw the circle
#Main entrance point
if __name__ == "__main__": #good practice with tkinter to check this
instance = TkInstance()
instance.mainloop()
I was trying to create a program inside tkinter that draws different patterns based on user input. I want a 'clear' button on my window that can clear everything on the window. I tried 'turtle.clear" and 'turtle.reset', they work but they open a new turtle window which I dont want and also that I am using tut = turtle.RawTurtle(). What can I do to try to fix this problem?
I use the following sample code:
from tkinter import *
from turtle import *
root = Tk()
tut = None
def reset_button():
tut.reset()
tut.hideturtle()
def draw_again():
tut.speed('fastest')
tut.color('blue', 'yellow')
tut.begin_fill()
while True:
tut.forward(200)
tut.left(170)
if abs(tut.pos()) < 1:
break
tut.end_fill()
button1 = Button(text = 'Reset', command = reset_button)
button1.pack()
button2 = Button(text = 'Draw', command = draw_again)
button2.pack()
canvas_Main = Canvas(root, bg='#ffffff', width = 500, height = 500)
canvas_Main.pack()
tut = RawTurtle(canvas_Main)
tut.speed('fastest')
tut.color('red', 'yellow')
tut.begin_fill()
while True:
tut.forward(200)
tut.left(170)
if abs(tut.pos()) < 1:
break
tut.end_fill()
root.mainloop()
I am trying to make a program that lets me draw on a tkinter window using turtle. For some reason I cannot get the absolute mouse coordinates.
I have done root.winfo_pointerx() - root.winfo_rootx() (and vrootx).
I have also tried:
def mousePos(event):
x,y = event.x , event.y
return x,y
My code:
import turtle
import tkinter as tk
root = tk.Tk()
root.title("Draw!")
cv = tk.Canvas(root, width=500,height=500)
cv.focus_set()
cv.pack(side = tk.LEFT)
pen = turtle.RawTurtle(cv)
window = pen.getscreen()
def main():
window.setworldcoordinates(-500,-500,500,500)
window.bgcolor("white")
frame = tk.Frame(root)
frame.pack(side = tk.RIGHT,fill=tk.BOTH)
pointLabel = tk.Label(frame,text="Width")
pointLabel.pack()
def getPosition(event):
x = root.winfo_pointerx()-root.winfo_vrootx()
y = root.winfo_pointery()-root.winfo_vrooty()
pen.goto(x,y)
cv.bind("<Motion>", getPosition)
cv.pack
tk.mainloop()
pass
I want the cursor to be on top of the arrow, but instead it is always to the right and down. Also, when I move the mouse up, the arrow moves down, and vice versa.
Think hard about how you set the setworldcoordinate(). -500 - 500 means your world has 1,000 in size and window size is 500. Also, the mouse pointer offset from the window root - both absolute coordinates should be used. You mixed up the absolute coordinates - mouse pointer and vrootx which is in different scale so the distance of two makes no sense. Following code is probably closer to what you intended. Note that, I set the world coordinate to match the absolute coordinates of mouse pointer offset from the top/left corner of window.
import turtle
import tkinter as tk
root = tk.Tk()
root.title("Draw!")
cv = tk.Canvas(root, width=500,height=500)
cv.focus_set()
cv.pack(side = tk.LEFT)
pen = turtle.RawTurtle(cv)
window = pen.getscreen()
def main():
window.setworldcoordinates(0,500,500,0)
window.bgcolor("white")
frame = tk.Frame(root)
frame.pack(side = tk.RIGHT,fill=tk.BOTH)
pointLabel = tk.Label(frame,text="Width")
pointLabel.pack()
print(dir(root))
def getPosition(event):
x = root.winfo_pointerx()-root.winfo_rootx()
y = root.winfo_pointery()-root.winfo_rooty()
print(x, y)
pen.goto(x,y)
pass
cv.bind("<Motion>", getPosition)
cv.pack
tk.mainloop()
pass
if __name__ == "__main__":
main()
pass
You've got an issue working against you that isn't of your own making. The general rule is when in a turtle canvas, use turtle methods. But turtle doesn't have an inherent 'Motion' event type, so you were trying to use the raw Canvas one as a substitute. Thus the conflict.
An issue of your own making is that when you're inside a fast moving event handler, you need to disable the event hander as the first thing you do, reenabling on exit. Otherwise, events overlap and bad things happen. (Inadvertant recursions and other wierdness.)
I've rewritten your program below to work as I believe you intended. The fix is adding the missing turtle method so we can stay within the turtle domain:
import tkinter as tk
from turtle import RawTurtle, TurtleScreen
from functools import partial
def onscreenmove(self, fun, add=None): # method missing from turtle.py
if fun is None:
self.cv.unbind('<Motion>')
else:
def eventfun(event):
fun(self.cv.canvasx(event.x) / self.xscale, -self.cv.canvasy(event.y) / self.yscale)
self.cv.bind('<Motion>', eventfun, add)
def getPosition(x, y):
screen.onscreenmove(None) # disable events inside handler
pen.setheading(pen.towards(x, y))
pen.goto(x, y)
screen.onscreenmove(getPosition) # reenable handler on exit
root = tk.Tk()
root.title("Draw!")
cv = tk.Canvas(root, width=500, height=500)
cv.focus_set()
cv.pack(side=tk.LEFT)
screen = TurtleScreen(cv)
screen.onscreenmove = partial(onscreenmove, screen) # install missing method
pen = RawTurtle(screen)
frame = tk.Frame(root)
frame.pack(side=tk.RIGHT, fill=tk.BOTH)
tk.Label(frame, text="Width").pack()
screen.onscreenmove(getPosition)
screen.mainloop()
Mouse position for Tkinter:
import Tkinter as tk
root = tk.Tk()
def motion(event):
x, y = event.x, event.y
print('{}, {}'.format(x, y))
root.bind('<Motion>', motion)
root.mainloop()
Mouse position for turtle:
canvas = turtle.getcanvas()
x, y = canvas.winfo_pointerx(), canvas.winfo_pointery()
Hope this helps.
Here is my code:
from tkinter import *
import time
master=Tk()
w=Canvas(master,width=500,height=500)
w.pack()
line=w.create_line
rect=w.create_rectangle
oval=w.create_oval
poly=w.create_polygon
rect(0,100,500,150,fill="#770077")
for x in range(0,51):
rect(0,100,x*10,150,fill="#007700")
w.after(100)
I've tried using the time.sleep() function, but that did not help, so I tried tkinter's wait function, and that didn't do anything either. This is for a class, but no one from the class has any ideas either.
Create function which draws rectangle using x and which uses after() to run itself again after 100ms with bigger x
import tkinter as tk
def draw_next_rect(x):
w.create_rectangle(0, 100, x*10, 150, fill="#007700")
x += 1
# stop animation with x==51
if x < 51:
master.after(100, draw_next_rect, x)
master = tk.Tk()
w = tk.Canvas(master, width=500, height=500)
w.pack()
w.create_rectangle(0, 100, 500, 150, fill="#770077")
# start animation with x=0
draw_next_rect(0)
master.mainloop()
EDIT: similar with ttk.Progressbar
import tkinter as tk
import tkinter.ttk as ttk
def draw_next_rect():
progressbar.step()
if progressbar['value'] < 50:
master.after(100, draw_next_rect)
master = tk.Tk()
progressbar = ttk.Progressbar(master, maximum=50.001, mode='indeterminate')
progressbar.pack()
draw_next_rect()
maximum= has to be little bigger then 50 because for 50 it removes bar when value == maximum
master.mainloop()