How to get multiple ovals moving around the canvas? - python-3.x

My question is how do I get multiple "bubbles" moving around the screen with random numbers?
Would I have to do heaps of def bubble(): or is there a simpler way to do it? Also Moving the "bubbles" randomly across the canvas baffles me.
Coding so far:
from tkinter import *
import random
def quit():
root.destroy()
def bubble():
xval = random.randint(5,765)
yval = random.randint(5,615)
canvas.create_oval(xval,yval,xval+30,yval+30, fill="#00ffff",outline="#00bfff",width=5)
canvas.create_text(xval+15,yval+15,text=number)
canvas.update()
def main():
global root
global tkinter
global canvas
root = Tk()
root.title("Math Bubbles")
Button(root, text="Quit", width=8, command=quit).pack()
Button(root, text="Start", width=8, command=bubble).pack()
canvas = Canvas(root, width=800, height=650, bg = '#afeeee')
canvas.pack()
root.mainloop()
# Create a sequence of numbers to choose from. CAPS denotes a constant variable
NUMBERS = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
# Pick numbers randomly from the sequence with the help of a random.choice function
number = random.choice(NUMBERS)
# Create a variable to use later to see if the guess is correct
correct = number
main()

This should get you going:
import Tkinter, random
class BubbleFrame:
def __init__(self, root):
root.title("Math Bubbles")
Tkinter.Button(root, text="Add Bubbles", width=8, command=self.bubble).pack()
Tkinter.Button(root, text="Quit", width=8, command=quit).pack()
self.canvas = Tkinter.Canvas(root, width=800, height=650, bg = '#afeeee')
self.canvas.pack()
self.bubbles = {} # this will hold bubbles ids, positions and velocities
def bubble(self):
# add bubbles for numbers from 1 to 20
for number in range(1, 20+1):
xval = random.randint(5,765)
yval = random.randint(5,615)
s1 = self.canvas.create_oval(xval,yval,xval+30,yval+30, fill="#00ffff",outline="#00bfff",width=5)
s2 = self.canvas.create_text(xval+15,yval+15, text=number)
self.bubbles[(s1, s2)] = (xval, yval, 0, 0) # add bubbles to dictionary
def loop(self, root):
for (s1, s2), (x, y, dx, dy) in self.bubbles.items():
# update velocities and positions
dx += random.randint(-1, 1)
dy += random.randint(-1, 1)
# dx and dy should not be too large
dx, dy = max(-5, min(dx, 5)), max(-5, min(dy, 5))
# bounce off walls
if not 0 < x < 770: dx = -dx
if not 0 < y < 620: dy = -dy
# apply new velocities
self.canvas.move(s1, dx, dy)
self.canvas.move(s2, dx, dy)
self.bubbles[(s1, s2)] = (x + dx, y + dy, dx, dy)
# have mainloop repeat this after 100 ms
root.after(100, self.loop, root)
if __name__ == "__main__":
root = Tkinter.Tk()
frame = BubbleFrame(root)
frame.loop(root)
root.mainloop()
Note that I've restructured the code a bit, using a class for wrapping those methods and attributes, but that's entirely up to you. Also note, that I've done this with Python 2.7, so there may be some small adaptions to make, e.g., the name of the Tkinter package.
You do not need to define another bubble function for each bubble to add -- in fact your code already adds many bubbles to the canvas, so this part is fine. The tricky bit is moving the bubbles around.
For this I've first added the self.bubbles dictionary, mapping the IDs of the bubbles and their labels (returned by the canvas.create... methods) to their current positions and velocities. Finally, the loop method updates the positions and velocities of the bubbles and redraws the canvas. At the end, this method will schedule it's next execution using the after method.

Related

Python tkinter - how to create a realtime x-y plot which scrolls left off canvas

