Tkinter: displaying label with automatically updated variable - python-3.x

I know there are a lot of questions about this subject, but after long researches I didn't find any that could solve my problem.
I'm trying to display with a label (using tkinter) a variable that I get from the I²C bus. The variable is therefore updated very regularly and automatically. The rest of the window should stay available for the user.
For now, the only way I found to display the label with the updated variable and to keep the rest of the window available for the user is to do so:
window = tk.Tk()
window.title("Gestionnaire de périphériques")
window.minsize(1024,600)
labelValThermo = tk.Label(a_frame_in_the_main_window,text = "")
labelValThermo.grid(row = 1, column = 1)
while True:
if mcp.get_hot_junction_temperature() != 16.0625:
labelValThermo.configure(text = "Température thermocouple: {} °C".format(mcp.get_hot_junction_temperature()))
window.update()
time.sleep(0.75)
The variable that comes from the I²C and got updated is mcp.get_hot_junction_temperature
The fact is that I know it's not the best way to force the update in an infinite loop. This should be the role of the mainloop(). I found out that the after() method could solve my problem but I don't know how to run it. I tried the following code that didn't work:
def displayThermoTemp():
if mcp.get_hot_junction_temperature() != 16.0625:
labelValThermo.configure(text = "Température thermocouple: {} °C".format(mcp.get_hot_junction_temperature()))
labelValThermo.after(500,displayThermoTemp)
window = tk.Tk()
labelValThermo = tk.Label(thermoGraphFrame,text = "")
labelValThermo.after(500, displayThermoTemp)
labelValThermo.grid(row = 1, column = 1)
window.mainloop()
Does anyone have the right syntax ?

How To use after() ?
The after() calls the function callback after the given delay in ms. Just define it inside the given function and it'll run just like a while loop till you call after_cancel(id) .
Here is an example:
import tkinter as tk
Count = 1
root = tk.Tk()
label = tk.Label(root, text=Count, font = ('', 30))
label.pack()
def update():
global Count
Count += 1
label['text'] = Count
root.after(100, update)
update()
root.mainloop()
Update your function with this and call it once before mainloop().
def displayThermoTemp():
if mcp.get_hot_junction_temperature() != 16.0625:
labelValThermo.configure(text = "Température thermocouple: {} °C".format(mcp.get_hot_junction_temperature()))
labelValThermo.after(500,displayThermoTemp)
# 100ms = 0.1 secs
window(100, displayThermoTemp)

