Simple GUI in Python using tkinter - python-3.x

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

Related

Screenshot area from mouse

Am trying to take a screenshot area specifying from my mouse (x, y coordinates).
The next code opens an opaque window, then it starts to listen the mouse left button, when it selects an area it draws a red border color, then it takes the screenshot on that specific area. The issue that am having is removing the opaque color that I set before.
Note: I use the opaque color to let the user know when the program is ready to take the screenshot area.
from pynput.mouse import Listener
from PIL import Image, ImageGrab
import tkinter as tk
root = tk.Tk()
########################## Set Variables ##########################
ix = None
iy = None
#Get the current screen width and height
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
#Get and print coordinates
def on_move(x, y):
print('Pointer moved to {0}'.format( (x, y) ))
#Start and End mouse position
def on_click(x, y, button, pressed):
global ix, iy
if button == button.left:
#Left button pressed then continue
if pressed:
ix = x
iy = y
print('left button pressed at {0}'.format( (x, y) ))
else:
print('left button released at {0}'.format( (x, y) ))
canvas.create_rectangle(ix, iy, x, y, outline="red", width = 5)#Draw a rectangle
canvas.pack()
img = ImageGrab.grab(bbox = (ix, iy, x, y))#Take the screenshot
img.save('screenshot.png')#Save screenshot
root.quit()#Remove tkinter window
if not pressed:
# Stop listener
return False
#Print the screen width and height
print(screen_width, screen_height)
root_geometry = str(screen_width) + 'x' + str(screen_height) #Creates a geometric string argument
root.geometry(root_geometry) #Sets the geometry string value
root.overrideredirect(True)
root.wait_visibility(root)
root.wm_attributes("-alpha", 0.3)#Set windows transparent
canvas = tk.Canvas(root, width=screen_width, height=screen_height)#Crate canvas
canvas.config(cursor="cross")#Change mouse pointer to cross
canvas.pack()
# Collect events until released
with Listener(on_move=on_move, on_click=on_click) as listener:
root.mainloop()#Start tkinter window
listener.join()
System info:
Linux Mint 20 Cinnamon
Python3.8
I think you need to update root.wm_attributes again before screenshot. So like this:
# Start and End mouse position
def on_click(x, y, button, pressed):
global ix, iy
if button == button.left:
# Left button pressed then continue
if pressed:
ix = x
iy = y
print('left button pressed at {0}'.format((x, y)))
else:
print('left button released at {0}'.format((x, y)))
root.wm_attributes('-alpha', 0)
img = ImageGrab.grab(bbox=(ix, iy, x, y)) # Take the screenshot
root.quit() # Remove tkinter window
img.save('screenshot_area.png') # Save the screenshot
if not pressed:
# Stop listener
return False
Maybe you can use tkinter.canvas for the rectangle.

How do I add back the icon to the task bar in guizero/tkinter python 3?