I'm writing a Phyton app using tkinter for the GUI, and want to plot real time sensor data in a never ending X-Y graph that scrolls the old data off the left hand side of the canvas. It's ok for the old data to be lost.
I'd prefer not to use matplotlib as my needs are simple and will be targeting a RPi 3 so not a lot of resources. I'm thinking of just using tkinter "create_line" for each new sample. It's just the scrolling that I'm unsure about.
Example keeps values y on list data and uses after to run function move every 500ms (0.5s) which removes first value from list and add new value at the end and then remove all lines from canvas to draw new ones.
It could keep IDs of lines on list, and moves lines but current method seems simpler.
#!/usr/bin/env python3
import tkinter as tk
import random
# --- constants --- (UPPER_CASE names)
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 200
DISTANCE = 50
NUMBER = (SCREEN_WIDTH // DISTANCE) + 1
# --- functions --- (lower_case names)
def move():
# remove first
data.pop(0)
# add last
data.append(random.randint(0,SCREEN_HEIGHT))
# remove all lines
canvas.delete('all')
# draw new lines
for x, (y1, y2) in enumerate(zip(data, data[1:])):
x1 = x * DISTANCE
x2 = (x+1) * DISTANCE # x1 + DISTANCE
canvas.create_line([x1, y1, x2, y2])
# run again after 500ms (0.5s)
root.after(500, move)
# --- main --- (lower_case names)
# data at start
data = [random.randint(0, SCREEN_HEIGHT) for _ in range(NUMBER)]
root = tk.Tk()
root.geometry('{}x{}'.format(SCREEN_WIDTH, SCREEN_HEIGHT))
canvas = tk.Canvas(root, width=SCREEN_WIDTH, height=SCREEN_HEIGHT)
canvas.pack()
# start animation
move()
root.mainloop()
EDIT: DISTANCE = 2
EDIT:
This code use two little different classes to draw graph. Now they need only append new element to move line
def move():
graph.append(random.randint(0,SCREEN_HEIGHT))
graph_canvas.append(random.randint(0,SCREEN_HEIGHT))
# run again after 100ms (0.1s)
root.after(100, move)
Class Graph gets canvas as argument so teoreticly you can add graph to existing canvas. But it remove all object so it would need changes to resolve this problem.
Class GraphCanvas is widget which resize Canvas and doesn't need existing canvas. Teoreticly you can add other elements like to normal canvas but again problem is that it removes all object and it would need changes in code.
It would use canvas.move(offset_x, offset_y) to move lines and then it wouldn't need to delete lines. Or maybe it would use canvas.create_polygon but it would have to first create list with pairs (x,y)
Both change "distance" between point so they use all space when canvas change size horizontally. Vertically it would need recalculations.
I added colors to see if canvases change size in window.
import tkinter as tk
import random
# --- constants --- (UPPER_CASE_NAMES)
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 200
DISTANCE = 20
NUMBER = (SCREEN_WIDTH // DISTANCE) + 1
# --- classes --- (CamelCaseNames)
class Graph(object):
def __init__(self, canvas, data, *args, **kwargs):
super().__init__()
self.canvas = canvas
self.data = data
self.draw()
def append(self, item, remove_first=True):
if remove_first:
self.data.pop(0)
self.data.append(item)
self.draw()
def draw(self):
# new distance if size changed
self.distance = int(self.canvas.winfo_width())/len(self.data)+1
# remove all lines
self.canvas.delete('all')
# draw new lines
for x, (y1, y2) in enumerate(zip(self.data, self.data[1:])):
x1 = x * self.distance
x2 = (x+1) * self.distance # x1 + self.distance
self.canvas.create_line([x1, y1, x2, y2])
class GraphCanvas(tk.Canvas):
def __init__(self, master, data, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.data = data
self.draw()
def append(self, item, remove_first=True):
if remove_first:
self.data.pop(0)
self.data.append(item)
self.draw()
def draw(self):
# new distance if size changed
self.distance = int(self.winfo_width())/len(self.data)+1
# remove all lines
self.delete('all')
# draw new lines
for x, (y1, y2) in enumerate(zip(self.data, self.data[1:])):
x1 = x * self.distance
x2 = (x+1) * self.distance # x1 + self.distance
self.create_line([x1, y1, x2, y2])
# --- functions --- (lower_case_names)
def move():
graph.append(random.randint(0,SCREEN_HEIGHT))
graph_canvas.append(random.randint(0,SCREEN_HEIGHT))
# run again after 100ms (0.1s)
root.after(100, move)
# --- main --- (lower_case names)
# data at start
data = [random.randint(0, SCREEN_HEIGHT) for _ in range(NUMBER)]
root = tk.Tk()
canvas = tk.Canvas(root, width=SCREEN_WIDTH, height=SCREEN_HEIGHT, bg='#ccffcc')
canvas.pack(fill='both', expand=True)
graph = Graph(canvas, data)
graph_canvas = GraphCanvas(root, data, width=SCREEN_WIDTH, height=SCREEN_HEIGHT, bg='#ffcccc')
graph_canvas.pack(fill='both', expand=True)
# start animation
move()
root.mainloop()
Using class you can easily add more graphs

Trouble filling a Python tkinter/turtle window with squares

I'm having trouble with getting the turtle.begin_fill() / turtle.end_fill() commands to work in a tkinter window. I want to have the program draw a bunch of coloured cells onto the screen in a 64x32 grid. This the whole code to set up the window and draw however it simply doesn't do anything after setting up a window:
import turtle as tu
import tkinter as tk
axis = []
num = -512
for i in range(0,64):
axis.append(num)
num = num + 16
def drawworld(*args):
for i in range(0,64):
for j in range(0,32):
tu.goto(axis[i],axis[j]+256)
tu.pd()
tu.color=("#ffff00")
tu.begin_fill()
for k in range(0,4):
tu.fd(16)
tu.lt(90)
tu.end_fill()
tu.pu()
root = tk.Tk()
root.title("game")
root.resizable(False, False)
canvas = tk.Canvas(master = root, width = 1024, height = 512)
canvas.grid(row=0, column=0, columnspan=10)
tu = tu.RawTurtle(canvas)
tu.speed(50)
tu.pu()
tu.ht()
drawworld()
root.mainloop()
however, if I were to comment out the fill lines, the code works perfectly (without colouring the boxes) which means there must be something wrong with:
tu.color=("#ffff00")
tu.begin_fill()
for k in range(0,4):
tu.fd(16)
tu.lt(90)
tu.end_fill()
tu.pu()
I've looked up the documentation and this should be perfect syntax. What am I doing wrong?
I believe your problem is this line:
tu.color=("#ffff00")
which probably should be:
tu.color("#ffff00")
to set the color, you don't assign to the turtle's color property but rather invoke the turtle's .color() method. Your code with this change and some other cleanup:
import tkinter as tk
from turtle import RawTurtle
axis = []
num = -512
for _ in range(64):
axis.append(num)
num += 16
def drawworld():
for i in range(64):
for j in range(32):
tu.goto(axis[i], axis[j] + 256)
tu.pendown()
tu.color("#ffff00")
tu.begin_fill()
for _ in range(4):
tu.forward(16)
tu.left(90)
tu.end_fill()
tu.penup()
root = tk.Tk()
root.title("game")
root.resizable(False, False)
canvas = tk.Canvas(master=root, width=1024, height=512)
canvas.grid(row=0, column=0, columnspan=10)
tu = RawTurtle(canvas)
tu.speed('fastest')
tu.hideturtle()
tu.penup()
drawworld()
root.mainloop()
However, this is a painfully slow way to go about this. There are faster ways, here's one that uses stamping to speed up the process:
import tkinter as tk
from turtle import RawTurtle
from random import random
CUBE_SIZE = 16
CURSOR_SIZE = 20
WIDTH, HEIGHT = 1024, 512
axis = []
num = -HEIGHT
for _ in range(WIDTH // CUBE_SIZE):
axis.append(num)
num += CUBE_SIZE
def drawworld():
for i in range(WIDTH // CUBE_SIZE):
tu.goto(axis[i] + CUBE_SIZE // 2, axis[0] + HEIGHT // 2 + CUBE_SIZE // 2)
for j in range(HEIGHT // CUBE_SIZE):
tu.color(random(), random(), random())
tu.stamp()
tu.forward(CUBE_SIZE)
root = tk.Tk()
root.title("game")
root.resizable(False, False)
canvas = tk.Canvas(master=root, width=WIDTH, height=HEIGHT)
canvas.grid(row=0, column=0, columnspan=10)
tu = RawTurtle(canvas)
tu.shape('square')
tu.shapesize(CUBE_SIZE / CURSOR_SIZE)
tu.speed('fastest')
tu.hideturtle()
tu.setheading(90)
tu.penup()
drawworld()
root.mainloop()

Tkinter - How to move image from canvas in slow motion

guys. I am trying to create my own version of a card game. I got the following problem trying to move my cards to the center of the canvas on click event. Here is an example of my code
import tkinter as tk
class gui(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.canvas = tk.Canvas(parent, bg="blue", highlightthickness=0)
self.canvas.pack(fill="both", expand=True)
self.img = PhotoImage(file="card.gif")
self.card = self.canvas.create_image(10, 10, image=self.img)
self.canvas.tag_bind(self.card, '<Button-1>', self.onObjectClick1)
def onObjectClick1(self, event):
if self.canvas.find_withtag("current"):
x = 400
y = 400
self.canvas.coords("current", x, y)
self.canvas.tag_raise("current")
if __name__ == "__main__":
root = tk.Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry("%dx%d+0+0" % (w, h))
gui(root)
root.mainloop()
What I want is to move my card but not just move from one coordinate to another but giving it in slow motion effect.
The basic idea is to write a function that moves an object a small amount, and then schedules itself to be called again after a short delay. It does this until it reaches its destination.
Here is a very simple example that moves a couple items independently. You can adjust the speed by changing the speed parameter, or by changing the values of delta_x and delta_y.
This is a very simplistic algorithm that just increases the x and y coordinates by a fixed amount. You could instead calculate equally spaced points along a curve or straight line. Regardless, the animation technique remains the same.
import Tkinter as tk
def move_object(canvas, object_id, destination, speed=50):
dest_x, dest_y = destination
coords = canvas.coords(object_id)
current_x = coords[0]
current_y = coords[1]
new_x, new_y = current_x, current_y
delta_x = delta_y = 0
if current_x < dest_x:
delta_x = 1
elif current_x > dest_x:
delta_x = -1
if current_y < dest_y:
delta_y = 1
elif current_y > dest_y:
delta_y = -1
if (delta_x, delta_y) != (0, 0):
canvas.move(object_id, delta_x, delta_y)
if (new_x, new_y) != (dest_x, dest_y):
canvas.after(speed, move_object, canvas, object_id, destination, speed)
root = tk.Tk()
canvas = tk.Canvas(root, width=400, height=400)
canvas.pack()
item1 = canvas.create_rectangle(10, 10, 30, 30, fill="red")
item2 = canvas.create_rectangle(360, 10, 380, 30, fill="green")
move_object(canvas, item1, (200, 180), 25)
move_object(canvas, item2, (200, 220), 50)
root.mainloop()
In order to 'animate' your cards moving, a system of breaking down the total distance to be moved, and then moving/updating by smaller distances over a time-period would work.
For example, if you wish to move a card 400 units in x & y, something like this could work:
total_time = 500 #Time in milliseconds
period = 8
dx = 400/period
dy = 400/period
for i in range(period):
self.canvas.move(chosen_card, dx, dy)
root.after(total_time/period) #Pause for time, creating animation effect
root.update() #Update position of card on canvas
This could be a basic premise for an animation. Of course you would need to edit the total_time and period variables in my example to create what you feel is right.
This code below (ready for copy/paste and run as it is) gives a nice smooth motion on my box:
import tkinter as tk
import time
class gui(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.canvas = tk.Canvas(parent, bg="blue", highlightthickness=0)
self.canvas.pack(fill="both", expand=True)
self.img = tk.PhotoImage(file="card.gif")
self.card = self.canvas.create_image(10, 10, image=self.img)
self.canvas.tag_bind(self.card, '<Button-1>', self.onObjectClick1)
def onObjectClick1(self, event):
if self.canvas.find_withtag("current"):
x = 400
y = 400
self.canvas.coords("current", x, y)
self.canvas.tag_raise("current")
total_time = 500 #Time in milliseconds
period = 400
dx = 400/period
dy = 400/period
for i in range(period):
self.canvas.move(self.card, dx, dy) # chosen_card
time.sleep(0.01)
# root.after(total_time/period) #Pause for time, creating animation effect
root.update() #Update position of card on canvas
if __name__ == "__main__":
root = tk.Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry("%dx%d+0+0" % (w, h))
gui(root)
root.mainloop()

Simple GUI in Python using tkinter

I am trying to create a simple GUI in python using tkinter. What I am trying to do is
Place my entry element in the center of the upper half of the GUI window
Place a button right next to it
On clicking button, open up an interface to choose a file
Display the file name along with its path in the entry element
def center_window(width, height):
# get screen width and height
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# calculate position x and y coordinates
x = (screen_width/2) - (width/2)
y = (screen_height/2) - (height/2)
root.geometry('%dx%d+%d+%d' % (width, height, x, y))
def OnButtonClick(self):
self.entryVariable.set( tkinter.filedialog.askopenfilename() )
self.entry.focus_set()
self.entry.selection_range(0, tkinter.END)
root = tkinter.Tk()
center_window(400, 300)
root.title("A simple GUI")
root.entryVariable = tkinter.StringVar()
root.entry = tkinter.Entry(root,textvariable=root.entryVariable)
root.entry.grid(column=10,row=5,columnspan=20)
B = tkinter.Button(root, text ="Choose", command=OnButtonClick(root))
B.grid(column=30,row=5, columnspan=2)
Could anybody guide me how can I move entry element and button in the center of the upper half of the GUI window. Also, how can I make tkinter.filedialog.askopenfilename() function to be invoked on clicking the button. It gets invoked as soon as the GUI window opens when I run the above code. Thanks.
Here is the revised code. Basically, you need to pass a function object to the command argument of a Button, which means you can either pass a function without the trailing parenthesis (if it doesn't take any argument) or use lambda. In your original code, your function was executed immediately after the python interpreter reaches that line. Also, you need to call root.mainloop at the end of the program.
import tkinter
import tkinter.filedialog
def center_window(width, height):
# get screen width and height
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# calculate position x and y coordinates
x = (screen_width/2) - (width/2)
y = (screen_height/2) - (height/2)
root.geometry('%dx%d+%d+%d' % (width, height, x, y))
def OnButtonClick(self):
self.entryVariable.set( tkinter.filedialog.askopenfilename() )
self.entry.focus_set()
self.entry.selection_range(0, tkinter.END)
root = tkinter.Tk()
center_window(400, 300)
root.title("A simple GUI")
root.entryVariable = tkinter.StringVar()
frame=tkinter.Frame(root)
root.entry = tkinter.Entry(frame,textvariable=root.entryVariable)
B = tkinter.Button(frame, text ="Choose", command=lambda: OnButtonClick(root))
root.entry.grid(column=0,row=0)
B.grid(column=1,row=0)
frame.pack(pady=100) #Change this number to move the frame up or down
root.mainloop()

Generate 10 balls in tkinter canvas

Ok guys.
I am trying to generate 10 balls of random color in Tkinter canvas when I click the generate button.
Program works, and random color choice works for the ball, but I only get one ball generated at a time.
Every time I click the button it randomly moves the ball around, but all I want is 10 balls in 10 random positions at a time. I am using Python 3.4 on a Linux box.
This is a code I've got:
from tkinter import *
import random # to generate random balls
colors = ["red", "blue", "purple", "green", "violet", "black"]
class RandomBalls:
"""
Boilerplate code for window in Tkinter
window = Tk()
window.title("Random title")
window.mainloop()
"""
def __init__(self):
"""
Initialize the window and add two frames, one with button, and another one with
canvas
:return:
"""
window = Tk()
window.title("Random balls")
# A canvas frame
frame1 = Frame(window)
frame1.pack()
self.canvas = Canvas(frame1, width = 200, height = 300, bg = "white")
self.canvas.pack()
# A button frame
frame2 = Frame(window)
frame2.pack()
displayBtn = Button(frame2, text = "Display", command = self.display)
displayBtn.pack()
window.mainloop()
def display(self):
for i in range(0, 10):
self.canvas.delete("circle") # delete references to the old circle
self.x1 = random.randrange(150)
self.y1 = random.randrange(200)
self.x2 = self.x1 + 5
self.y2 = self.y1 + 5
self.coords = self.x1, self.y1, self.x2, self.y2
self.canvas.create_oval(self.coords, fill = random.choice(colors), tags = "circle")
self.canvas.update()
RandomBalls()
Every time through your loop you are deleting everything you created before, including what you created the previous iteration. Move the delete statement outside of the loop:
def display(self):
self.canvas.delete("circle")
for i in range(0, 10):
...

Resources