Tkinter interactive and dynamic GUI - python

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

Related

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 window freezes after some time

I am new to Tkinter. I was experimenting with this, but while the Tkinter starts just fine, it some times freezes after a while for no reason. Actually, the weird thing that happens, is that while the Tkinter windows do not update the information anymore, the counter keeps printing ON MY WINDOWS DESKTOP LEFT TOP SIDE! (just the black bg box with the white font that shows the counter number) and eventually finds number 777 and ends. But long freezes in between, even without any printing (not on my Windows desktop either)
this is the normal tkinter window
this is the console output
all good here
crazy Windows display here
The simple program here is generating random ints from 1 - 10000 and should 1) if 777 is found it should print the counter and exit 2) if any number between 1-100 is found, it should print the number and counter.
I am trying to learn how to print new content in the same line, and if something happens print more info on another line, while the first (or more) keep updating.
I'm not sure if I exit the program the right way or how exactly I should be using/calling the Tkinter function to update the info on the screen.
Any help or tips most appreciated.
from tkinter import *
import random
import time
tk=Tk()
i = 0
counter = 0
tk.title("Testing...")
tk.geometry('300x200')
canvas_width = 200
canvas_height = 100
canvas = Canvas(tk, width=canvas_width, height=canvas_height, bg='white')
canvas.pack(expand=YES)
def screen(Found):
#canvas.create_text(20, 20, text="Counter:" + str(counter), font="Times 20 italic")
widget = Label(canvas, text="Counter:" + str(counter), fg='white', bg='black')
widget.grid(column=0, row=0)
if Found:
widget = Label(canvas, text="Number " + str(i) +" was found at counter:" + str(counter), fg='white', bg='black')
widget.grid(column=0, row=1)
found = False
loop = True
while loop:
counter += 1
i = random.randint(1, 10000)
if i in range(1, 100):
print("Found i = ", i, " in counter:", counter)
Found = True
else:
Found = False
if i == 777:
loop = False
print("JUST FOUND 777")
print("Counter:", counter)
screen(Found)
tk.update_idletasks()
tk.update()
# tk.mainloop()
As a matter of design, it is usually a better approach to rely on the tkinter mainloop to drive the application. root.after is a great substitute to a while loop. Labels do not need to be recreated; they can use special variables that update the displayed values themselves, and their properties can be reconfigured. Calling update, and update_idletasks is hardly ever needed.
The following is maybe a better approach where one single label is updated from a loop powered by the tkinter mainloop:
(it shows you "how to keep displaying new stuff, without such label issues")
import tkinter as tk
import random
def screen(found, v, c):
if found:
label.configure(bg='red', fg='blue')
print('found')
else:
label.configure(bg='black', fg='white')
update_lbl_txt(v, c)
def update_lbl_txt(v=0, c=0):
txt = f'Number at {v} was found at counter {c}'
lbl_txt.set(txt)
def found_target(t):
return t in range(1, 100) or t == 777
def loop(c=0):
v = random.randint(1, 10000)
screen(found_target(v), v, c)
root.after(100, loop, c+1)
root = tk.Tk()
root.title("Testing...")
root.geometry('300x200')
lbl_txt = tk.StringVar(root)
update_lbl_txt()
label = tk.Label(root, textvariable=lbl_txt, fg='white', bg='black')
label.pack(expand=True, fill=tk.BOTH)
loop()
root.mainloop()
The reason it seems to freeze it at least partly because you have an infinite loop that you never, ever break out of. Most of the time i will not be in the range of 1 to 100, nor will it be 777, but your loop will keep on running and never update the screen.
You're also creating hundreds or thousands of label widgets every second, stacked on top of one another. You will run into performance issues rather quickly.

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 window is still not appearing

