Using Tkinter to draw and track a mouse pointer - python

im still learning to use Python and Tkinter.
I have created a bit of code which (i thought) should create a canvas with 2 dots on and afterwards continuously printers the position of the mouse curser
from tkinter import *
from win32 import win32gui
win = Tk()
def mouse_pos():
flags, hcursor, (x, y) = win32gui.GetCursorInfo()
return {"x": x, "y": y}
win.geometry("1500x900")
win.configure(background="black")
g_circle = Canvas(win, width=100, height=100, bg="black", bd=1, highlightthickness=0)
g_circle.place(x=100, y=100, in_=win)
g_circle.create_oval(50, 50, 100, 100, fill="green", offset="200,200", outline="white")
b_circle = Canvas(win, width=100, height=100, bg="black", bd=1, highlightthickness=0)
b_circle.place(x=1300, y=700, in_=win)
b_circle.create_oval(50, 50, 100, 100, fill="blue", outline="white")
while True:
print(mouse_pos())
win.mainloop()
I know there is an infinite loop but i am just testing it for now.
This issue is that when i run this code a TK window opens of the canvas with 2 circles and then a cmd displays an single value for x and y coordinate in text. The coordinates do not continue to update unless i close the TK window and i dont know why.
Ill post a screenshot in hopes it helps.
Any help is appreciated.

win.mainloop() will block the while loop until the main window is closed.
You can use .after() to replace the while loop:
...
def mouse_pos():
# no need to use external module to get the mouse position
#flags, hcursor, (x, y) = win32gui.GetCursorInfo()
x, y = win.winfo_pointerxy()
print({"x": x, "y": y})
# run mouse_pos() again 10 microseconds later
win.after(10, mouse_pos)
...
''' don't need the while loop
while True:
print(mouse_pos())
win.mainloop()
'''
# start the "after loop"
mouse_pos()
win.mainloop()

Related

How to combine tkinter and turtle?

I have a part of codes like this
import turtle
turtle.bgcolor("green")
draw = turtle.Turtle()
draw.speed(1000000)
draw.hideturtle()
draw.pensize(3)
draw.color("white")
def Board (a, x, y, size):
draw.pu()
draw.goto(x, y)
draw.pd()
for i in range (0, 4):
draw.forward(size)
draw.right(90)
x =-40
y = -40
size = 40
for i in range (0, 10):
for j in range (0, 10):
Board (draw, x + j*size, y + i*size, size)
turtle.done()
And like this
import tkinter
import tkinter.messagebox
window = tkinter.Tk()
def Button_click ():
tkinter.messagebox.showinfo("Game", "Tic Tac Toe")
button = tkinter.Button(window, text = "Play!", command = Button_click)
button.pack()
window.mainloop()
Since I'm trying to create a window with a button to enter the TicTacToe game (I haven't finished the rest, just only the board). Is there any way that I can do to combine both turtle and tkinter?
Thank you
Yes. Python turtle operates in two modes, standalone and embedded in a larger tkinter program. Instead of Turtle and Screen, when using turtle embedded, you work with RawTurtle, TurtleScreen and optionally ScrolledCanvas. You build your tkinter interface as needed, using a Canvas to contain your turtle graphics. You can find examples of embedding turtle in tkinter by searching SO for RawTurtle.
Here's an example of the same code written both embedded and standalone.
I was playing with your code while cdlane was answering your question! As cdlane said I replaced "turtle" with "RawTurtle" and put your button on the same window as the canvas. I prefer using grid than pack when placing things because I feel like I have more control.
import tkinter
import turtle
import tkinter.messagebox
window = tkinter.Tk()
canvas = tkinter.Canvas(master = window, width = 800, height = 800)
canvas.grid(padx=2, pady=2, row=0, column=0, rowspan=10, columnspan=10) # , sticky='nsew')
#draw = turtle.Turtle()
draw = turtle.RawTurtle(canvas)
def Board(a, x, y, size):
#draw.pu()
draw.penup()
draw.goto(x,y)
#draw.pd()
draw.pendown()
for i in range (0, 4):
draw.forward(size)
draw.right(90)
def Board2():
x =-40
y = -40
size = 40
for i in range (0, 10):
for j in range (0, 10):
Board(draw, x + j*size, y + i*size, size)
def Button_click ():
tkinter.messagebox.showinfo("Game", "Tic Tac Toe")
#button = tkinter.Button(window, text = "Play!", command = Button_click)
#button = Tk.Button(window, text = "Play!", command = Button_click)
#button.pack()
#
Play_Button = tkinter.Button(master = window, text ="Play!", command = Button_click)
Play_Button.config(bg="cyan",fg="black")
Play_Button.grid(padx=2, pady=2, row=0, column=11, sticky='nsew')
Board_Button = tkinter.Button(master = window, text ="Draw_Board", command = Board2)
Board_Button.config(bg="cyan",fg="black")
Board_Button.grid(padx=2, pady=2, row=1, column=11, sticky='nsew')
#
window.mainloop()

Canvas.Tag_bind not working with OOP | Python 3 [duplicate]