I am trying to add back the icon for a custom app window with the border removed. I made this in Guizero/Tkinter. I Need to be able to add back the icon so it can be displayed in the taskbar. The icon need to be displayed as active so that you can hide/minimize into the task bar and show/reopen the window from there. I have tried "root.iconify()".
from guizero import *
app=App(title='Test',bg='#121212',width=750,height=550)
root = app.tk
#Icon does not display when overrideredirect is True
app.tk.iconbitmap("icon.ico")
#Remove window border
root.overrideredirect(True)
#https://stackoverflow.com/questions/14910858/how-to-specify-where-a-tkinter-window-opens
#base on answer given byRachel Gallen
def center_window(width=750, height=500):
# 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))
#menu bar
Top_box = Box(app,height=35,width='fill', align="top")
Top_box.bg='white'
#window title
left_box = Box(Top_box,height=35,width='fill', align="left")
left_box.bg='#121212'
text = Text(left_box, text=" Test Project",align="left")
text.text_color='gray'
text.tk.padx=15
text.tk.config(font='Helvetica 15 bold')
#end of window title
#Exit/quit button
Quit_button = PushButton(Top_box, text="X", command=quit ,align='right')
Quit_button.bd=0
Quit_button.text_size=22
Quit_button.text_color='purple'
Quit_button.bg='gray'
Quit_button.tk.config(activebackground='black',highlightthickness=10,bd=0,highlightbackground='red')
#Minmize/hide button
Minmize_button = PushButton(Top_box, text="-", command=app.hide ,align='right')
Minmize_button.tk.config(activebackground='green',highlightthickness=2,bd=0,highlightbackground='red',font=("Verdana", 31))
Minmize_button.text_color="purple"
#Content of the window
Canvas_box = Box(app,height='fill',width='fill', align="top")
Canvas_box.bg='green'
Text=Text(Canvas_box,text='Test',align='top',size=26)
Text.text_color='white'
center_window(750, 500)
app.display()
While not perfect this is pretty close. It adds the icon back when you minimize the window. It's a complete custom window with drag support.
I integrated and update some code made by others to work with Guizero 1.2.0.
from guizero import *
app = App(bg='white',title='Test',width=645,height=275)
#Removes window border and title bar
app.tk.overrideredirect(True)
#app.icon="Shorcut_Icon1.ico"
#minimize system to re-add icon
#https://stackoverflow.com/questions/52714026/python-tkinter-restore-window-without-title-bar
#base on Answer given by Mike - SMT
def close():
app.destroy()
def minimizeWindow():
app.hide()
app.tk.overrideredirect(False)
app.tk.iconify()
def check_map(event): # apply override on deiconify.
if str(event) == "<Map event>":
app.tk.overrideredirect(1)
print ('Deiconified', event)
else:
print ('Iconified', event)
winControl_container=Box(app,width=app.width,height=35,align='top')
Bottom_line=Box(winControl_container,width=app.width,height=1,align='bottom')
Bottom_line.bg='blue'
closeButton= PushButton(winControl_container, text='X', command=close,align='right')
minButton= PushButton(winControl_container, text='—', command=minimizeWindow,align='right')
#Start of window movement system
#https://stackoverflow.com/questions/4055267/tkinter-mouse-drag-a-window-without-borders-eg-overridedirect1
#base on answer given by David58
lastClickX = 0
lastClickY = 0
def SaveLastClickPos(event):
global lastClickX, lastClickY
lastClickX = event.x
lastClickY = event.y
def Dragging(event):
x, y = event.x - lastClickX + app.tk.winfo_x(), event.y - lastClickY + app.tk.winfo_y()
app.tk.geometry("+%s+%s" % (x , y))
#Adds window movement/dragging events. Just comment this out if you don't want the window to be able to move.
app.when_left_button_pressed=SaveLastClickPos
app.when_mouse_dragged=Dragging
#End of movement/drag system
app.tk.bind('<Map>', check_map) # added bindings to pass windows status to function
app.tk.bind('<Unmap>', check_map)
#Center window system
#https://stackoverflow.com/questions/14910858/how-to-specify-where-a-tkinter-window-opens
#base on answer given byRachel Gallen
def center_window(width, height):
# get screen width and height
screen_width = app.tk.winfo_screenwidth()
screen_height = app.tk.winfo_screenheight()
# calculate position x and y coordinates
x = (screen_width/2) - (width/2)
y = (screen_height/2) - (height/2)
app.tk.geometry('%dx%d+%d+%d' % (width, height, x, y))
center_window(app.width,app.height)
app.display()
# This works fine do not adjust code
# works with version 1.1.0
# This is no longer supposed to work with the latest version of guizero.
#
# I have tried with version 1.3.0 which is the
# latest version on 05/01/22
# and it still appears to work without any changes needed
# in comments below.
# You will need to use the command below instead.
# WindowsName.tk.iconbitmap("Icon.ico")
# Imports ---------------
from tkinter import *
from guizero import App
root = Tk()
app = App(title="App window")
root.iconbitmap(app, 'green-finger.ico')
# Root window is shown when the program is run,
# so it must be destroyed it to stop this happening.
root.destroy()
app.display()

How to get rid of titlebar without removing icon in taskbar with Tkinter?

