Tkinter GUI canvas - python

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!

Related

Using Tkinter to draw and track a mouse pointer

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()

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 properly use and define attributes within a class. tkinter sticky grid of buttons that reacts to a function individually

I am trying to create a stretchy grid of buttons that change color when pressed. Using tkinter to create the grid, I think I am having issues with calling the correct root or formatting. Creating the resizing grid is not an issue, however getting the code to react to the function is providing me with problems.
Any push in the right direction will greatly be appreciated.
from tkinter import *
gx = 4
gy = 4
class trisector:
def __init__(self, master): #starts the index and root of function
Grid.rowconfigure(master, 0, weight=1) #
Grid.columnconfigure(master, 0, weight=1)
frame=Frame(master)
frame.grid(row=0,column=0,sticky=N+S+E+W)
for x in range(gx):
Grid.rowconfigure(frame, x , weight=1)
for y in range(gy):
Grid.columnconfigure(frame, y, weight=1)
self.btn = Button(frame, command=lambda widget=self.btn:
self.color_change(widget))
self.btn.grid(row=x, column=y, sticky=N+E+W+S)
self.btn.position=(x,y)
def color_change(self,widget):
x,y = widget.position
print("Changing", (x,y))
swidget.configure(bg="red")
root = Tk()
bob = trisector(root)
root.mainloop()
First you will need to split out the assignment of command to self.btn, since you're trying to pass the instance of the button itself:
self.btn = Button(frame)
self.btn['command'] = lambda widget=self.btn: self.color_change(widget)
And then fix the typo in color_change():
def color_change(self,widget):
x,y = widget.position
print("Changing", (x,y))
widget.configure(bg="red") # <-- change swidget to widget

Tkinter -- how to horizontally center canvas text?

I'm working on a function in a UI class that is a config window, it displays the logo of the program and has an updating text at the bottom telling you what it's loading, etc. This is what I have so far:
self.window = "config"
self.windowWidth = 340
self.windowHeight = 270
infoText = "Configuring Kh..."
self.root = tk.Tk()
self.root.geometry("%dx%d+400+400" % (self.windowWidth, self.windowHeight))
self.root.title("Kh Control v1.1 starting...")
logo = tk.PhotoImage(file="KhLogo.gif")
mainPanel = tk.Canvas(self.root, width=self.windowWidth, height=self.windowHeight)
mainPanel.image = logo
mainPanel.pack()
mainPanel.create_image(0, 0, image=logo, anchor="nw")
mainPanel.create_text(0,200, text=infoText, anchor="nw", fill="yellow")
return
I'd like the text in infoText to be centered horizontally and offset vertically about 200px down. The vertical offset works fine, but I can't figure out how to center the text horizontally.
I started by trying the age old ((width / 2) - (str length / 2)) but then realized that each letter isn't 1px. And anchor = "center" seems to only put half the text off the left side of the screen.
I'm very new to Python (only a few days now) so if I'm missing something obvious, that's why.
EDIT: and in case it wasn't obvious, this text will change so I can't just make an absolute decision on the offsets, it has to change with the text
I figured it out after scrounging through the canvas reference.
There is a method for a canvas called bbox that returns a tuple containing (x1, y1, x2, y2) of the area an item takes up. I got those coords, drew up a function to find the px length of it, divided it by 2 and subtracted it from the window width. Then I used canvas.move to change the x offset using the number the function returned.
def findXCenter(self, canvas, item):
coords = canvas.bbox(item)
xOffset = (self.windowWidth / 2) - ((coords[2] - coords[0]) / 2)
return xOffset
The main part is here:
textID = mainPanel.create_text(0,0, text=infoText, anchor="nw", fill="yellow")
xOffset = self.findXCenter(mainPanel, textID)
mainPanel.move(textID, xOffset, 0)
Hopefully my hours of searching for this answer will help someone later on.
Have you tried using the justify parameter?
Often center is used with non-text objects.
https://stackoverflow.com/a/15016161/3900967 might provide some insight.
You can set the position of the text using the first part of the .create_text() method.
see http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/create_text.html
To make this position horizontialy center to the window use self.windowWidth / 2 as the x cordinate
By default the text is anchored to center around the given position.
(.create_text will default to anchor="CENTER")
You will also want to remove the anchor="nw" as this will make your text display down and to the right of the position given
As such your updated code should be.
self.window = "config"
self.windowWidth = 340
self.windowHeight = 270
infoText = "Configuring Kh..."
self.root = tk.Tk()
self.root.geometry("%dx%d+400+400" % (self.windowWidth, self.windowHeight))
self.root.title("Kh Control v1.1 starting...")
logo = tk.PhotoImage(file="KhLogo.gif")
mainPanel = tk.Canvas(self.root, width=self.windowWidth, height=self.windowHeight)
mainPanel.image = logo
mainPanel.pack()
mainPanel.create_image(0, 0, image=logo, anchor="nw")
mainPanel.create_text(self.windowWidth/2,200, text=infoText, fill="yellow")
return
You can use the anchor="center" method to align your text that created inside the canvas using canvas.create_text method
try following example code:
from tkinter import *
root = Tk()
canvas = Canvas(root, width=400, height=300)
canvas.pack()
text = canvas.create_text(200, 150, text="Hello World!", anchor="center")
root.mainloop()

Tkinter draws in two canvases

I'm writing 3 functions in Tkinter. Each function is in the form ObjectName(c,x,y) where c is the name of the canvas. I want each function to draw shape in any given canvas.
Example:
from Tkinter import *
root = Tk()
def line(c,x,y):
root = Tk()
c = Canvas(root, width=600, height=800)
c.pack()
c.create_line(x-160,y,x+300,y)
drawLine(c,200,300)
root.mainloop()
Problem:
when I call the same function to draw two shapes on the same canvas it draws on two different canvases :(
Your code looks to be creating a new canvas object each time you call line (or drawLine, as your function name and usage appears to be inconsistent.) You shouldn't create a new root object and Canvas object in your function.
Try something like this:
from Tkinter import *
def drawLine(c, x, y):
c.create_line(x - 160, y, x + 300, y)
root = Tk()
c = Canvas(root, width=600, height=800)
c.pack()
drawLine(c, 200, 300)
drawLine(c, 300, 400)
drawLine(c, 350, 450)
root.mainloop()

Categories