In my simple code, a red ball is falling down in a straight line (that's working). When I push the right arrow key, I want the ball to also move in right direction. This is not working, however. What am I doing wrong?
from tkinter import *
root = Tk()
canvas = Canvas(root, height=400, width=500, background='black')
canvas.pack()
class Bird:
def __init__(self, canvas, coords):
self.canvas = canvas
self.coords = coords
self.bird = canvas.create_rectangle(coords, fill='red')
def gravity(self):
self.canvas.move(self.bird, 0, 10)
self.canvas.after(200, self.gravity)
def moveRight(self, event):
self.canvas.move(self.bird, 10, 0)
self.canvas.after(200, self.moveRight)
bird = Bird(canvas, (100, 100, 110, 110))
bird.gravity()
canvas.bind('<Right>', bird.moveRight)
root.mainloop()
I have another additional question:
Is it possible to call this "after"-function or a similar function for the whole canvas instead of the two methods separately?
If you see any other flaws with my code plz let me know!
Thanks!
You must bind the right key to the canvas inside the class, and set the focus on the canvas:
from tkinter import *
root = Tk()
canvas = Canvas(root, height=400, width=500, background='black')
canvas.pack()
class Bird:
def __init__(self, canvas, coords):
self.canvas = canvas
self.coords = coords
self.bird = canvas.create_rectangle(coords, fill='red')
self.canvas.bind('<Right>', self.moveRight)
self.canvas.focus_set()
def gravity(self):
self.canvas.move(self.bird, 0, 10)
self.canvas.after(200, self.gravity)
def moveRight(self, event=None):
self.canvas.move(self.bird, 10, 0)
self.canvas.after(200, self.moveRight)
bird = Bird(canvas, (100, 100, 110, 110))
bird.gravity()
root.mainloop()
The problem you are facing is that you are binding keyboard events, but the events can only work if the widget with the bindings has the keyboard focus. You can give the canvas the keyboard focus with focus_set():
canvas = Canvas(root, height=400, width=500, background='black')
canvas.focus_set()
Is it possible to call this "after"-function or a similar function for the whole canvas instead of the two methods separately?
Yes. Your binding can call any function you want. If you expect to have more than one object and you want them all to move at the same time, you can move them all from a function.
First, remove the call to after from moveRight. Next, define a global function that calls moveRight for every object. For example:
def move_them_all():
bird1.moveRight()
bird2.moveRight()
something_else.moveRight()
self.canvas.after(1000, move_them_all)
...
canvas = Canvas(root, height=400, width=500, background='black')
...
canvas.bind('<right>', move_them_all)

How to pause during a callback using python 3.6?

I am relatively new to python and I am programming a computer player for Othello. I am using the callback method to find the position of the mouse click on the board. I need to be able to pause halfway through the callback so that the player's move is shown followed by the computer's. However, for now, both moves happen together so the player cannot see the result of his/her move. When I tried the time.sleep() method it just delayed the execution of the whole callback.
This is a simplified version of my code:
from tkinter import *
import time
root = Tk()
root.configure(background="black")
canvas=Canvas(root, bg = "black", height = 708, width = 1280)
def callback(event):
if event.y < 350:
canvas.create_rectangle(500,234,780,334,fill="#004800",width=0)
time.sleep(2)
canvas.create_rectangle(500,374,780,474,fill="#004800",width=0)
else:
canvas.create_rectangle(500,374,780,474,fill="#004800",width=0)
time.sleep(2)
canvas.create_rectangle(500,234,780,334,fill="#004800",width=0)
canvas.bind("<Button-1>", callback)
canvas.pack(fill=BOTH, expand=1, pady=0, padx=0)
root.mainloop()
Both moves seem to happen simultaneously because the canvas content is updated only after time.sleep finishes. To see the first move separately, you need to force the canvas to update before the pause with root.update_idletasks().
By the way, you don't have to import the module time to pause, you can use root.after(2000) (the time is in ms) instead.
from tkinter import *
root = Tk()
root.configure(background="black")
canvas = Canvas(root, bg = "black", height = 708, width = 1280)
def callback(event):
if event.y < 350:
canvas.create_rectangle(500,234,780,334,fill="red",width=0)
root.update_idletasks()
root.after(2000)
canvas.create_rectangle(500,374,780,474,fill="#004800",width=0)
else:
canvas.create_rectangle(500,374,780,474,fill="red",width=0)
root.update_idletasks()
root.after(2000)
canvas.create_rectangle(500,234,780,334,fill="#004800",width=0)
canvas.bind("<Button-1>", callback)
canvas.pack(fill=BOTH, expand=1, pady=0, padx=0)
root.mainloop()

Zoom in and out function in Python

I am trying to use zoom in, zoom out feature in python. I have previously tried to use the functionality to zoom in a line in turtle, canvas, etc. but nothing seem to work out, instead of zooming, the code is either increasing or decreasing the length of the line. I want to zoom in the line to add text on the line so that when a user zoom's in the line he/she can see the text. here is the code which I am trying to change.
from tkinter import *
root = Tk()
Label(root).pack()
canvas = Canvas(root, width=400, height=400)
canvas.pack(fill=BOTH, expand=1)
widget = Button(None, text='zoomin-out')
widget.pack()
canvas.create_line(175,175,225,225)
def zoomin(event):
d = event.delta
if d < 0:
amt=0.9
else:
amt=1.1
canvas.scale(ALL, 200,200, amt, amt)
widget.bind('<Button-1>', zoomin)
def zoomout(event):
d = event.delta
if d >0:
amt=1.1
else:
amt=0.7
canvas.scale(ALL, 200,200 , amt, amt)
widget.bind('<Double-1>', zoomout)
widget.mainloop()
root.mainloop()
There are two issues. First, when you add both the Button-1 and Double-1 events to your Button widget, doing a double-click fires both events. They end up cancelling each other out, so only the single-click works as expected.
Second, as I pointed out in this SO answer, certain elements, like text, won't zoom, they'll remain fixed. You'll need to manually scale your fonts to simulate text zoom.
Below's a rework of your code along the above lines. Instead of the problematic single and double click, I've changed it so that left and right clicks on the button cause the canvas to zoom in or out:
from tkinter import *
EXAMPLE_TEXT = "Left or Right click button to zoom in/out"
FONT_NAME = "Helvetica"
font_size = 12
def zoom(amount):
global font_size
canvas.scale(ALL, 200, 200, amount, amount)
font_size *= amount
canvas.itemconfigure(text_item, font=(FONT_NAME, int(font_size)))
root = Tk()
canvas = Canvas(root, width=400, height=400)
canvas.pack(fill=BOTH, expand=1)
text_item = canvas.create_text(200, 200, font=(FONT_NAME, font_size), text=EXAMPLE_TEXT)
canvas.create_oval(50, 50, 350, 350)
widget = Button(root, text='zoom in/out')
widget.pack()
widget.bind('<Button-1>', lambda e: zoom(1.1))
widget.bind('<Button-2>', lambda e: zoom(0.7))
root.mainloop()
If you comment out the line that starts with canvas.itemconfigure(...), you'll see that the circle continues to zoom in and out, but the text remains fixed size.

Tkinter GUI canvas

I am working on a Lego Mindstorms project where we create a GUI that can be used to control the robot. The thing I need to do is to create something that shows the robots position after every move. I am using a canvas where I draw a rectangle and then a dot that shows the current position of the robot. I have a whole bunch of code but I am just showing you a small piece of it relevant to my problem.
from Tkinter import *
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.button = Button(frame, text="Move", command=lambda: do_move())
self.button.pack(side=TOP)
self.canvas = Canvas(master, width=300, height=450)
self.canvas.place(x=250, y=550)
self.canvas.create_rectangle(0, 0, 300, 450, fill="white")
self.canvas.create_oval(150, 300, 160, 310, fill="blue", tags="Position")
x, y = self.canvas.coords("Position")
x = int(x)
y = int(y)
x2 = self.canvas.canvasx(self.x)
y2 = self.canvas.canvasy(self.y)
x2 = int(x2)
y2 = int(y2)
def move_forward():
self.canvas.move(Position, x2, y2)
def move_backwards():
self.canvas.move(Position, , )
root = Tk()
app = App(root)
root.title("Mindstorms GUI")
root.geometry("800x1200")
root.mainloop()
root.destroy()
For the move function that I have a button for, I choose a value and that value will move the robot forward/backward. When the robot has moved, I want to also move my blue circle on my canvas. X and Y are the coordinates for the circles current position, and the rest about X2 and Y2 are taken from another site. I am not really sure why you have to write x=int(x) and I dont really understand the parts for X2 and Y2. Any explanations and suggestions about how I can write the rest of my code?
The first new function that I define at the end will be used with my move button so that I have two commands for the button. When I click the button, the Position-circle will also be moved to the new coordinates. I will also need to write somewhere that a specific value of the unit I use for my move function equals for example a move of 5 coordinates in my canvas. Any tips on how to do that?
I hope you understand the task and my formulations. Any help is appreciated!
You have a couple issues with your sample code, hopefully this minimal example will help you get on track:
from Tkinter import *
import random
class App(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.move_button = Button(self, text="Move", command=self.do_move)
self.move_button.pack()
self.random_button = Button(
self, text="Random!",
command=self.random_move)
self.random_button.pack()
self.canvas = Canvas(self, width=300, height=450)
self.canvas.config(
highlightbackground="grey",
borderwidth=2,
relief="flat")
self.canvas.pack()
self.canvas.create_oval(
150, 300, 160, 310, fill="blue", tag="Oval")
self.pack()
def do_move(self):
self.canvas.move("Oval", 10, 10)
def random_move(self):
x = random.randint(1, 290)
y = random.randint(1, 440)
self.canvas.coords("Oval", x, y, x+10, y+10)
root = Tk()
root.title("Mindstorms GUI")
root.geometry("400x600")
app = App(root)
root.mainloop()
Notice that the Canvas move method takes an offset. Alternatively, you could use the coords method with coordinates as arguments if you know the new location of the oval, however note that the coordinates should be a list of coordinate pairs. I've added a random button to show how to use coords.
Sounds like a cool project, have fun!

Categories