So, I was recently checking out VsCode, and I noticed an interesting feature. Although there was a taskbar icon, there was no titlebar; instead, VsCode implements its own. I looked at some other programs from Microsoft, and they do the same thing. I think this is a very cool feature.
I make a lot of productivity apps with Tkinter*, so I looked at how to do this in my apps. Unfortunately, the standard way to get rid of the titlebar in Tkinter is to disable the Window Manager (using overridedirect(1)). This also gets rid of the taskbar icon, which I want to keep.
In other words, what I am trying to get is
while still keeping this: .
* For reference I am using Python 3.8 and TkInter 8.6.
You can create your own buttons titlebar using frames. Here take a look at this. I also worked on a tkinter based app and created this along with using what #Hruthik Reddy has given.
I make a lot of productivity apps with Tkinter*, so I looked at how to do this in my apps.
Assuming that you may have used classes at some point in those apps, I have created this inherited Tk subclass, and explained in comments:
import tkinter as tk
import tkinter.ttk as ttk
from ctypes import windll
class TestApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# set overrideredirect to True to remove the windows default decorators
self.overrideredirect(True)
self.geometry('700x500+10+10') # you may or may not want to initialize the geometry of the window
self.minsize(193, 109)
# (x, y) coordinates from top left corner of the window
self.x = None
self.y = None
# Create a frame that will contain the title label of the window
self.frame = tk.Frame(self, bg='gray38')
self.frame.pack(side=tk.TOP, fill=tk.X)
# Label `name` for the window
# Since buttons are on the right side and the name of the window is on the left side, the label will be packed towards LEFT side
self.name = tk.Label(self.frame, text='Simple Text Box', font='Consolas 11',
bg=self.frame.cget('background'), fg='white')
self.name.pack(side=tk.LEFT, fill=tk.X, anchor=tk.CENTER)
# Pack the close button to the right-most side
self.close = tk.Button(self.frame, text='✕', bd=0, width=3, font='Consolas 13',
command=self.destroy, bg=self.frame.cget('background'))
self.close.pack(side=tk.RIGHT)
# Pack the maximize button second from the right
# The unicode string as the value of the keyword `text` below, is taken from the internet, it contains the maximize icon as unicode character
self.maximize = tk.Button(self.frame, text=u"\U0001F5D6", bd=0, width=3, font='Consolas',
command=self.maximize_win, bg=self.frame.cget('background'))
self.maximize.pack(side=tk.RIGHT)
# Pack the minimize button third from the right
self.minimize = tk.Button(self.frame, text='—', bd=0, width=3, font='Consolas 13',
command=self.minimize_win, bg=self.frame.cget('background'))
self.minimize.pack(side=tk.RIGHT)
# -------------------
# NOW YOU CAN PUT WHATEVER WIDGETS YOU WANT AFTER THIS BUT FOR THIS EXAMPLE I
# HAVE TAKEN A TEXTBOX WITH HORIZONTAL AND VERTICAL SCROLLBARS AND A SIZEGRIP
# -------------------
# The frame below contains the vertical scrollbar and the sizegrip (sizegrip helps in resizing the window
self.scroll_frame = tk.Frame(self)
v_scroll = tk.Scrollbar(self.scroll_frame, orient=tk.VERTICAL)
h_scroll = tk.Scrollbar(self, orient=tk.HORIZONTAL)
self.grip = ttk.Sizegrip(self.scroll_frame)
# I am directly putting the textbox in the window, you may add frames and other stuff
self.text = tk.Text(self, wrap=tk.NONE, yscrollcommand=v_scroll.set, xscrollcommand=h_scroll.set,
font='Consolas 14', width=1, height=1)
# set the scrollbar for y and x views of the textbox respectively
v_scroll.config(command=self.text.yview)
h_scroll.config(command=self.text.xview)
# Packing scrollbar frame, the scrollbars and the grip according to the arrangement I want
self.scroll_frame.pack(side=tk.RIGHT, fill=tk.Y)
v_scroll.pack(side=tk.TOP, fill=tk.Y, expand=tk.Y)
self.grip.pack(side=tk.BOTTOM)
self.text.pack(side=tk.TOP, expand=tk.TRUE, fill=tk.BOTH)
h_scroll.pack(side=tk.BOTTOM, fill=tk.X)
self.grip.bind("<B1-Motion>", self.onmotion)
# Bind the motion of mouse after mouse click to the onmotion function for window resizing
self.call('encoding', 'system', 'utf-8')
# Binding `<Enter>` and `<Leave>` mouse event to their respective functions
# `<Enter>` event is called when the mouse pointer enters any widget
# `<Leave>` event is called when the mouse pointer leaves any widget
# Here when the mouse pointer enters or leaves the buttons their color will change
self.close.bind('<Enter>', lambda _: self.close.config(bg='red'))
self.close.bind('<Leave>', lambda _: self.close.config(bg=self.frame.cget('background')))
self.minimize.bind('<Enter>', lambda _: self.minimize.config(bg='gray58'))
self.minimize.bind('<Leave>', lambda _: self.minimize.config(bg=self.frame.cget('background')))
self.maximize.bind('<Enter>', lambda _: self.maximize.config(bg='gray58'))
self.maximize.bind('<Leave>', lambda _: self.maximize.config(bg=self.frame.cget('background')))
# Now you may want to move your window (obviously), so the respective events are bound to the functions
self.frame.bind("<ButtonPress-1>", self.start_move)
self.frame.bind("<ButtonRelease-1>", self.stop_move)
self.frame.bind("<B1-Motion>", self.do_move)
self.frame.bind('<Double-1>', self.maximize_win)
self.name.bind("<ButtonPress-1>", self.start_move)
self.name.bind("<ButtonRelease-1>", self.stop_move)
self.name.bind("<B1-Motion>", self.do_move)
self.name.bind('<Double-1>', self.maximize_win)
def start_move(self, event):
""" change the (x, y) coordinate on mousebutton press and hold motion """
self.x = event.x
self.y = event.y
def stop_move(self, event):
""" when mouse button is released set the (x, y) coordinates to None """
self.x = None
self.y = None
def do_move(self, event):
""" function to move the window """
self.wm_state('normal') # if window is maximized, set it to normal (or resizable)
self.maximize.config(text=u"\U0001F5D6") # set the maximize button text to the square character of maximizing window
deltax = event.x - self.x
deltay = event.y - self.y
x = self.winfo_x() + deltax
y = self.winfo_y() + deltay
self.geometry(f"+{x}+{y}")
def onmotion(self, event):
""" function to change window size """
self.wm_state('normal')
self.maximize.config(text=u"\U0001F5D6")
x1 = self.winfo_pointerx()
y1 = self.winfo_pointery()
x0 = self.winfo_rootx()
y0 = self.winfo_rooty()
self.geometry("%sx%s" % ((x1-x0), (y1-y0)))
return
def minimize_win(self, event=None):
""" function to iconify or minimize window as an icon """
self.overrideredirect(False)
self.wm_iconify()
self.bind('<FocusIn>', self.on_deiconify)
def maximize_win(self, event=None):
""" function to maximize window or make it normal (exit maximize) """
if self.maximize.cget('text') == u"\U0001F5D7":
self.wm_state('normal')
self.maximize.config(text=u"\U0001F5D6")
return
self.wm_state('zoomed')
self.maximize.config(text=u"\U0001F5D7")
def on_deiconify(self, event):
""" function to deiconify or window """
self.overrideredirect(True)
set_appwindow(root=self)
def set_appwindow(root):
hwnd = windll.user32.GetParent(root.winfo_id())
style = windll.user32.GetWindowLongPtrW(hwnd, GWL_EXSTYLE)
style = style & ~WS_EX_TOOLWINDOW
style = style | WS_EX_APPWINDOW
res = windll.user32.SetWindowLongPtrW(hwnd, GWL_EXSTYLE, style)
# re-assert the new window style
root.wm_withdraw()
root.after(10, lambda: root.wm_deiconify())
if __name__ == '__main__':
GWL_EXSTYLE = -20
WS_EX_APPWINDOW = 0x00040000
WS_EX_TOOLWINDOW = 0x00000080
app = TestApp()
# print(app.tk.call('tk', 'windowingsystem'))
# # Here root.tk.call('tk', 'windowingsystem') calls tk windowingsystem in Tcl, and that returns 'win32',
# # 'aqua' or 'x11' as documented in tk
app.after(10, lambda: set_appwindow(root=app))
app.text.insert(1.0, 'Drag the window using the title or the empty area to the right of the\ntitle.'
' Try maximizing / minimizing.\n\n-- YOU MAY HAVE A PROBLEM WITH RESIZING --\n'
'-- ALSO IF YOU REMOVE `height` AND `width` KEYWORDS FROM THE TEXTBOX DECLARATION'
' AND FONT SIZE IS TOO BIG THE SCROLLBAR MAY DISAPPEAR --\nSO KEEP THOSE KEYWORDS THERE!')
app.mainloop()
When window is maximized you may not be able to see the taskbar. But you can still resize it using sizegrip. I don't yet know how to make window resizing possible from window borders with overrideredirect but Sizegrip works just fine.
Now regarding the set_appwindow function, this is what MSDN says:
The Shell creates a button on the taskbar whenever an application
creates a window that isn't owned. To ensure that the window button is
placed on the taskbar, create an unowned window with the
WS_EX_APPWINDOW extended style. To prevent the window button from
being placed on the taskbar, create the unowned window with the
WS_EX_TOOLWINDOW extended style. As an alternative, you can create a
hidden window and make this hidden window the owner of your visible
window.
Complete reference here
This may seem like a very long answer but I hope it covers all what you need and helps you.
Check out the following code it worked for me :-
import tkinter as tk
import tkinter.ttk as ttk
from ctypes import windll
GWL_EXSTYLE=-20
WS_EX_APPWINDOW=0x00040000
WS_EX_TOOLWINDOW=0x00000080
def set_appwindow(root):
hwnd = windll.user32.GetParent(root.winfo_id())
style = windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
style = style & ~WS_EX_TOOLWINDOW
style = style | WS_EX_APPWINDOW
res = windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, style)
root.wm_withdraw()
root.after(10, lambda: root.wm_deiconify())
def main():
root = tk.Tk()
root.wm_title("AppWindow Test")
button = ttk.Button(root, text='Exit', command=lambda: root.destroy())
button.place(x=10,y=10)
root.overrideredirect(True)
root.after(10, lambda: set_appwindow(root))
root.mainloop()
if __name__ == '__main__':
main()
In the function on_deiconify() we need to unbind the previous event <FocusIn> to avoid from blitting of the window we write like this
def on_deiconify(self, event):
""" function to deiconify or window """
self.overrideredirect(True)
set_appwindow(root=self)
self.unbind("<FocusIn>")

