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

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

Related

Fast changing labels in Tkinter?

I would like to display some numbers, as fast as possible in Tkinter. The Program, I am trying to do, gets many numbers send and should show those.
Here is an similar environment, where tinter has to change a label very quickly.
from tkinter import *
import time
window = Tk()
lbl13 = Label(window, text="-")
lbl13.grid(column=0, row=0)
x = 0
while 1:
lbl13.config(text = str(x))
time.sleep(2)
x +=1
window.mainloop()
The Tkinter window doesn't even open on my computer. Is that because i have too weak hardware? What could I change that this Program also runs on my Computer. Thank you for every answer!
The infinite while loop will keep the program from getting to the line where you call window.mainloop(). You should call window.update() repeatedly instead of window.mainloop() at the end:
from tkinter import *
import time
window = Tk()
lbl13 = Label(window, text="-")
lbl13.grid(column=0, row=0)
x = 0
while 1:
lbl13.config(text = str(x))
window.update()
x +=1
Using after and a proper mainloop is probably a more more flexible way to achieve what you want; it is also reusable in different contexts, and can be used in an application that does more than trivially increment a number on a label:
maybe something like this:
import tkinter as tk
if __name__ == '__main__':
def increment():
var.set(var.get() + 1)
label.after(1, increment)
window = tk.Tk()
var = tk.IntVar(0)
label = tk.Label(window, textvariable=var)
label.pack()
increment()
window.mainloop()

Tkinter interactive and dynamic GUI

I am currently coding a little Python script for a colleague of mine which should give the framework for his PhD defense challenge, e.g. little tasks and questions he has to answer. The script itself should guide him through his challenges and give those tasks, introductions, etc.
I am currently using Tkinter for that purpose. Principally, I want to have a canvas/part of the screen, where text and introductions pop up like in the example shown below.
import Tkinter as tk
import time
global_delay =150
counter = 0
delay = global_delay
def display_text():
global num_letters
global global_text
global label
text = global_text[counter]
num_letters = len(text) - 1
old_text = label.cget("text")+'\n'
def display():
global num_letters
global counter
global global_delay
global delay
if delay == 0:
user_text = ''
com_text = ' '
else:
user_text = 'user#hlrdbb4 ~ '
com_text = ''
print_text = old_text + user_text + str(text[1:len(text) - num_letters])+(num_letters+50-len(text))*' ' + com_text
label.config(text=print_text)
label.config(font=("Courier", 30))
num_letters-=1
if num_letters>=0:
label.after(delay, display)
elif counter<len(global_text)-1:
counter += 1
if global_text[counter][0] == 'o':
delay = 0
if global_text[counter][0] == 'i':
delay = global_delay
label.after(global_delay*10, display_text)
display()
root = tk.Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry("%dx%d+0+0" % (w, h))
root.title("PhD")
label = tk.Label(root, fg="green", bg='black', height=h, width=w, anchor=tk.NW)
label.pack()
global_text=['icd E:\dbb\ ','oChange directory to E:\dbb\ ','iget_PhD.exe','oError file not found','iget_PhD.exe','oError file not found','iget_PhD.exe','oExecuting get_PhD.exe','oHere are your introductions...']
display_text()
root.mainloop()
The other side of the GUI should display the corresponding minigames, e.g. a dynamically changing noise plot for which he has to put in some numbers to see a decent signal. As you can see I am currently using the after-method at the moment to display the text, but I can't figure out, how to incorporate such games or how the script could wait for his (keyboard) input to continue.
Could anyone help me here a bit?
You could bind a keyboard input (in this case enter) or a tkinter button, to use the input of an Entry widget. You execute a function with it (in this case callback). You continue your program if you get the desired input.
import tkinter as tk
root = tk.Tk()
e = tk.Entry(root)
e.pack()
def callback(*args):
print (e.get())
e.bind("<Return>",callback)
root.mainloop()
Some good reading material and extra explanation:
Tkinter Confirmation buttons and game GUI (splice(?))

Close tkinter window after the script execution is complete

