.place_forget() not executed until after everything else in the function - python

So I have the below function that should forget a button, iterate a score, wait one second, and bring the button back.
def iteration():
global score
#should hide button
b1.place_forget()
#raise score
score += 1
label.config(text = score)
#wait one second
sleep(1)
#bring button back
b1.place(relx = 0.3, y = 30)
Instead, the place_forget() does not run until after everything else, resulting in the button never blinking and waiting a second before iterating the score. Why are things happening in this order and how can I fix it? Here is the rest of my code:
from tkinter import *
from time import sleep
global score
score = 0
def iteration():
global score
#should hide button
b1.place_forget()
#raise score
score += 1
label.config(text = score)
#wait one second
sleep(1)
#bring button back
b1.place(relx = 0.3, y = 30)
root = Tk()
root.geometry("150x100")
#make label
label = Label(root, text = score)
# place in the window
label.place(relx=0.4, y=5)
#make and place button 1
b1 = Button(root, text = "hide text",
command = lambda: iteration())
b1.place(relx = 0.3, y = 30)
# make and place button 2
b2 = Button(root, text = "retrieve text",
command = lambda: iteration())
b2.place(relx = 0.3, y = 50)
# Start the GUI
root.mainloop()

I think the process is happening but your sleep(1) is freezing the GUI and hence you cannot see it.
Either, replace sleep(1) and then place the widget with after(), might get your desired effect, like:
def iteration():
global score
#should hide button
b1.place_forget()
#raise score
score += 1
label.config(text = score)
#wait one second and bring button back
root.after(1000,lambda: b1.place(relx = 0.3, y = 30))
after() prevents the GUI from lagging for one second where it is supposed to be hiding and showing.
Or else, use threading like:
import threading
.... #same old codes
b1 = Button(root, text = "hide text",command =lambda: threading.Thread(target=iteration).start())
Now the sleep(1) does not cause GUI to lag as it is not in the tkinter thread.
Or else, you can get it to work with update() but the GUI still might be frozen but it updates, the button "blinking", like:
root.update()
sleep(1)
#bring button back
b1.place(relx = 0.3, y = 30)

Related

Python: why does first restart works, but second - doesn't?

This is a simple minesweeper game with implementation of windows user interface
The only thing this function has to do is to erase all information (such as text) on the buttons, as well as to create a new random array. It works completely well the 1st time after i press the button, but the second it doesn't work. 2nd time it erases everything (it does its job again as planned), but other functions don't work (I press the buttons after the 2nd restart, and nothing happens, but after the 1st restart everything is fine).
What's going on?? Is it a problem of the memory, where variables are stored, or a specific of the graphical user interface, I am not aware of?
from tkinter import *
def new_game():
lost = False
label['text'] = str(mines) + ' mines left'
global mine_sweep
mine_sweep = mine_randomization().tolist()
for row in range(10):
for col in range(10):
buttons[row][col]['text'] = ''
window = Tk()
window.title('minesweeper')
label = Label(text=str(mines)+' mines left', font=('consolas', 20))
label.pack(side='top')
reset_button = Button(text='restart', command=new_game)
reset_button.pack(side='top')
buttons = buttons.tolist()
frame = Frame(window)
frame.pack()
for row in range(10):
for col in range(10):
buttons[row][col] = Button(frame, text='', font=('consolas', 10),
width=2, height=1,
command= lambda row=row, col=col: cell(row, col))
buttons[row][col].grid(row=row, column=col)
window.mainloop()
(I can't place the whole program here, just part which doesn't work)
here is what the function cell does:
def cell(row, col):
global lost
if buttons[row][col]['text'] == '' and mine_sweep[row][col] == 0 and not lost:
open_fields(row, col)
elif buttons[row][col]['text'] == '' and mine_sweep[row][col] == 1 and not lost:
buttons[row][col].config(bg='red', font=('consolas', 10))
buttons[row][col]['text'] = '*'
label['text'] = 'You lost!'
lost = True
if check_win():
label['text'] = 'You win!'
Yes, #Matiiss was wright, and the solution was that the variable lost is used in different functions, that's why it should be global. Moreover, when the first game is complete, the lost should again be set to false, in order to start a new game and the computer to know you have actually not yet 'lost'.

Python how to use Tkinter GUI without interfering the main code loop