I'm trying to create a simple tkinter GUI, and have read up online to add a .mainloop() to the end of my code to make the window appear, It is still not appearing.
There is no error message and simply
Process finished with exit code 0
I have attached my code.
Any help is greatly appreciated
def window():
global FPS
global maxFPS
root = Tk()
root.title('Camera Tracker')
root.geometry('500x300')
def quitfunc():
quitm=Tk()
quitm.title('Quit')
quitm.geometry('200x100')
yesbutton=Button(quitm,text='Yes',command=quit)
nobutton =Button(quitm,text='No',command=quitm.destroy)
yesbutton.place(x=50,y=60)
nobutton.place(x=130,y=60)
reassure = Label(quitm,text='Are you sure you want to quit?')
reassure.place(x=17,y=20)
quitm.mainloop()
sbview = Label(root, text=FPS)
sbview.place(y=50, x=50)
def FPScallback(self):
global FPS
FPS = round(sb.get())
if 10 > FPS < 18 or 29 < FPS:
sbview.config(fg='orange')
elif FPS < 10:
sbview.config(fg='red')
else:
sbview.config(fg='green')
sbview.config(text=FPS)
quitbutton = Button(root,command=quitfunc,text='Quit')
quitbutton.pack()
sb = ttk.Scale(root, from_=0, to=maxFPS, command=FPScallback, orient=HORIZONTAL)
sb.place(y=100, x=100)
sb.set(FPS)
root.mainloop()
Thanks A Bunch In Advance
If you create root from inside a function it will not be available when the function exits. In my example I'm creating root as well as some global variables in the global scope.
To ask the user if he wants to exit it's easier to use the standard library messagebox. If you want to catch any attempt to close the application (such as ALT-F4) you should reasearch root.protocol("WM_DELETE_WINDOW", do_exit) which runs the do_exit function when the app wants to exit by any means.
from tkinter import *
from tkinter import ttk
from tkinter import messagebox # Quit dialog
root = Tk() # Create root in the global scope
root.title('Camera Tracker') # so the functions can find it
root.geometry('500x300')
maxFPS = 50 # Initiate variables in the global scope
FPS = 25
sbview = Label(root, text=FPS)
sbview.place(y=50, x=50)
def quitfunc():
result = messagebox.askyesno('Quit', 'Are you sure you want to quit?')
if result:
root.destroy()
def FPScallback(self):
global FPS
FPS = round(sb.get())
if 10 > FPS < 18 or 29 < FPS:
sbview.config(fg='orange')
elif FPS < 10:
sbview.config(fg='red')
else:
sbview.config(fg='green')
sbview.config(text=FPS)
sb = ttk.Scale(root, from_=0, to=maxFPS, command=FPScallback, orient=HORIZONTAL)
sb.place(y=100, x=100)
sb.set(FPS)
quitbutton = Button(root, command=quitfunc, text='Quit')
quitbutton.pack()
root.mainloop()

how to make Tkinter windows appear when opened rather than start minimized?

How can I open Tkinter windows (such as entry, text...) and make them appear on the screen when they are opened rather than start minimized ?
I don't really know how to start... I have some windows but they are opened minimized. I searched on the internet, but found nothing that may be relevant. how can I do it ?
using python on windows (both Python 3 and Python 2)
thanks for the help in advance !
EDIT: the problem now as I mentioned in a comment here is that I have to force the window to be showed. But when I do so, the window does not start centered even if I use a function to center it that worked before.
code:
def center(toplevel):
toplevel.update_idletasks()
w = toplevel.winfo_screenwidth()
h = toplevel.winfo_screenheight()
size = tuple(int(_) for _ in toplevel.geometry().split('+')[0].split('x'))
x = w/2 - size[0]/2
y = h/2 - size[1]/2
toplevel.geometry("%dx%d+%d+%d" % (size + (x, y)))
def paste_func():
global text_box
text_box.insert(END, top.clipboard_get())
button_pressed()
def button_pressed(x=0):
# This function determines which button was pressed, and closes this menu/message box/etc...
global pressed
pressed = x
destroy_top()
def destroy_top():
# This function closes this menu/message box/etc...
global top
top.iconify()
top.withdraw()
top.quit()
def get_text():
global pressed
global top
global text_box
pressed = 0
top = Tk()
top.withdraw()
top.rowconfigure(0, weight=0)
top.columnconfigure(0, weight=0)
top.config(height=0, width=0)
top.protocol('WM_DELETE_WINDOW', lambda: button_pressed(-1))
text_box = Entry(top, width=50)
text_box.focus_set()
text_box.grid(row=0, column=0)
but = Button(top, text='Enter', command=button_pressed)
but.grid(row=0, column=1)
paste = Button(top, text='Paste', command=paste_func)
paste.grid(row=0, column=2)
top.deiconify()
text_box.focus_set()
top.after(0, top.focus_force())
center(top)
top.mainloop()
if pressed == -1:
exit()
return text_box.get('1.0', index2=END)
The window.focus_force() method does this:
Force the input focus to the widget. This is impolite. It's better to wait for the window manager to give you the focus. See also .grab_set_global() below.
Sometimes if this doesn't work, you can manually force it like so:
from Tkinter import *
window = Tk()
window.after(2000, window.focus_force)
window.mainloop()
Sometimes you will have issues on Macs which can require some additional finagling but this should work fine elsewhere (OP has not specified any information about environment).

Categories