I am working an a tool where-in I am getting the initial user credentials and an unique identifier via Tkinter GUI interface. Post that after a lot of data fetching and processing I would get a report into an excel sheet using xlsxwriter package.
I generally exit/close the tkinter window using destroy() method on click of a button. Here, I want to show the user the status of the report creation in a Tkinter messagebox and then close the main window.
Note: I am using .pyw extension, so that the end user who is using the tool shouldn't see the console. So once the user hits the submit button, I will show a label at the footer of the window saying "Processing ..."
Sample code:
from tkinter import *
#Some other libraries are imported
mScrn = Tk()
mScrn.title("Report Generation Tool v1.0")
mScrn.geometry("200x180")
mScrn.resizable(False, False)
tk_uid_lbl = Label(mScrn, text="MVS1 Username")
tk_uid_lbl.pack()
tk_uid_lbl.place(x=20,y=20)
uid = StringVar()
tk_uid = Entry(mScrn, bd=3, textvariable=uid)
tk_uid.pack()
tk_uid.place(x=150, y=20)
tk_pwd_lbl = Label(mScrn, text="MVS1 Password")
tk_pwd_lbl.pack()
tk_pwd_lbl.place(x=20,y=60)
pwd = StringVar()
tk_pwd = Entry(mScrn, bd=3, show='*', textvariable=pwd)
tk_pwd.pack()
tk_pwd.place(x=150, y=60)
tk_ver_lbl = Label(mScrn, text="Version #")
tk_ver_lbl.pack()
tk_ver_lbl.place(x=20,y=100)
ver = StringVar()
tk_ver=Entry(mScrn, bd=3, textvariable=ver)
tk_ver.pack()
tk_ver.place(x=150, y=100)
tk_sub_button = Button(text='Submit', command = show_footer)
tk_sub_button.pack()
tk_sub_button.place(x=150, y=150)
mScrn.mainloop()
#The data provided in the GUI is used for access and a lot of process goes on
#Close the Tkinter window post the process is done
Thanks in Advance. I am using Python3
I am having a hard time understanding your question. My understanding is that using destroy() is exactly what you are looking for. Use destroy() when you are finished. You basically already answered your own question. I would find it helpful if you could explain your question more thoroughly. I agree with Goyo but I cannot comment.
I don't know how to get the data before closing the mainloop(). In
that aspect once that is closed I cannot show the label on the GUI and
then close with user consent (i.e. after clicking 'ok' in message box)
I don't undestand where is your problem, you can save your data with a lot ways, list, module, object, file, etc.
import tkinter as tk
import random
import threading
import time
# Simulate a process
def get_data(callback):
while True:
if len(data) == 10:
break
time.sleep(.5)
data.append(random.randint(1, 200))
callback()
def wait_end(label, tk_var_end, num=0):
label["text"] = "Processing " + " ." * num
num += 1
if num == 4:
num = 0
if not tk_var_end.get():
mScrn.after(500, wait_end, label, tk_var_end, num)
def execute():
for entry in (tk_uid, tk_pwd, tk_ver):
entry['state'] = tk.DISABLED
tk_sub_button.destroy()
tk_process_lbl = tk.Label(mScrn)
tk_process_lbl.pack()
tk_process_lbl.place(x=150,y=150)
tk_var_end = tk.BooleanVar(False)
wait_end(tk_process_lbl, tk_var_end)
process = threading.Thread(
target=get_data,
kwargs=(dict(callback=lambda: tk_var_end.set(True)))
)
process.start()
mScrn.wait_variable(tk_var_end)
mScrn.after(500, tk_process_lbl.config, dict(text='Process completed'))
mScrn.after(1500, mScrn.quit)
mScrn = tk.Tk()
data = []
mScrn.title("Report Generation Tool v1.0")
mScrn.geometry("400x180")
mScrn.resizable(False, False)
tk_uid_lbl = tk.Label(mScrn, text="MVS1 Username")
tk_uid_lbl.pack()
tk_uid_lbl.place(x=20,y=20)
uid = tk.StringVar()
tk_uid = tk.Entry(mScrn, bd=3, textvariable=uid)
tk_uid.pack()
tk_uid.place(x=150, y=20)
tk_pwd_lbl = tk.Label(mScrn, text="MVS1 Password")
tk_pwd_lbl.pack()
tk_pwd_lbl.place(x=20,y=60)
pwd = tk.StringVar()
tk_pwd = tk.Entry(mScrn, bd=3, show='*', textvariable=pwd)
tk_pwd.pack()
tk_pwd.place(x=150, y=60)
tk_ver_lbl = tk.Label(mScrn, text="Version #")
tk_ver_lbl.pack()
tk_ver_lbl.place(x=20,y=100)
ver = tk.StringVar()
tk_ver= tk.Entry(mScrn, bd=3, textvariable=ver)
tk_ver.pack()
tk_ver.place(x=150, y=100)
tk_sub_button = tk.Button(text='Submit', command = execute)
tk_sub_button.pack()
tk_sub_button.place(x=150, y=150)
mScrn.mainloop()
print(data)
But, you can also make your own class which will inherit of Tk, in this class you could override the quit or destroy method of Tk.

Python 3: Can't get serial communication and a Tkinter button to work at the same time

