I am trying to make a graphical slot machine and am currently working on trying to make the slots "spin". However the buttons seem to skip all other changes and go straight to the final value. I have tried using time.sleep but this too doesn't work. Currently I am looping through a second function but still does not work.
How can I ensure the buttons show all values they have taken throughout the looping process?
Here is the code below:
import tkinter as tk,random,time
root=tk.Tk()
results=["#","!","$","W","*","£","#","X","%"]
class spin:
def __init__(self,rowa,columna,text):
self.text=text
self=tk.Button(root, height=1,width=2,text=text)
self.grid(row=rowa,column=columna)
class spun:
def __init__(self,rowa,columna,text,com):
self.text=text
self=tk.Button(root, height=1,width=15,text=self.text,command=com)
self.grid(row=rowa,column=columna, columnspan=5)
def rotate():
for i in range(0,10):
part2()
time.sleep(0.00005)
def part2():
global slot1,slot2,slot3,slot4,slot5
slot1=spin(1,0,results[random.randint(0,len(results)-1)])
slot2=spin(1,1,results[random.randint(0,len(results)-1)])
slot3=spin(1,2,results[random.randint(0,len(results)-1)])
slot4=spin(1,3,results[random.randint(0,len(results)-1)])
slot5=spin(1,4,results[random.randint(0,len(results)-1)])
slot1=spin(1,0,"#")
slot2=spin(1,1,"£")
slot3=spin(1,2,"%")
slot4=spin(1,3,"$")
slot5=spin(1,4,"#")
spinner=spun(2,0,"Spin",rotate)
root.mainloop()
As stated in the comments, the key is to use update_idletasks() in the for loop to force the GUI to update at each iteration.
Below is a suggestion about how to structure your code. Instead of using global slot1, ... you could create a class for your slot machine. As indicated in the comments you don't need to recreate the buttons at each iteration in rotate(), you just need to change their texts with .configure(text=<new value>). Finally I used random.choices() to directly draw a random value in a list instead of drawing the item's index with random.randint().
import tkinter as tk
import random
class Spinner(tk.Frame):
def __init__(self, master, results, **kw):
tk.Frame.__init__(self, master, **kw)
self.results = results
self.slots = []
for i in range(5):
b = tk.Button(self, height=1, width=2, text=self.results[i])
b.grid(row=0, column=i)
self.slots.append(b)
tk.Button(self, height=1, width=15, text='Spin',
command=self.rotate).grid(row=1, columnspan=5)
def rotate(self):
for i in range(0, 10):
for slot in self.slots:
slot.configure(text=random.choices(self.results))
self.update_idletasks() # force GUI to update
self.after(5) # tkinter method which pause the GUI, the time is given in ms
# it can also be use to schedule a function call (see Tkinter doc)
root = tk.Tk()
results = ["#", "!", "$", "W", "*", "£", "#", "X", "%"]
spinner = Spinner(root, results)
spinner.pack()
root.mainloop()
Note: you might want to have a look at the style guideline PEP 8 which helps writing clear python code (e.g. they advise to capitalize class names, put one space after a comma, ...)
I have a class called Bones I have 5 Bones in my skeleton dictionary. However in my actual implementation there are 300+ bones, that's why I am asking this question today on stackoverflow.
Each Bone has:
ID: An int to identify a bone
w: w position (float between -1 and 1)
x: x position (float between -1 and 1)
y: y position (float between -1 and 1)
z: z position (float between -1 and 1)
Bone.py
INCREMENT = 0.01
class Bone:
def __init__(self, boneId, w, x, y, z):
self.id = boneId
self.w = w
self.x = x
self.y = y
self.z = z
def shouldChangePos(self, num):
if (num >= 1 or num <= -1):
return False
return True
def incrW(self):
if(self.shouldChangePos(self.w)):
self.w = self.w + INCREMENT
def decrW(self):
if(self.shouldChangePos(self.w)):
self.w = self.w - INCREMENT
def incrX(self):
if(self.shouldChangePos(self.x)):
self.x = self.x + INCREMENT
def decrX(self):
if(self.shouldChangePos(self.x)):
self.x = self.x - INCREMENT
def incrY(self):
if(self.shouldChangePos(self.y)):
self.y = self.y + INCREMENT
def decrY(self):
if(self.shouldChangePos(self.y)):
self.y = self.y - INCREMENT
def incrZ(self):
if(self.shouldChangePos(self.z)):
self.z = self.z + INCREMENT
def decrZ(self):
if(self.shouldChangePos(self.z)):
self.z = self.z - INCREMENT
Explanation of the problem
I am trying to make a tkinter GUI that looks something like this:
Legend:
Green - represents a Frame (just my annotation to explain)
Red - are attributes of the object (just my annotation to explain)
Black - are methods of the object (just my annotation to explain)
Blue - are text and buttons displayed to me
As you can see, it shows the ID, w, x, y, z. And under it, there is a + button and a - button. Each time these buttons get clicked, I want to decrease the corresponding value in the object and update the tkinter number displayed. I know how to do this manually, but as per my requirement, I have 300+ Bones. I cannot make these frames manually.
How can I create these frames in a loop and update the value displayed on the GUI and object when a + or - button is clicked?
main.py
from tkinter import *
from tkinter import ttk
from Bone import *
skeleton = {
1: Bone(-0.42, 0.1, 0.02, 0.002, 0.234),
4: Bone(4, 0.042, 0.32, 0.23, -0.32),
11: Bone(11, 1, -0.23, -0.42, 0.42),
95: Bone(95, -0.93, 0.32, 0.346, 0.31),
}
root = Tk()
root.geometry('400x600')
boneID = Label(root, text="ID: 1")
boneID.grid(row=1, column=1, sticky=W, padx=(0, 15))
w = Label(root, text="-0.42")
w.grid(row=1, column=2, sticky=W)
x = Label(root, text="0.02")
x.grid(row=1, column=4, sticky=W)
y = Label(root, text="0.002")
y.grid(row=1, column=6, sticky=W)
z = Label(root, text="0.234")
z.grid(row=1, column=8, sticky=W)
wPlusBtn = Button(root, text="+")
wPlusBtn.grid(row=2, column=2)
wMinusBtn = Button(root, text="-")
wMinusBtn.grid(row=2, column=3, padx=(0, 15))
xPlusBtn = Button(root, text="+")
xPlusBtn.grid(row=2, column=4)
xMinusBtn = Button(root, text="-")
xMinusBtn.grid(row=2, column=5, padx=(0, 15))
yPlusBtn = Button(root, text="+")
yPlusBtn.grid(row=2, column=6)
yMinusBtn = Button(root, text="-")
yMinusBtn.grid(row=2, column=7, padx=(0, 15))
zPlusBtn = Button(root, text="+")
zPlusBtn.grid(row=2, column=8)
zMinusBtn = Button(root, text="-")
zMinusBtn.grid(row=2, column=9, padx=(0, 15))
root.mainloop()
TL;DR - break your one big problem into several smaller problems, and then solve each problem separately.
The main window
Start by looking at the overall design of the UI. You have two sections: a panel holding bones, and a panel holding random text. So the first thing I would do is create these panels as frames:
root = tk.Tk()
bonePanel = tk.Frame(root, background="forestgreen", bd=2, relief="groove")
textPanel = tk.Frame(root, background="forestgreen", bd=2, relief="groove")
Of course, you also need to use pack or grid to lay them out on the window. I recommend pack since there are only two frames and they are side-by-side.
Displaying bones
For the bone panel, you appear to have a single row for each bone. So, I recommend creating a class to represent each row. It can inherit from Frame, and be responsible for everything that goes on inside that row. By inheriting from Frame, you can treat it just like a custom widget with respect to laying it out on the screen.
The goal is for your UI code to look something like this:
bones = (
Bone(boneId=1, w=-0.42, x=0.02, y=0.002, z=0.234),
Bone(boneId=4, w=0.042, x=0.32, y=0.23, z=-0.32),
Bone(boneId=11, w=1, x=-0.23, y=-0.42, z=0.42),
...
)
bonePanel = tk.Frame(root)
for bone in bones:
bf = BoneFrame(bonePanel, bone)
bf.pack(side="top", fill="x", expand=True)
Again, you can use grid if you want, but pack seems like the natural choice since the rows are stacked top-to-bottom.
Displaying a single bone
Now, we need to tackle what each BoneFrame does. It appears to be made up of five sections: a section to display the id, and then four nearly identical sections for the attributes. Since the only difference between these sections is the attribute they represent, it makes sense to represent each section as an instance of a class. Again, if the class inherits from Frame we can treat it like it was a custom widget.
This time, we should pass in the bone, and perhaps a string telling it which id to update.
So, it might start out looking something like this:
class BoneFrame(tk.Frame):
def __init__(self, master, bone):
tk.Frame.__init__(self, master)
self.bone = bone
idlabel = tk.Label(self, text="ID: {}".format(bone.id))
attr_w = BoneAttribute(self, self.bone, "w")
attr_x = BoneAttribute(self, self.bone, "x")
attr_y = BoneAttribute(self, self.bone, "y")
attr_z = BoneAttribute(self, self.bone, "z")
pack is a good choice here since these sections are all lined up left-to-right, but you could use grid if you prefer. The only real difference is that using grid takes a couple more lines of code to configure row and column weights.
Widgets for the attribute buttons and labels
Finally, we have to tackle the BoneAttribute class. This is where we finally add the buttons.
It's pretty straight-forward and follows the same pattern: create the widgets, then lay them out. There's a bit more, though. We need to hook up the buttons to update the bone, and we also need to update the label whenever the bone changes.
I won't go into all of the details. All you need to do is to create a label, a couple of buttons, and functions for the buttons to call. Plus, we want a function to update the label when the value changes.
Let's start with tha function to update the label. Since we know the name of the attribute, we can do a simple lookup to get the current value and change the label:
class BoneAttribute(tk.Frame):
...
def refresh(self):
value = "{0:.4f}".format(getattr(self.bone, self.attr))
self.value.configure(text=value)
With that, we can update the label whenever we want.
Now it's just a matter of defining what the buttons do. There are better ways to do it, but a simple, straight-forward way is to just have some if statements. Here's what the increment function might look like:
...
plus_button = tk.Button(self, text="+", command=self.do_incr)
...
def do_incr(self):
if self.attr == "w":
self.bone.incrW()
elif self.attr == "x":
self.bone.incrX()
elif self.attr == "y":
self.bone.incrY()
elif self.attr == "z":
self.bone.incrZ()
self.refresh()
The do_decr function is identical, except that it calls once of the decrement functions.
And that's about it. The key point here is to break down your larger problem into smaller problems, and then tackle each smaller problem one at a time. Whether you have three bones or 300, the only extra code you have to write is where you initially create the bone objects. The UI code stays exactly the same.
There are two issues here: creating the frames in a loop, and updating the values upon a press on the +/- buttons.
To handle the frame issue, I suggest that you create a BoneFrame class that holds all the widgets (buttons and labels) related to one Bone instance.
There, you can also bind the buttons to the Bone methods so as to act on the values.
Something like that - I'm sure you'll know how to complete this with the other variables and the grid coordinates you want
class BoneFrame(tk.Frame):
def __init__(self, parent, bone):
super().__init__(parent)
# Create your widgets
self.x_label = tk.Label(self, text=bone.x)
self.x_decr_button = tk.Button(self, text="-", action=bone.decr_x)
self.x_incr_button = tk.Button(self, text="+", action=bone.incr_x)
...
# Then grid all the widgets as you want
self.x_label.grid()
...
Then you can easily iterate over your dict of Bones, instantiate BoneFrame every time, and pack or grid that instance to a parent container.
Maybe you'll want to add a bone_id to the parameters of BoneFrame.__init__ and pass it in the loop.
# In your main script
for bone_id, bone in skeleton.items():
frame = BoneFrame(root, bone)
frame.pack()
For now, the values in the label never update.
That's because we just set their text once, and then we never update them.
Rather than binding the buttons directly to methods of Bone, we can define more complex methods in BoneFrame that achieve more logic, including updating the values, and also refreshing the widgets.
Here's one way to do it:
class BoneFrame(tk.Frame):
def __init__(self, parent, bone):
super().__init__(parent)
# Store the bone to update it later on
self.bone = bone
# Instantiate a StringVar in order to be able to update the label's text
self.x_var = tk.StringVar()
self.x_var.set(self.bone.x)
self.x_label = tk.Label(self, textvariable=self.x_var)
self.x_incr_button = tk.Button(self, text="+", action=self.incr_x)
...
def incr_x(self):
self.bone.incr_x()
self.x_var.set(self.bone.x)
So we need a StringVar to update the content of the label.
To sum it up, instead of binding the button to bone.incr_x, we bind it to self.incr_x, which allows us to do whatever we want upon a button press, that is 1. change the value in the Bone instance, and 2. update the value displayed by the label.
A usual way to address this kind of problem is to create functions (or class methods) to perform the repetitious bits of the code (i.e. the DRY principle of software engineering).
Ironically, doing this can itself be a little tedious as I quickly discovered trying to refactor your existing code to be that way — but below is the result which should give you a good idea of how it can be done.
Besides reducing the amount of code you have to write, it also simplifies making changes or adding enhancements because they only have be done in one spot. Often the trickiest thing is determining what arguments to pass the functions so they can do what it needs to be done in a generic way and avoiding hardcoded values.
from tkinter import *
from tkinter import ttk
from Bone import *
skeleton = {
1: Bone(1, -0.42, 0.02, 0.002, 0.234),
4: Bone(4, 0.042, 0.32, 0.23, -0.32),
11: Bone(11, 1, -0.23, -0.42, 0.42),
95: Bone(95, -0.93, 0.32, 0.346, 0.31),
}
def make_widget_group(parent, col, bone, attr_name, variable, incr_cmd, decr_cmd):
label = Label(parent, textvariable=variable)
label.grid(row=1, column=col, sticky=W)
def incr_callback():
incr_cmd()
value = round(getattr(bone, attr_name), 3)
variable.set(value)
plus_btn = Button(parent, text='+', command=incr_callback)
plus_btn.grid(row=2, column=col)
def decr_callback():
decr_cmd()
value = round(getattr(bone, attr_name), 3)
variable.set(value)
minus_btn = Button(parent, text='-', command=decr_callback)
minus_btn.grid(row=2, column=col+1, padx=(0, 15))
def make_frame(parent, bone):
container = Frame(parent)
boneID = Label(container, text='ID: {}'.format(bone.id))
boneID.grid(row=1, column=1, sticky=W, padx=(0, 15))
parent.varW = DoubleVar(value=bone.w)
make_widget_group(container, 2, bone, 'w', parent.varW, bone.incrW, bone.decrW)
parent.varX = DoubleVar(value=bone.x)
make_widget_group(container, 4, bone, 'x', parent.varX, bone.incrX, bone.decrX)
parent.varY = DoubleVar(value=bone.y)
make_widget_group(container, 6, bone, 'y', parent.varY, bone.incrY, bone.decrY)
parent.varZ = DoubleVar(value=bone.z)
make_widget_group(container, 8, bone, 'z', parent.varZ, bone.incrZ, bone.decrZ)
container.pack()
if __name__ == '__main__':
root = Tk()
root.geometry('400x600')
for bone in skeleton.values():
make_frame(root, bone)
root.mainloop()
Screenshot of it running:
BTW, I noticed a lot of repetition in the Bone.py module's code, which could probably be reduced in a similar manner.
Total noob, seriously and angrily struggling with Python...
What I'm trying to do SHOULD be simple:
Make a button.
Connect that button go a function.
Click button --> run function.
The problem comes when we have to use CLASS (which, no matter how much I read, study - or even pay to take classes continues to make zero sense to me)...
I've tried every concieveable combination of putting this little convert() function IN the class, of adding self.convert or root.convert - and NONE of it works. And, I am clueless why - or what to try next.
Here's the code:
from tkinter import *
from tkinter.ttk import Frame, Button, Style
def convert():
print("clicked")
kg = entry_kg.get()
print(kg)
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI() # initiate the GUI
# -------------------------------
def initUI(self):
self.master.title("Weight Converter")
self.pack(fill=BOTH, expand=True)
# -------------------------------------
frame_kg = Frame(self) # frame for Kilograms
frame_kg.pack(fill=X)
lbl_kg = Label(frame_kg, text="Kilograms", width=16)
lbl_kg.pack(side=LEFT, padx=5, pady=5)
entry_kg = Entry(frame_kg)
entry_kg.pack(fill=X, padx=(5, 30), expand=True)
# ------------------------------------------------
frame_btn = Frame(self) # frame for buttons
frame_btn.pack(fill=BOTH, expand=True, padx=20, pady=5)
btn_convert=Button(frame_btn, text="Convert", command=convert)
btn_convert.pack(side=LEFT, padx=5, pady=5)
# -------------------------------------------
def main():
root = Tk()
root.geometry("300x200+300+200")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
What am I doing wrong?
How to do it right?
The seemingly arbitrary and useless over-complication of a simple task is seriously maddening...
If you want your functions to be designed outside of the class, but want to call it from a button defined in the class, the solution is to create a method in your class which takes the input values and then passes them to the external function.
This is called decoupling. Your function is decoupled from the implementation of the UI, and it means that you are free to completely change the implementation of the UI without changing the function, and vice versa. It also means that you can reuse the same function in many different programs without modification.
The overall structure of your code should look something like this:
# this is the external function, which could be in the same
# file or imported from some other module
def convert(kg):
pounds = kg * 2.2046
return pounds
class Example(...):
def initUI(self):
...
self.entry_kg = Entry(...)
btn_convert=Button(..., command=self.do_convert)
...
def do_convert(self):
kg = float(self.entry_kg.get())
result = convert(kg)
print("%d kg = %d lb" % (kg, result))
Here's a modified version of your code that works. The changes have been indicated with ALL CAPS line comments. I obviously misunderstood your question (which does say you could figure out how to make the convert() function part of the class. However, you mentioned you wanted the opposite of that, so I'm modified the code here accordingly.
Essentially the problem boils down to the convert() function needing to access a tkinter.Entry widget that's created somewhere else—inside your Example class in this case.
One way of doing that would be to save the widget in a global variable and access it through the variable name assigned to it. Many folks do that because it's easiest to thing to do, but as you should know, global variables are considered a bad practice and are generally something to be avoided.
There's a common way to avoid needing one with tkinter, which is sometimes called "The extra arguments trick". To use it all you need to do is create a short anonymous function with an argument that has a default value defined—which in this case will be the Entry widget you want passed to the now "wrapped" convert() function. This can be done using what's called a lambda expression. The revised code below and the comments in it show and describe how to do this:
from tkinter import *
from tkinter.ttk import Frame, Button, Style
def convert(entry_widget): # ADDED WIDGET ARGUMENT
""" Some function outside class. """
print("clicked")
kg = entry_widget.get() # REFERENCE ENTRY WIDGET PASSED AS ARGUMENT
print(kg)
class Example(Frame):
def __init__(self):
super().__init__()
self.initUI() # initiate the GUI
def initUI(self):
self.master.title("Weight Converter")
self.pack(fill=BOTH, expand=True)
frame_kg = Frame(self) # frame for Kilograms
frame_kg.pack(fill=X)
lbl_kg = Label(frame_kg, text="Kilograms", width=16)
lbl_kg.pack(side=LEFT, padx=5, pady=5)
entry_kg = Entry(frame_kg)
entry_kg.pack(fill=X, padx=(5, 30), expand=True)
frame_btn = Frame(self) # frame for buttons
frame_btn.pack(fill=BOTH, expand=True, padx=20, pady=5)
btn_convert=Button(frame_btn, text="Convert",
# DEFINE ANONYMOUS FUNCTION WITH DEFAULT ARGUMENT SO IT'S
# AUTOMATICALLY PASSED TO THE TARGET FUNCTION.
command=lambda entry_obj=entry_kg: convert(entry_obj))
btn_convert.pack(side=LEFT, padx=5, pady=5)
def main():
root = Tk()
root.geometry("300x200+300+200")
app = Example()
root.mainloop()
if __name__ == '__main__':
main()
Your entry_kg is not known anywhere outside the scope of initUI method. That is why. You could convert it from a method variable to instance attribute to be within reach for class methods by replacing:
entry_kg = Entry(frame_kg)
entry_kg.pack(fill=X, padx=(5, 30), expand=True)
with:
self.entry_kg = Entry(frame_kg)
self.entry_kg.pack(fill=X, padx=(5, 30), expand=True)
Only then:
You can mention it in a class method like:
...
kg = self.entry_kg.get()
That way you if you make your convert a method under Example again:
def initUI(self):
...
def convert(self): # is defined under the same scope as initUI
print("clicked")
kg = self.entry_kg.get() # notice that it now refers to self.e...
print(kg)
also don't forget to replace command option as well:
btn_convert=Button(..., command=self.convert)
Or only then:
When outside the class scope, by using dot notation on the object that the class creates:
def main():
root = Tk()
root.geometry("300x200+300+200")
app = Example()
kg = app.entry_kg.get() # This would return an empty string, but it would still be valid
root.mainloop()
to be used with global methods, such as the current state of convert you need to make app (the object it is an attribute of) global, or pass it explicitly to the method.
I wrote a small python function, which takes several numerical input parameters and prints many lines with statements, which going to be used in an experiment, like this toy example:
def myADD(x,y,z):
res = x + y + z
print("the result is: {0}+{1}+{2}={3}").format(x,y,z,res)
I would like to create a minimalistic GUI, simply an overlay which calls my myADD.py script, where I can fill those parameters x,y,z and after clicking a "compute" button a text field occurs with the print statement.
Does anyone has a template, I was looking into the TKinter, but my attempts by manipulating other templates didn't succeed.
Would appreciate help, thanks.
Tkinter is a fantastic choice since it is built-in. It is ideally suited for this type of quick, minimalistic GUI.
Here's a basic framework for a Tkinter app to show you how simple it can be. All you need to do is add your function, either by importing it or including it in the same file:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.entry = {}
# the basic layout is a form on the top, and
# a submit button on the bottom
form = tk.Frame(self)
submit = tk.Button(self, text="Compute", command=self.submit)
form.pack(side="top", fill="both", expand=True)
submit.pack(side="bottom")
# this fills in the form with input widgets for each parameter
for row, item in enumerate(("x", "y", "z")):
label = tk.Label(form, text="%s:"%item, anchor="w")
entry = tk.Entry(form)
label.grid(row=row, column=0, sticky="ew")
entry.grid(row=row, column=1, sticky="ew")
self.entry[item] = entry
# this makes sure the column with the entry widgets
# gets all the extra space when the window is resized
form.grid_columnconfigure(1, weight=1)
def submit(self):
'''Get the values out of the widgets and call the function'''
x = self.entry["x"].get()
y = self.entry["y"].get()
z = self.entry["z"].get()
print "x:", x, "y:", y, "z:", z
if __name__ == "__main__":
# create a root window
root = tk.Tk()
# add our example to the root window
example = Example(root)
example.pack(fill="both", expand=True)
# start the event loop
root.mainloop()
If you want the result to appear in the window, you can create another instance of a Label widget, and change it's value when you perform the computation by doing something like self.results_label.configure(text="the result")
Tkinter is usually a good start because it is bundled with Python (tutorial).
That said, Tk is pretty old and therefore "odd" at times. If you want a more modern UI, have a look at PyQt. It's based on Qt but it doesn't come with Python by default, so you have to install it manually.