tkinter: why does this movable label move when i don't move it?

i have a moveable label. its position is read from a txt file and wrote to txt file when it was changed by dragging.
but the position of the label changes when i don't drag it, just by running the snippet.
i tried it with multiple labels and found out that each time i run the snippet the x and y of every single moveable label increment with the same number.
import tkinter as tk
def make_draggable(widget):
widget.bind("<Button-1>", on_drag_start)
widget.bind("<B1-Motion>", on_drag_motion)
def on_drag_start(event):
widget = event.widget
widget._drag_start_x = event.x
widget._drag_start_y = event.y
def on_drag_motion(event):
widget = event.widget
x = widget.winfo_x() - widget._drag_start_x + event.x
y = widget.winfo_y() - widget._drag_start_y + event.y
widget.place(x=x, y=y)
def get_new_positions():
x, y = label1.winfo_rootx(), label1.winfo_rooty()
positions["moveable_label"] = (x, y)
dictfile = open("labelposition.txt", "w") # save new positions
dictfile.write(str(positions))
dictfile.close()
def close_window():
main.destroy()
root = tk.Tk()
root.geometry('1200x900')
tk.Label(root, text="test").pack()
root.withdraw()
# start window
aWindow = tk.Toplevel(root)
aWindow.geometry('1200x900')
def change_window():
# call function which reads the y coordinates
aWindow.update()
get_new_positions()
#remove the other window entirely
aWindow.destroy()
#make root visible again
root.iconify()
root.deiconify()
tk.Button(aWindow, text="OK", command=change_window).pack()
# load dict with label positions
dictfile = open("labelposition.txt", "r")
positions = eval(dictfile.read()) # load label positions
dictfile.close()
# position label
label1 = tk.Label(aWindow, text="moveable")
label1.place(x=positions["moveable_label"][0], y=positions["moveable_label"][1])
make_draggable(label1)
root.mainloop()
The goal of my code snippet should be:
show a moveable label and position it according to the x y data saved in a txt file.
if the label is moved to another position with the mouse and the ok button is klicked, the new x y position is saved to the txt file.
a new empty page opens
but: when i run the snippet multiple times without touching the label with the mouse, the label position changes its position every time anyway (x and y get higher)
can you see where this x y change comes from?
The reason the location keeps changing throughout runs is because the method you use to capture the x and y coordinates of your widget return the coordinates relative to the top left corner of the screen, not the parent.
winfo_rootx():
Get the pixel coordinate for the widget’s left edge, relative to the screen’s upper left corner.
Returns: The root coordinate.
Instead of using winfo_root*, use winfo_*, which returns the location relative to the parent window.
winfo_x():
Returns the pixel coordinates for the widgets’s left corner, relative to its parent’s left corner.
So inside your get_new_positions function, the x and y coordinates should be:
def get_new_positions():
x, y = label1.winfo_x(), label1.winfo_y()
positions["moveable_label"] = (x, y)
dictfile = open("labelposition.txt", "w") # save new positions
dictfile.write(str(positions))
dictfile.close()

How to get multiple ovals moving around the canvas?

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.

Resources