I am new to python. I am attempting to get serial input from an Arduino board and receive it in Python 3 on a Raspberry Pi 3. In the code below, I get the code just fine from the Arduino and I can display it using Tkinter. The thing is, depending on the code I get from the Arduino, I want to display different screens. For this, I have added a Tkinter button. This button should just call the NextButton subroutine and increment the DisplayScreen value. It should then recall the ShowDisplay routine and give me the next screen. The button does display on the screen, but clicking it does nothing.
Any help would be greatly appreciated. Thanks
import serial
from tkinter import *
v=" "
DisplayScreen =1
# Make Serial Connection
ser = serial.Serial('/dev/ttyACM0', 9600)
#Subroutine to increment display value
def NextButton():
DisplayScreen = DisplayScreen +1
Print ("Got Here")
if DisplayScreen == 3:
DisplayScreen = 1
# Update Display
ShowDisplay()
#Subroutine to show display
def ShowDisplay():
# Make values available from other parts of the program.
global v
if DisplayScreen == 1:
# Get rid of existing display entities
for widget in frame.winfo_children():
widget.destroy()
#Add Label
Label(frame, text="Display Screen 1").grid(row=0, column=0)
Label(frame, text=v).grid(row=1, column=0)
# Add button to go to next screen
Button(frame, text='Next', command=NextButton).grid(row=3, column=1)
else:
# Get rid of existing display entities
for widget in frame.winfo_children():
widget.destroy()
#Add Label
Label(frame, text="Display Screen 2").grid(row=0, column=0)
Label(frame, text=v).grid(row=1, column=0)
# Add button to go to next screen
Button(frame, text='Next', command=NextButton).grid(row=3, column=1)
def update_label():
global v
# get arduino info
PinballData= ser.readline()
# convert data to text
v = str(PinballData, 'utf-8')
# show display
ShowDisplay()
root.after(10, update_label)
#calls update_label function again after 1 second. (1000 milliseconds.)
root = Tk()
frame = Frame(root)
frame.grid()
update_label()
root.mainloop()
There are several things that need fixing:
You should declare 'DisplayScreen' globally inside NextButton function.
Print statement is wrong. It must be print()
Thus, the function NextButton has to look like:
def NextButton():
global DisplayScreen
DisplayScreen = DisplayScreen +1
print("Got Here")
if DisplayScreen == 3:
DisplayScreen = 1
# Update Display
ShowDisplay()
By doing these changes, I have been able to display "Got Here" by clicking on the button. However, it is really complicated to click on it since you are updating the entire graphical interface each 10 ms.
I would strongly recommend to update only the label instead of the entire root. Even better, you can change the text of a widget at any time by associating a StringVar():
v = StringVar()
Label(master, textvariable=v).pack()
In this way, you would have a much more stable graphical interface.
I would also recommend you to make use of classes since you have several variables shared between functions. Then, you could easily use self.

Multiple windows open at once in python

I've searched and found a few things on parent windows in python but that is not what I was looking for. I am trying make a simple program that opens a window and another window after that when the previous one is closed. I was also trying to implement some kind of loop or sleep time to destroy the window by default if the user does not. This is what I have (I'm new please don't laugh)
from tkinter import *
import time
root = Tk()
i = 0
if i < 1:
root.title("title")
logo = PhotoImage(file="burger.gif")
w1 = Label(root, image=logo).pack()
time.sleep(3)
root.destroy()
i = i + 1
if i == 1:
root.title("title")
photoTwo = PhotoImage(file="freedom.gif")
labelTwo = Label(root, image=photoTwo).pack()
time.sleep(3)
root.destroy()
i = i + 1
mainloop.()
Perhaps you're looking for something like this:
from tkinter import *
import time
def openNewWindow():
firstWindow.destroy()
secondWindow = Tk()
secondWindow.title("Second Window")
photoTwo = PhotoImage(file="freedom.gif")
labelTwo = Label(secondWindow, image=photoTwo).pack()
secondWindow.mainloop()
firstWindow = Tk()
firstWindow.title("First Window")
logo = PhotoImage(file="burger.gif")
w1 = Label(firstWindow, image=logo).pack()
closeBttn = Button(firstWindow, text="Close!", command=openNewWindow)
closeBttn.pack()
firstWindow.mainloop()
This creates a button in the first window, which the user clicks. This then calls the openNewWindow function, which destroys that window, and opens the second window. I'm not sure there's a way to do this using the window exit button.
To get create a more sustainable window creation, use this:
from tkinter import *
import time
def openThirdWindow(previouswindow):
previouswindow.destroy()
thirdWindow = Tk()
thirdWindow.title("Third Window")
photoTwo = PhotoImage(file="freedom.gif")
labelTwo = Label(thirdWindow, image=photoTwo).pack()
thirdWindow.mainloop()
def openSecondWindow(previouswindow):
previouswindow.destroy()
secondWindow = Tk()
secondWindow.title("Second Window")
photoTwo = PhotoImage(file="freedom.gif")
labelTwo = Label(secondWindow, image=photoTwo).pack()
closeBttn = Button(secondWindow, text="Close!", command= lambda: openThirdWindow(secondWindow))
closeBttn.pack()
secondWindow.mainloop()
def openFirstWindow():
firstWindow = Tk()
firstWindow.title("First Window")
logo = PhotoImage(file="burger.gif")
w1 = Label(firstWindow, image=logo).pack()
closeBttn = Button(firstWindow, text="Close!", command= lambda: openSecondWindow(firstWindow))
closeBttn.pack()
firstWindow.mainloop()
openFirstWindow()
This places the opening of each window in a seperate function, and passes the name of the window through the button presses into the next function. Another method would be setting the window names as global, but this is messy.
The function "lambda:" calls the function, in tkinter you must type this if you want to pass something through a command.
We initiate the whole process first first called "openFirstWindow()"

Categories