I would like to implement a very simple GUI for my project. I was previously using just Print statements to output some text and data. However, that is not very conveneint and since a person will need to operate a device that I am coding, he needs to be clearly see the instructions that I am going to display on GUI.
my code:
main()
myConnection = mysql.connector.connect( host=hostname, user=username, passwd=password, db=database )
counter = 0
window = tk.Tk()
window.title("GUI")
window.geometry("400x200")
while(1):
# OPERACIJOS KODAI:
# 0 - PILDYMAS
# 1 - KOMPLEKTAVIMAS
# 2 - NETINKAMAS KODAS
tk.Label(window,text = "Scan barcode here:").pack()
entry = tk.Entry(window)
entry.pack()
var = tk.IntVar()
button = tk.Button(window,text="Continue",command = lambda: var.set(1))
button.pack()
print("waiting...")
button.wait_variable(var)
result = entry.get()
print("Entry string=",result)
var.set(0)
operacijos_kodas=Scanning_operation(myConnection,result)
print("operacijos kodas=",operacijos_kodas)
if(operacijos_kodas == 0):
tk.label(window,text = "PILDYMO OPERACIJA:").pack()
pildymo_operacija(myConnection)
elif(operacijos_kodas == 1):
tk.Label(window,text = "PAKAVIMO OPERACIJA:").pack()
insertData_komplektacija(myConnection,"fmb110bbv801.csv");
update_current_operation(myConnection);
picking_operation();
elif(operacijos_kodas == 2):
print("Skenuokite dar karta")
#break
window.mainloop();
Nothing is being displayed. It just opens up an empty GUI window.
First of all, I am unsure where should I call function window.mainloop().
Secondly, since my system runs in an infinite while loop ( the operation starts when a user scans a bar-code, then he completes an operation and the while loop starts over again (waiting for user to scan a bar-code). So I just simply have to display some text and allow user to input data in the text box.
Could someone suggest me whether this GUI is suitable for my needs or I should look for an alternatives?
UPDATE*********************
I have tried to use mainloop:
print ("Using mysql.connector…")
main()
GPIO_SETUP()
myConnection = mysql.connector.connect( host=hostname, user=username, passwd=password, db=database )
counter = 0
window = tk.Tk()
window.resizable(False,False)
window_height = 1000
window_width = 1200
#window.attributes('-fullscreen',True)
#window.config(height=500,width=500)
#can = Canvas(window,bg='red',height=100,width=100)
#can.place(relx=0.5,rely=0.5,anchor='center')
window.title("GUI")
screen_width = window.winfo_screenwidth()
screen_height= window.winfo_screenheight()
x = int((screen_width/ 2) - (window_width / 2))
y = int((screen_height/ 2) - (window_height / 2))
window.geometry("{}x{}+{}+{}".format(window_width,window_height,x,y))
label1=Label(window,text = "SKENUOKITE BARKODA(GUID) ARBA DAIKTO RIVILINI KODA:")
label1.pack()
entry = Entry(window)
entry.pack()
var = tk.IntVar()
button = Button(window,text="Testi operacija",width = 30,command = lambda: var.set(1))
button.pack()
#button2 = Button(window,text="RESTARTUOTI SISTEMA",width = 30,command = restart_devices())
#button2.pack()
print("waiting...")
button.wait_variable(var)
Scanned_serial = entry.get()
print("Entry string=",Scanned_serial)
var.set(0)
label2=Label(window,text = "Vykdoma operacija:")
label2.pack()
window.update()
window.after(1000,Full_operation(Scanned_serial,label2,window))
window.mainloop()
This is my code. As you can see. i call Full_operation function and then window.mainloop()
my Full_operation:
def Full_operation(Scanned_serial,label2,window):
operacijos_kodas=Scanning_operation(myConnection,Scanned_serial)
print("operacijos kodas=",operacijos_kodas)
if(operacijos_kodas == 0):
label2.config(text = "SPAUSKITE MYGTUKA ANT DEZES KURIA NORITE PILDYTI:")#update the label2
window.update()#call update to update the label
pildymo_operacija(myConnection,Scanned_serial,label2,window)
elif(operacijos_kodas == 1):
insertData_komplektacija(myConnection,"fmb110bbv801.csv");
update_current_operation(myConnection);
#label2.config(text = "IMKITE DAIKTUS IS ZALIOS DEZUTES:")#update the label2
picking_operation(myConnection,label2);
elif(operacijos_kodas == 2):
print("Skenuokite dar karta")
label2.config(text = "NUSKENUOTAS NEGALIMAS KODAS:")#update the label2
window.update()#call update to update the label
How can I ensure that everytime I enter FUll_operation function I start from clean GUI again and start another operation.
Now I am able to complete operation once. After that, the GUI is not responsive.
I have added a print statement at the beggining of my full_operation and it does not execute after I complete it once so my mainwindow does not seem to work properly.
You'll need to adapt your code to work with a GUI. You can't introduce infinite loops in to tkinter GUI's without causing all sorts of problems.
Mainloop should only be called once.
I'd suggest that you move all of your scanning/saving operations in to a separate function which you schedule to occur periodically using the tkinter after method.
For example if you call your function scan you would schedule it to occur after 1 second using
root.after(1000, scan)
A more advanced method would be to have your scanning code running on a separate thread.
Also, you are currently trying to create the label each time you go round the while loop rather than just creating and packing them once and updating the text of the labels when you perform the "scanning". You can update the text of a label using the config method, for example
## Create a label
label1 = tk.Label(window,text = "PAKAVIMO OPERACIJA:")
##Pack the label
label1.pack()
## Update the text later
label1.config(text="New Text")
Here is an example of updating tkinter widgets periodically from a function.
import tkinter as tk
import random
def scanning():
num = random.randint(0,100)
entryTemperature.delete(0, tk.END) #Delete the current contents
entryTemperature.insert(0, f"{num} K") #Add new text
root.after(1000, scanning) #Schedule the function to run again in 1000ms (1 second)
root = tk.Tk()
entryTemperature = tk.Entry(root)
entryTemperature.grid(padx=50,pady=50)
root.after(1000, scanning)
root.mainloop()

Tkinter label text not updating, label is visible and text value is up to date

When trying to display text overtime on the tkinter window through a button press, the label does not update correctly. After testing multiple different scenarios, the label is classified as visible, the text value for it is correct, the function itself works without a button press and no matter if using pack, grid or place the result is the same. No matter how the label is updated, it is never displayed on the screen, but only if the button calls the function. When the button is pressed, it runs through the function, printing out everything along the way, and therefore the button itself is also working. Code below:
def label_change(self, text):
""" Repeats f|char_show (with a pause) until <text> equals the label text.
Parameters:
text (str): The text displayed in the label.
"""
if self.last_txt == text:
pass
def char_show():
""" Adds the next single character of <text> to the label text.
Curtosy of Hritik Bhat on:
stackoverflow.com/questions/56684556/how-to-have-variable-text-in-a-label-in-tkinter
"""
if self.label_store == "":
self.label_index = 0
self.label_store += text[self.label_index]
print(self.label_index)
self.label_index += 1
self.display.place_forget()
self.display.config(text=self.label_store)
self.display.update()
self.display.place()
print(self.display.cget("text"))
self.display.update_idletasks()
self.label_change(self.txt)
If you want to add char with delay then you could use root.after(time_ms, function_name) to execute function with delay. This doesn't stops mainloop() and it doesn't need update() and `update_idletask()
import tkinter as tk
def start():
global label_index
# add next char to label
display['text'] += text[label_index]
label_index += 1
# check if there is need to run it again
if label_index < len(text):
# run again after 200ms
root.after(200, start)
# ---
text = 'Hello World'
label_index = 0
root = tk.Tk()
display = tk.Label(root)
display.pack()
button = tk.Button(root, text="Start", command=start)
button.pack()
root.mainloop()
EDIT: this version blocks button when it updates label. It also reset settings when it starts animation again.
import tkinter as tk
def add_char():
global label_index
global running
# add next char to label
display['text'] += text[label_index]
label_index += 1
# check if there is need to run it again
if label_index < len(text):
# run again after 200ms
root.after(200, add_char)
else:
# unblock button after end
running = False
def start():
global label_index
global running
# check if animation is running
if not running:
# block button
running = True
# reset settings
display['text'] = ''
label_index = 0
# run animation
add_char()
# ---
text = 'Hello World'
label_index = 0
running = False
root = tk.Tk()
display = tk.Label(root)
display.pack()
button = tk.Button(root, text="Start", command=start)
button.pack()
root.mainloop()

Python Pause/Resume Button

I'm trying to make a button that can pause and resume the loop.
In code:
for index in range(10):
print index
// Runs until here, and pause
// Button pressed
print index + 10
// Runs until here, and pause
// Button pressed
In terminal:
0
// Button pressed
10
// Button pressed
1
// Button pressed
11
...
9
// Button pressed
19
// Button pressed
Is there a way that I can do pause and resume the loop with a button?
You can use generators for this by calling next() on each button click.
A little example of how:
import tkinter as tk
def plusten(x):
i = 0
while i<x:
yield i
yield i+10
i += 1
def next_item():
if gen:
try:
lbl["text"] = next(gen) #calls the next item of generator
except StopIteration:
lbl["text"] = "End of iteration" #if generator is exhausted, write an error
else:
lbl["text"] = "start loop by entering a number and pressing start loop button"
def start_gen():
global gen
try:
gen = plusten(int(ent.get()))
lbl["text"] = "loop started with value: " + ent.get()
except ValueError:
lbl["text"] = "Enter a valid value"
gen = None
root = tk.Tk()
ent = tk.Entry()
ent.pack()
tk.Button(root, text="start loop", command=start_gen).pack()
tk.Button(root, text="next item", command=next_item).pack()
lbl = tk.Label(root, text="")
lbl.pack()
root.mainloop()
'''
Import the following in the code
from tkinter import *
import time
The following programme creates a tkinter window with two buttons
(Pause and Quit),simultaneously printing a sequence of numbers
from 1 to numb(numb=20, for illustration here) with a controllable
time delay between prints (2 secs for illustration here). Pressing the
Pause button stops the printing, changing the button text to Resume.
After a while when the Resume button is pressed, the printing continues.
To quit printing, the quit button is pressed.
In the illustration, printing a number is symbolic. It could as well be
display of images with time time delays. When it is desired to view an
image in more detail, the display can be paused when that image is displayed
and resumed when desired to view remaining images.
'''
from tkinter import *
import time
global move,stop
move = 1 #this a variable which can take a value 1 or 0
stop='Initial-Value' #This is a variable to control the for and while loops.
#Assignment of string as variable is optional. It could be
#integer as well
def switch(): #controls back and forth switching between Pause and Resume
global move
move = not move
def come_out(): # controls the process to be running or quit
global stop
stop='stop'
numb=20
root = Tk()
Pause_Button=Button(root,bg='wheat',command=switch)
Pause_Button.place(relwidth=0.25,relx=0.2,rely=0.2)
Quit_Button=Button(root,bg='lightblue',text='Quit',command=come_out)
Quit_Button.place(relwidth=0.25,relx=0.5,rely=0.2)
time.sleep(2)
for s in range(numb):
if stop=='stop':
break
stop='continue_while_loop'
while s<numb and stop=='continue_while_loop':
stop='enter_for_loop_again'
Pause_Button.update()
if move==1:
Pause_Button["text"]='Pause'
print('s2=',s,'\n')
time.sleep(2)
else:
stop='continue_while_loop'
Pause_Button["text"]='Resume'
Quit_Button.update()
root.mainloop()

How to refresh the GUI window in Tkinter

Im trying to make a simple GUI with Tkinker that, when you press a button it adds 1 to the text on a label. However, the label simply remains at 0. Is there a way I can refresh it so it stays up-to-date?
heres what i have so far:
from Tkinter import *
clicks = 0
def click():
global clicks
global Window
clicks += 1
print 'ok', clicks
def close_window():
global Window
Window.destroy()
Window = Tk()
Number = Label(text = clicks)
close = Button(Window , text='exit' , command = close_window)
button = Button(Window,text = 'clickme' ,command = click)
button.pack()
close.pack()
Number.pack()
Window.mainloop()
clicks += 1 only changes the variable clicks.
Use Label.config(text=...) or Label['text'] = ... to change the label text.
def click():
global clicks
clicks += 1
Number.config(text=clicks) # <------
print 'ok', clicks
You almost have it, but for your label you don't want to use "text", you want "textvariable". However, this takes a StringVar as a variable, which forces a little bit of busywork:
Window = Tk()
strclicks = StringVar()
Number = Label(textvariable=clicks)
and within click():
clicks += 1
strclicks.set(clicks)
Using "text" evaluates the variable at creation; "textvariable" updates the label when the variable updates.

Categories