after has the following syntax:
after(delay_ms, callback=None, *args)
You need to place the command in your callback function and update the window on which the widget is (in your case the labelValThermo looks to be on the root window:
def displayThermoTemp():
if mcp.get_hot_junction_temperature() != 16.0625:
labelValThermo.configure(text = "Température thermocouple: {} °C".format(mcp.get_hot_junction_temperature()))
root.after(500,displayThermoTemp)

Related

Invoke tkinter trace callback AFTER a certain variable delay has been achieved

I have a tkinter application that searches through a list of about 100000 wordlist when user types into the Entry widget (using trace with write callback to capture change in Entry variable).
I want to implement sort of a delay in order to NOT invoke the trace callback (to search the entire 100k wordlist) at EVERY keystroke (as the user might still be typing and it can become rather jerky/slow to invoke the callback function for each keystroke), rather I want to employ some sort of a min time to wait for additional input/keystroke AND/OR a max time since the first key was pressed BEFORE invoking the trace callback function.
I tried implementing a sleep but that is just a blocking call and does not achieve the desired affect. Here is some sample code where entering the string 'password' will invoke the callback (since this is literally just checking against the string 'password', it is super fast, yet in my app I loop over 100k word list for each keystroke which becomes slow). Thank You!
import tkinter as tk
from tkinter import ttk
class App(tk.Tk):
SUCCESS = 'Success.TLabel'
def __init__(self):
super().__init__()
self.title('Enter <password>')
self.geometry("200x120")
self.passwordVariable = tk.StringVar()
self.passwordVariable.trace('w', self.validate)
password_entry = ttk.Entry(
self, textvariable=self.passwordVariable) #, show='*'
password_entry.grid(column=0, row=1)
password_entry.focus()
self.message_label = ttk.Label(self)
self.message_label.grid(column=0, row=0)
def set_message(self, message, type=None):
self.message_label['text'] = message
if type:
self.message_label['style'] = type
def validate(self, *args):
confirm_password = self.passwordVariable.get()
if confirm_password == "password":
self.set_message(
"Success: The new password looks good!", self.SUCCESS)
return
if confirm_password.startswith("pas"):
self.set_message('Warning: Keep entering the password')
if __name__ == "__main__":
app = App()
app.mainloop()
I tried to understand what your current code does so I can implement the function here, but I've had no luck. Hopefully you being the author can implement this example onto your code.
The idea here is to schedule a callback to run after x seconds and if it is already scheduled, then cancel it. Sort of like a timer, if you think about it.
from tkinter import *
root = Tk()
SECONDS_TO_WAIT = 1
rep = None
def typing(*args):
global rep
if rep is None:
writing.config(text='Typing...')
else:
root.after_cancel(rep) # if already scheduled, then cancel it
rep = root.after(SECONDS_TO_WAIT*1000, caller)
def caller(*args):
global rep
writing.config(text='Not typing')
rep = None # Set it to None if `caller` GETS executed
var = StringVar()
entry = Entry(root,textvariable=var)
entry.pack(padx=10,pady=10)
entry.focus_force()
writing = Label(root,text='Not typing')
writing.pack()
var.trace('w',typing)
root.mainloop()
This will execute typing each time the entry widget is edited/written to. And according to the conditions inside the function, caller gets executed.

Mapping Tkinter entry box to variable in python 3.8

I am a complete beginner in python,I was hoping someone could help me figure out what I am trying to accomplish.
I built a small tkinter front end that will generate a string multiple times. I want the amount of times that the string is generated to be based off of an entry box. I have hit a wall.
Here is my Front End (help_FE.py)
from tkinter import *
import help_BE
def view_formula():
text1.delete('1.0',END)
text1.insert(END,help_BE.End_result)
pass
window = Tk()
window.wm_title=("Print:")
l1=Label(window,text = "Page to print:")
l1.grid(row=3, column=2)
e1 = Entry(window)
e1.grid(row=3, column=3)
text1=Text(window, height=20,width=35)
text1.grid(row=4,column=3, rowspan=10, columnspan=7, padx=5, pady=10)
sb1=Scrollbar(window)
sb1.grid(row=4,column=11,rowspan=10)
text1.configure(yscrollcommand=sb1.set)
sb1.configure(command=text1.yview)
text1.bind('<<TextboxSelect>>')
b1=Button(window, text = "Generate", width =10, command=view_formula)
b1.grid(row=3,column=6)
window.mainloop()
and my backend (help_BE.py)
currently, the generate button will print "Testing" 3 times, because i have set pages = 3 in the backend, but I want to be able to set pages to whatever is entered into the frontend entry box.
pages = 3
result=[]
def foo():
skip_zero = pages + 1
for x in range (skip_zero):
if x==0:
continue
result.append("Testing"+str(x))
listToStr = ''.join([str(element) for element in result])
full_formula = (listToStr)
return full_formula
End_result = foo()
The data inputted in the field can be accessed using the function Entry.get(), and you can convert the string to a number with the int function.
In order to get it to the backend, and in order to keep your value up to date, I would make sure that, as jasonharper mentioned, you call foo each time the button is pressed, passing the entry's value in as the argument pages. This means tweaking your code as such:
help_BE.py
def foo(pages):
skip_zero = pages + 1
for x in range (skip_zero):
if x==0:
continue
result.append("Testing"+str(x))
listToStr = ''.join([str(element) for element in result])
full_formula = (listToStr)
return full_formula
help_FE.py
def view_formula():
text1.delete('1.0',END)
text1.insert(END,help_BE.foo(int(e1.get())))

Why is it saying a isnt defined even though i have defined you

so i defined a but when i try to type out a using keybaord.type it just says that it isnt defined
i tried making a global that didnt work i tried moving postions of the code and that didnt work ive tried many other things they didnt work either
from tkinter import *
import webbrowser
from pynput.keyboard import Key, Controller
import time
menu = Tk()
menu.geometry('200x300')
def webop(): # new window definition
global a
def hh():
a = "" + txt.get()
while True:
keyboard = Controller()
time.sleep(1)
keyboard.type(a)
keyboard.press(Key.enter)
keyboard.release(Key.enter)
sp = Toplevel(menu)
sp.title("Spammer")
txt = Entry(sp, width=10)
txt.grid(row=1,column=1)
btn = Button(sp, text='spam', command=hh)
btn.grid(row=1,column=2)
def enc():
window = Toplevel(menu)
window.title("nou")
button1 =Button(menu, text ="Spammer", command =webop) #command linked
button2 = Button(menu, text="Fake bot", command = enc)
button1.grid(row=1,column=2)
button2.grid(row=2,column=2)
menu.mainloop()
global a underneath def webop() gives webop access to the variable a in the enclosing scope (the scope up there where you're doing your imports). Since you haven't defined a in that scope, you're getting the error.
Either way, you generally should avoid using globals like that and pass data to functions using arguments. In order to pass arguments into your Button command you can use a closure.
You should move the part of your code accessing a to the part where that value is set
It is unclear what you're trying to achieve here since when you run webop your program will reach while True and continuously loop there and never reach the code below your while loop
For instance
def hh(a):
a = "" + txt.get()
while True:
keyboard = Controller()
time.sleep(1)
keyboard.type(a)
keyboard.press(Key.enter)
keyboard.release(Key.enter)
btn = Button(sp, text='spam', command=hh)
An alternative approach achieves the same thing using functools partial. See https://www.delftstack.com/howto/python-tkinter/how-to-pass-arguments-to-tkinter-button-command/

tkinker optionmenu not showing chosen result

import tkinter
window = tkinter.Tk()
def abc(event):
ans=0
numberss=['7','8','9']
omenu2['menu'].delete(0, 'end')
for number in numberss:
omenu2['menu'].add_command(label=numberss[ans], command=efg)
ans=ans+1
def efg(event=None):
print('yee')
numbers = ['1','2', '3']
number=['4','5','6']
var = tkinter.StringVar(window)
var1 = tkinter.StringVar(window)
omenu = tkinter.OptionMenu(window, var, *numbers, command = abc)
omenu.grid(row=1)
omenu2 = tkinter.OptionMenu(window, var1, *number, command = efg)
omenu2.grid(row=2)
after you have entered the first option menu, it will update the second one. when you enter data into the second one, it runs the command, but doesn't show you what you entered. i do not want to include a button, and i know that the command works and not on the second
i found some code that changed the options of the second menu, however when i ran this, the command wouldn't work as it was changed to tkinter.setit (i would also like to know what is does. i do not currently understand it)
omenu2['menu'].add_command(label=numberss[ans], command=tkinter._setit(var1, number))
this has been taken from a larger piece of code, and has thrown the same error
You should set your StringVar(var1) new value.
def abc(event):
numberss=['7','8','9']
omenu2['menu'].delete(0, 'end')
for number in numberss:
omenu2['menu'].add_command(label=number, command=lambda val=number: efg(val))
def efg(val, event=None):
print('yee')
var1.set(val)
You are using for loop so you don't need ans(at least not in this code) since it iterates over items themselves.

Label keeps on appearing

SO I am using Python 3.4 and tkinter.
And when I call a function again n again which contains a label, the label keeps on appearing in window but previous label doesn't go away?
How can I remove any printed label from GUI window as soon as function is called and then display new one?
Here is the code:-
#def prestart():
#here I check if number of match is okay, if not, user is redirected to setting else, I call start()
def start():
#CPU Choice
cpu_choice = Label(historyframe, text = "CPU Choosed: {}".format(dict['cpu_choice']))
#Played Match
#played_num_of_match = Label(scoreframe, text = "Number of Matches Played: {}".format(int(dict['match_played'])))
#Display Status
status_disp = Label(scoreframe, text = "Current Status: {}".format(dict['status']))
if(int(dict['match_played']) < int(dict['num_of_match'])):
playframe.grid(row = 1, column = 0)
historyframe.grid(row = 2, column = 1)
status_disp.pack(fill=X)
elif(int(dict['match_played']) == int(dict['num_of_match'])):
playframe.grid(row = 1, column = 0)
historyframe.grid(row = 2, column = 1)
status_disp.pack(fill=X)
cp = dict['cpu_point']
up = dict['user_point']
result(cp, up)
cpu_choice.pack(fill = X)
scoreframe.grid(row = 2, column = 0)
This function just updates the display!
def send_value(x):
#Here I run logic of game and change value of key in dictionary and call start() at end of change.
Now, the choice buttons are not in any definition as they don't need to be called again n again. I just make playframe disappear n appear!
Here is the code for them:-
#Display Question
question = Label(playframe, text = "Rock? Paper? Scissor?")
#Rock
rock = Button(playframe, text = "Rock!", command = lambda: send_value("ROCK"))
#Paper
paper = Button(playframe, text = "Paper!", command = lambda: send_value("PAPER"))
#Scissor
scissor = Button(playframe, text = "Scissor!", command = lambda: send_value("SCISSOR"))
So when user clicks Rock/Paper/Scissor, I just change key value in dictionary! But if I keep the label outside function, it doesn't get auto updated!
Everything else is working perfectly. I'll kind of now start to make code cleaner.
Try something like this instead of creating a new label every time:
import Tkinter as tk
class Window():
def __init__(self, root):
self.frame = tk.Frame(root)
self.frame.pack()
self.i = 0
self.labelVar = tk.StringVar()
self.labelVar.set("This is the first text: %d" %self.i)
self.label = tk.Label(self.frame, text = self.labelVar.get(), textvariable = self.labelVar)
self.label.pack(side = tk.LEFT)
self.button = tk.Button(self.frame, text = "Update", command = self.updateLabel)
self.button.pack(side = tk.RIGHT)
def updateLabel(self):
self.i += 1
self.labelVar.set("This is new text: %d" %self.i)
root = tk.Tk()
window = Window(root)
root.mainloop()
Important points:
1) A class is used, as it is much easier to pass values around when all Tkinter objects and variables are member variables, accessible from all of your GUI functions.
2) updateLabel does not create a new Label. It simply updates the StringVar() object to hold new text every time you call the function. This is accomplished with the textvariable = self.labelVar keyword when creating my Label widget.
PS: This is done in Python 2.5 so for this code to work for you, change Tkinter to tkinter
EDIT 06/19/2015:
If you want to implement something similar to what I have with your code, without using a class, you'll need to pass around references to your variables.
1) Change start:
Your Labels cpu_choice, status_disp, etc. should be created outside of the function; likely in the same location as question, rock, paper, scissors, etc. You will also pack them outside of the function as well. Same with all the calls to .grid inside of start; you shouldn't need to call pack or grid more than once: right when you create the widget.
The following lines:
playframe.grid(row = 1, column = 0)
historyframe.grid(row = 2, column = 1)
status_disp.pack(fill=X)
Can be done outside of the function as well; you execute these 3 statements under both the if and the elif conditions. This means they aren't really conditional statements; they are done regardless of the validity of the condition.
2) Create a StringVar for both cpu_choice & status_disp & edit the Labels as follows (remember, outside of the function):
cpu_choice_text = StringVar()
cpu_choice_text.set("Set this to whatever is shown at the start of the game")
cpu_choice = Label(historyframe, text = cpu_choice_text.get(), textvariable = cpu_choice_text)
cpu_choice.pack(fill = X)
# And do this same thing for status_disp
3) When you call start, you will now pass it cpu_choice_text & status_disp_text (or whatever they are called). Instead of trying to change the text field of the Label frame, you may now use a set call on the StringVar which is connected to the Label & the Label will automatically update. Example:
def start(cpu_choice_text, status_disp_text):
cpu_choice.set(text = "CPU Choice: {}".format(dict['cpu_choice']))
...
Alternatively, wrap it all in a class and make it much easier for yourself by using self on every Tkinter variable & widget. In this way you won't need to pass variables to your functions, just access member variables directly as I have with self.i, self.labelVar in my example.
Each time you call start you create new labels and use grid to place them in the same spot as the old labels. The best solution is to only create the labels once, but if you insist on creating new labels each time start is called, you need to delete the old labels first.
You can use the destroy() method of a label to destroy it, though for that to work you must keep a global reference of the label.

Resources