How to make tkinter frames in a loop and update object values?
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 Bone
s, 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.
Tkinter window not updating till after the loop is done
You made a very good first attempt and you were almost there. I have made some changes in your code, most importantly introduced a root
object (tk.Tk()
) so I can do root.update() in order to redraw to sort_canvas
in a new method blip_canvas
. To avoid some 'flickering' rather to destroy the canvas each time it is better to delete the 'bar' elements only.
Further I took the liberty to change some of the variable names to make it a bit more Pythonic (should make use of underscores rather than capitals) and added the if _name__ == '__main__'
statement.
Have a look at the code below.
import tkinter as tk
from tkinter import ttk
import random
class SortingVisualizer:
def __init__(self):
self.root = tk.Tk()
self.root.wm_title("Sorting Visualizer")
self.root.wm_minsize(width=600, height=500)
self.root.wm_resizable(width=False, height=False)
self.top_frame = tk.Frame(self.root)
self.top_frame.grid(row=0, sticky='w')
self.sort_options = ['Select Algorithm', 'Bubble sort', 'Quicksort', 'Merge sort']
self.option_var = tk.StringVar()
self.option_drop = ttk.OptionMenu(
self.top_frame, self.option_var, *self.sort_options)
self.option_drop.config(width=15)
self.option_drop.grid(row=0, column=1, sticky='ew')
self.sort_button = ttk.Button(
self.top_frame, text="Sort", command=self.bubble_sort)
self.sort_button.grid(row=0, column=2, sticky='w')
self.gen_button = ttk.Button(
self.top_frame, text="Generate New Array", command=self.new_array)
self.gen_button.grid(row=0, column=0)
self.sort_canvas = tk.Canvas(self.root)
self.bars = []
def new_array(self):
self.generate_array()
self.blip_canvas()
def generate_array(self):
self.array = []
self.num_operations = 0
i = 0
while i < 15:
height = random.randint(15, 200)
self.array.append(height)
i = i + 1
def draw_canvas(self):
label = "Number of Operations: " + str(self.num_operations)
self.num_label = tk.Label(self.top_frame, text=label)
self.num_label.grid(row=1)
self.sort_canvas = tk.Canvas(self.root, width=600, height=450)
self.sort_canvas.grid(row=1)
self.sort_canvas.create_line(15, 15, 585, 15)
bar_width = 20
bar_gap = bar_width + 10
start_x = 30
start_y = 15
self.bars = []
for bar_height in self.array:
x1 = start_x + bar_width
y1 = start_y + bar_height
self.bars.append(self.sort_canvas.create_rectangle(
start_x, start_y, x1, y1*2, fill='green'))
start_x = start_x + bar_gap
def blip_canvas(self):
self.sort_canvas.delete(self.bars)
self.draw_canvas()
self.root.update()
self.root.after(200)
def bubble_sort(self):
n = len(self.array)
for i in range(n):
for j in range(n-i-1):
if self.array[j] > self.array[j+1]:
self.array[j], self.array[j+1] = self.array[j+1], self.array[j]
self.num_operations += 1
self.blip_canvas()
def start(self):
tk.mainloop()
if __name__ == '__main__':
app = SortingVisualizer()
app.start()
Note in bubble_sort you do not need the temp variable to swap the values of array[j] and array[j+1]
Rather than to use time.sleep(0.2)
to set a delay I have used:
self.root.update()
self.root.after(200)
as suggested in Update button after delay
You can also stick to your original code and just make a few changes.
1) Change the sortButton
self.sortButton = ttk.Button(self.topFrame, text = "Sort", command=self.bubbleSort)
2) Indent the bubbleSort method to be aligned with SortingVisualizer
3) Change method redrawCanvas to:
def redrawCanvas(self):
self.sortCanvas.destroy()
self.drawCanvas()
self.update()
self.after(300)
and
4) in bubbleSort make the call to redrawCanvas
:
for j in range(0, n-i-1):
if self.array[j]>self.array[j+1]:
temp = self.array[j]
self.array[j] = self.array[j+1]
self.array[j+1] = temp
self.numOperations += 1
self.redrawCanvas()
et voila, it will work!
Tkinter adding entry values to list through a loop
This code snippet answers your question.
Use bind
to link your Entry
widget to Return key press and function answer
.
Then use method get
on the widget connected to Return key press to extract the contents of the Entry
widget.
import tkinter as tk
master = tk.Tk()
def answer( ev ):
print(ev.widget.get())
for a in range( 4 ):
e = tk.Entry(master, font = "Helvetica 14 normal")
e.pack(fill = "both", expand = True)
e.bind("<Return>", answer)
e.focus()
master.mainloop()
Python Tkinter, Update every second
Yes, you will need to create a method to update the values, and add that method to the tkinter mainloop with after
. I'd recommend naming it something else, because update
is already a tkinter method.
As a totally untested guess:
class Screen(tk.Frame):
def __init__(self, ADC, parent, controller):
tk.Frame.__init__(self, parent)
self.ADC = ADC
lbl = ttk.Label(self, text = "Test Display", background = "grey")
lbl.grid(column = 7, row = 8)
self.lblTestDisplay = ttk.Label(self, foreground = "lime", background = "black")
self.lblTestDisplay.grid(column = 7, row = 9, sticky = "ew")
self.adc_update() # start the adc loop
def adc_update(self):
displayText = self.ADC.ReadValues() #this method returns a list of values
for i in range(len(displayText)):
displayText[i] = round(displayText[i], 2)
self.lblTestDisplay.config(text = str(displayText)) # update the display
self.after(1000, self.adc_update) # ask the mainloop to call this method again in 1,000 milliseconds
Note I also split your label creation into 2 lines, one to initialize and one to layout. The way you had it is very bad; it leads to the variable assigned to None which leads to bugs.
Add images inside frame with loop in tkinter
The script is from @flavio-moraes logically works, however with a mix of the answers I got, I made the final script which fullfills my needs.
pc_images=[]
for i in range(int(len(obj))):
...
pc_images.append(PhotoImage(file=img_src))
live_pc_position = 10
for pc in pc_images:
#create the image
computer_icon = Label(black_background, image=pc, bg="black")
computer_icon.img = pc_images
computer_icon.place(x=live_pc_position, y=10)
# update values so the next image will load to the right
live_pc_position = live_pc_position + 40
How to make Python Tkinter GUI checks if an other thread has finished to make GUI changes?
You can use .wait_variable()
function to replace the while loop because .wait_variable()
does not block tkinter mainloop()
:
- create a tkinter variable:
var1 = StringVar()
- pass this variable to
databaseGetDF
class - call
.wait_variable(var1)
instead of the while loop - update this variable when the thread task completes
Example:
class databaseGetDF(Thread):
def __init__(self, filepath, notify_var):
super().__init__()
self.filepath = filepath
self.notify_var = notify_var
self.df = None
def run(self):
self.df = db.addCollectionData(self.filepath, True)
# update self.notify_var
self.notify_var.set('')
...
# create a tkinter variable
var1 = StringVar()
# pass the variable to databaseGetDF class
download_thread = databaseGetDF(filename, var1)
download_thread.start()
# wait for the thread completes
root.wait_variable(var1) # assume root is the root window
# thread completed, so you can get the dataframe
df = download_thread.df
...
Related Topics
Suppress Stdout/Stderr Print from Python Functions
Subtract One Dataframe from Another Excluding the First Column Pandas
Change a Colour of a Pixel in Python
No Unique Mode; Found 2 Equally Common Values
Finding Index of an Item Closest to the Value in a List That'S Not Entirely Sorted
Pip3: Command Not Found But Python3-Pip Is Already Installed
How to Make Print() Accept the User Input in Same Line
Iterate Over Worksheets, Rows, Columns
How to Change a Dataframe Column from String Type to Double Type in Pyspark
How to Create an Automatically Updating Gui Using Tkinter
Counting the Number of Duplicates in a List
What Is the Most Pythonic Way to Check If Multiple Variables Are Not None
Conditional Row Read of CSV in Pandas
Replacing Values in a Dataframe for Given Indices
Convert String to Python Class Object
How to Test Multiple Variables for Equality Against a Single Value