Getting Pyphons Tkinter to update a label with a changing variable [duplicate] - python

This question already has answers here:
Making python/tkinter label widget update?
(5 answers)
Closed 7 years ago.
I have a python script which I have written for a Raspberry Pi project, the script reads a value from a micrometer every second, and stores this value as a TkInter StringVar (http://effbot.org/tkinterbook/variable.htm)
What I want to happen is that that string is displayed on the UI as a label, and updated on the UI when the value changes.
I chose to use a Tkinter stringvar instead of a standard string because I thought that Tkinter would then automatically update on the UI, but it doesn't seem to work.
Currently the script displays nothing for the label, and does not update when show sets virtual_reading.
From other threads I have read, the Tkinter method update_idletasks() should force the window to update, but I haven't been able to get this to work.
Can anyone give any guidance on where I'm going wrong?
This is my first go at Python so sorry if this is simple.
import datetime
import csv
from tkinter import *
from tkinter import messagebox
import time
import pigpio
import os
os.system("sudo pigpiod")
root = Tk()
winx = 480
winy = 320
CLOCK=21
DATA=20
g_level=0
g_reading=0
g_bits=0
pi = pigpio.pi()
virtual_reading = StringVar()
def go():
global cb1
global cb2
cb1 = pi.callback(DATA, pigpio.EITHER_EDGE, cbf)
cb2 = pi.callback(CLOCK, pigpio.EITHER_EDGE, cbf)
root.after(1000 ,go)
def show(bits, value):
inch = value & (1<<23)
minus = value & (1<<20)
value = value & 0xfffff
if inch:
reading = value / 2000.0
units = "in"
else:
reading = value / 100.0
units = "mm"
if minus:
sign = "-"
else:
sign = ""
global virtual_reading
virtual_reading = StringVar()
virtual_reading.set("{} {:.3f} {}".format(sign, reading, units))
print(virtual_reading.get())
cb2.cancel()
cb1.cancel()
def measure(event=None):
todays_date = datetime.date.today()
try:
get_tool_no = int(tool_no_entry.get())
if get_tool_no <= 0:
messagebox.showerror("Try Again","Please Enter A Number")
else:
with open("thickness records.csv", "a") as thicknessdb:
thicknessdbWriter = csv.writer(thicknessdb, dialect='excel', lineterminator='\r')
thicknessdbWriter.writerow([get_tool_no] + [todays_date] + [virtual_reading.get()])
thicknessdb.close()
except:
messagebox.showerror("Try Again","Please Enter A Number")
tool_no_entry.delete(0, END)
def cbf(g, l, t):
global g_level, g_reading, g_bits
if g == DATA:
if l == 0:
g_level = 1
else:
g_level = 0
elif g == CLOCK:
if l == pigpio.TIMEOUT:
if g_bits > 10:
show(g_bits, g_reading)
g_reading=0
g_bits=0
elif l == 0:
g_reading = g_reading | (g_level<<g_bits)
g_bits += 1
go()
record_button = Button(root,width = 30,
height = 8,
text='Measure',
fg='black',
bg="light grey", command = measure)
tool_no_entry = Entry(root)
reading_display = Label(root, font=("Helvetica", 22), text = virtual_reading.get())
reading_display.place(x = 50, y =80)
root.resizable(width=FALSE, height=FALSE)
root.geometry('%dx%d' % (winx,winy))
root.title("Micrometer Reader V1.0")
record_button.place(x = 340, y = 100, anchor = CENTER)
tool_no_entry.place(x = 120, y = 250, anchor=CENTER)
tool_no_entry.focus_set()
root.bind("<Return>", measure)
root.mainloop()
cb2.cancel()
cb1.cancel()
pi.stop()

You are misunderstanding how StringVars work. For one, you're recreating the StringVar every second, and only the original one is tied to the label. The one you create isn't associated with any widgets so it will never be visible.
The second problem is that you're associating the variable with the label incorrectly. You're doing this:
reading_display = Label(..., text = virtual_reading.get())
... when you should be doing it like this to get the auto-update feature:
reading_display = Label(..., textvariable = virtual_reading)
That being said, you don't need to use a StringVar at all. You can use it, but it's just an extra object you have to manage. You can directly set the displayed string with the configure method anytime you want
text = "{} {:.3f} {}".format(sign, reading, units)
reading_display.configure(text=text)
Note: you do not need to call update or update_idletasks

Related

Why exec function in python adds '.!' at the beginning of the text?

I am currently working on a hangman game using tkinter in python.
When I click a button of the letter and it is in the word that we are guessing it should show the letter. But when I click the button this problem is popping up:
This example is only with one button. People say that this problem is because of the mainloop(), but i have no idea how to fix it.
from tkinter import *
from tkinter import messagebox
from generate_word import word
#DEFAULT VALUES
score = 0
count = 0
win_count = 2
WINDOW_BG = '#e5404e'
WINDOW_SIZE = '1200x870+300+80'
FONT = ('Arial', 40)
from tkinter import *
from tkinter import messagebox
from generate_word import word
#DEFAULT VALUES
score = 0
count = 0
win_count = 2
WINDOW_BG = '#e5404e'
WINDOW_SIZE = '1200x870+300+80'
FONT = ('Arial', 40)
#this is an example with only one button
buttons = [['b1','a',80,740]]
#Creating window and configurating it
window = Tk()
window.geometry(WINDOW_SIZE)
window.title('Hangman')
window.config(bg = WINDOW_BG)
#generates all of the labels for the word
def gen_labels_word():
label = Label(window, text = " ", bg = WINDOW_BG, font = FONT)
label.pack( padx = 40,pady = (500,100),side = LEFT)
label1 = Label(window, text = word[0], bg = WINDOW_BG, font = FONT)
label1.pack( padx = 41,pady = (500,100),side = LEFT)
x = 21
for var in range(1,len(word)):
exec('label{}=Label(window,text="_",bg=WINDOW_BG,font=FONT)'.format(var))
exec('label{}.pack(padx = {}, pady = (500,100), side=LEFT)'.format(var,x))
x += 1
exec('label{} = Label(window, text = "{}", bg = WINDOW_BG, font = FONT)'.format(len(word),word[-1]))
exec('label{}.pack( padx = {},pady = (500,100),side = LEFT)'.format(len(word), x+1))
# ---------------------------------------------------------------------------------------------------------------------------------------------------
gen_labels_word()
#----------------------------------------------------------------------------------------------------------------------------------------------------
#letters icons(images)
#hangman (images)
hangman = ['h0','h1','h2','h3','h4','h5','h6']
for var in hangman:
exec(f'{var}=PhotoImage(file="{var}.png")')
han = [['label0','h0'],['label1','h1'],['label2','h2'],['label3','h3'],['label4','h4'],['label5','h5'],['label6','h6']]
for p1 in han:
exec('{}=Label(window, bg = WINDOW_BG ,image={})'.format(p1[0],p1[1]))
exec('label0.place(x = 620,y = 0)')
for var in letters:
exec(f'{var}=PhotoImage(file="{var}.png")')
for var in buttons:
exec(f'{var[0]}=Button(window,bd=0,command=lambda: game_brain("{var[0]}","{var[1]}"),bg = WINDOW_BG,font=FONT,image={var[1]})')
exec('{}.place(x={},y={})'.format(var[0],var[2],var[3]))
def game_brain(button, letter):
global count,win_count,score
exec('{}.destroy()'.format(button))
if letter in word:
for i in range(1,len(word)):
if word[i] == letter:
win_count += 1
exec(f'label{i}.config(text="{letter}")')
if win_count == len(word):
score += 1
messagebox.showinfo('GOOD JOB, YOU WON!\n GOODBYE!')
window.destroy()
else:
count += 1
exec('label{}.destroy()'.format(count-1))
exec('label{}.place(x={},y={})'.format(count,620,0))
if count == 6:
messagebox.showinfo('GAME OVER','YOU LOST!\nGOODBYE!')
window.destroy()
def EXIT():
answer = messagebox.askyesno('ALERT','Do you want to exit the game?')
if answer == True:
window.destroy()
e1 = PhotoImage(file = 'exit.png')
ex = Button(window,bd = 0,command = EXIT,bg = WINDOW_BG,font = FONT,image = e1)
ex.place(x=1050,y=20)
#-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
window.mainloop()
Why exec function in python adds '.!' at the beginning of the text?
The exec function isn't doing that. Tkinter by default names all of its widgets with a leading exclamation point. When you print out a widget, it has to be converted to a string. For tkinter widgets, the result of str(some_widget) is the widget's name.
You can see this quite easily without exec:
import tkinter as tk
root = tk.Tk()
label = tk.Label(root)
print(label)
The above will print something like .!label. If you create a second label it will be .!label2, a third will be .!label3 and so on.
On an unrelated note, you shouldn't be using exec to create widgets. It makes the code very hard to understand and debug. If you want to create widgets in a loop, add them to a dictionary or list instead of dynamically creating variables with exec.
For example:
labels = {}
for var in range(1,len(word)):
label = Label(window,text="_",bg=WINDOW_BG,font=FONT)
label.pack(padx=500, pady=100)
labels[var] = label
With that, you can later reference the widgets as labels[1], labels[2], etc.
You should do the same thing with the images, or anything else that you create in a loop and want to keep track of.

Text Game to Tkinter GUI [duplicate]

This question already has answers here:
Why is my Button's command executed immediately when I create the Button, and not when I click it? [duplicate]
(5 answers)
Closed 1 year ago.
I'm attempting to make a GUI for my text game (similar to a pokemom game)
In summary the code works like:
import random
hp = 100
for i in range (0,4):
element = input("Enter attack: ")
list.append(element)
attack1 = list[0]
attack2 = list[1]
attack3 = list[2]
attack4 = list[3]
while(True):
cmd = input(">")
If cmd.lower() == attack1 ... :
if cmd.lower() == bite:
hp -= 10
Now I tried doing a gui on Tkinter through buttons but it seems I can't get a grasp of it
from tkinter import *
#4 buttons
bite = 0
root = Tk()
def click(hp):
hp -= 10
myLabel = Label(text=f"{hp} hp remaining left.")
myLabel.grid(row = 5, column = 5)
def click2():
bite = 0
hp = 50
hp -= 20
myLabel = Label(text=f"{hp} hp remaining left.")
myLabel.grid(row = 5, column = 5)
bite += 1
myButton = Button(root, text=f"hello {bite}/5", command = click(hp))
myButton2 = Button(root, text=f"Presssss {bite}/5", command = click2)
myButton3 = Button(root, text="Presssss")
myButton4 = Button(root, text="Presssss")
myButton.grid(row = 0, column = 1)
myButton2.grid(row = 1, column = 1)
myButton3.grid(row = 1, column = 2)
myButton4.grid(row = 0, column = 2)
root.mainloop()
And this just presents a constant value "90, 30" due to the variables being included on the function, but whenever I'm trying to put it into an argument like
hp = 100
def click(hp):
hp -= 10
button1 = Button(root, text = "try", command = click(100))
It just returns the value 90, 30 way before the button is clicked. When hp is used as an arg, it is saying undefined.
It does so because click() function is called as soon as window is created.
To prevent it you can use lambda :
myButton = Button(root, text=f"hello {bite}/5", command = lambda: click(hp))

"name is not defined" inside function inside condition

When I run the code below, and press the 'go' button, I get the error:
NameError: name 'be' is not defined
...on the following line in the countdown function:
if be >= 0:
Even if I add global be in the function countdown, I get the same error.
import tkinter as tk
def set1():
global be
if be1 is not '':
be = int(en.get())
def countdown():
global be
if be >= 0:
mins, secs = divmod(be, 60)
timer = '{:02d}:{:02d}'.format(mins, secs)
label['text'] = timer
root.after(1000, countdown)
be -= 1
root = tk.Tk()
label = tk.Label(root, text = '00:00', font = 'Helvetica 25')
label.pack()
en = tk.Entry(root)
en.pack()
be1 = en.get()
tk.Button(root, text = 'set', height = 3, width = 20, command = lambda: set1()).pack()
tk.Button(root, text = 'go', height = 3, width = 20, command = lambda: countdown()).pack()
root.mainloop()
Why doesn't global be resolve the problem?
The reason is clear from the error message: you need to define be as a global variable. Note that the global statement does not define the variable, it merely links the local references to that name with an existing global variable. But if it doesn't exist, there is nothing to match it with.
So you should define be as a global variable, like:
be = 0
There are a few other issues and things to improve:
be1 is read from the input only once, when the script starts, not when the user clicks a button. So its value will always remain the empty string. Instead, you should read the input into be1 when the "set" button is clicked, and so the variable be1 can just be a local variable in that set1 function, not global as it is now.
Don't use is not (or is) when comparing a variable with a string literal. So you should have:
if be1 != '':
It is better to already display the updated start-time when the user clicks the "set" button, even before the user clicks "go" -- otherwise they don't see the effect of clicking "set".
Since you already have code for displaying the timer in the countdown function, it would be good to avoid code duplication and make a new function just for doing the displaying.
So taking all that together you would get:
import tkinter as tk
be = 0 # define as global here
def display(): # new function to avoid code repetition
global be
mins, secs = divmod(be, 60)
timer = '{:02d}:{:02d}'.format(mins, secs)
label['text'] = timer
def set1():
global be
be1 = en.get() # local variable, reading fresh input value
if be1 != '': # don't use "is not" with a literal
be = int(be1)
display()
def countdown():
global be
if be >= 0:
display()
root.after(1000, countdown)
be -= 1
root = tk.Tk()
label = tk.Label(root, text = '00:00', font = 'Helvetica 25')
label.pack()
en = tk.Entry(root)
en.pack()
tk.Button(root, text = 'set', height = 3, width = 20, command = lambda: set1()).pack()
tk.Button(root, text = 'go', height = 3, width = 20, command = lambda: countdown()).pack()
root.mainloop()

TypeError: 'float' object has no attribute '__getitem__'. (Python)

import Tkinter
import tkMessageBox
from Tkinter import *
CanvasHeight = 500
CanvasWidth = 600
Canvas width and height set to 10x the maximum of the variables.
IsGraphHidden = 0
MainWindow = Tkinter.Tk()
This is the window for all the sliders, and is defined as "MainWindow" for later use.
Strength = DoubleVar()
Multiple = DoubleVar()
Time = DoubleVar()
All of the variables set to DoubleVar, because of the Tkinter plugin.
It needs it's own special floats, integers and strings to work.
They can be accessed as normal variables by using VARIABLE.get()
coords = []
lastcoords = [0,0]
This is what we'll use to continue the line instead of just having a bunch of lines drawing themselves from the corner of the screen.
Plot = DoubleVar()
StrengthScale = Scale( MainWindow, variable = Strength, orient = HORIZONTAL,label="Strength")
MultipleScale = Scale( MainWindow, variable = Multiple, from_ = float(0), to = float(1), resolution = float(0.01), orient = HORIZONTAL, label="Multiple")
TimeScale = Scale( MainWindow, variable = Time, orient = HORIZONTAL, from_ = int(0), to = int(120), label="Time")
These are the procedures for the buttons, as well as the rest of the code.
def Calculate():
answer = float(Strength.get())*float(Multiple.get())
tkMessageBox.showinfo("Answer:", answer)
def PrepPlot():
global IsGraphHidden
global coords
global lastcoords
lastcoords0 = lastcoords[0]
lastcoords1 = lastcoords[1]
coords.append(lastcoords0)
coords.append(lastcoords1)
coords.append(Time.get()*5)
coords.append(Strength.get()*Multiple.get()*5)
lastcoords = Time.get()*5
lastcoords = Strength.get()*Multiple.get()*5
if IsGraphHidden == 0:
Graph = Canvas(MainWindow, width = CanvasWidth, height = CanvasHeight, bg = "white")
Graph.create_line(coords, fill = "black")
Graph.grid(row=5, column=1)
else:
Graph.destroy()
Graph.delete("all")
Graph.create_line(coords, fill = "black")
Graph.grid(row=5,column=1)
IsGraphHidden = 1
def DisplayPoints():
PointWindow = Tkinter.Tk()
Text = Label(PointWindow, textvariable = "Hi there", relief=RAISED)
Text.pack()
PointWindow.mainloop() #Work in progress, nothin' to see here.
Button = Tkinter.Button(MainWindow, text= "Calculate",command = Calculate)
PrepButton = Tkinter.Button(MainWindow, text = "Plot", command = PrepPlot) #The text is the text on the button.
DisplayButton = Tkinter.Button(MainWindow, text = "Display Plots", command = DisplayPoints)
MultipleScale.grid(row=0,column=0)
StrengthScale.grid(row=1,column=0)
TimeScale.grid(row=1,column=2)
PrepButton.grid(row=2,column=1)
Button.grid(row=4,column=1)
DisplayButton.grid(row=3,column=1)
MainWindow.mainloop()
I need some help with the float object getitem error, I'm doing this code for work experience at Manchester university...
You replaced the lastcoords list with a floating point value:
lastcoords = Time.get()*5
lastcoords = Strength.get()*Multiple.get()*5
so that next time around the line:
lastcoords0 = lastcoords[0]
raises your exception as you cannot use subscription on a floating point value.
I think you wanted to set a new list instead:
lastcoords = [Time.get() * 5, Strength.get() * Multiple.get() * 5]

Python Tkinter StringVar issues

I am trying to make a simple calculator for a game I play as a random project. I found myself very confused on how to go about doing this; I want the user to enter a number in the textbox and it will multiply that number by 64000 and display it in a label above. But, I cannot find out anywhere how to do this. This is just a small practice thing and im amazed I cannot figure it out. Please help meh! Here is my code:
from Tkinter import *
####The GUI#####
root = Tk()
#Title#
root.wm_title("TMF Coin Calculator")
####The Widgets####
a = StringVar()
b = StringVar()
def multiply_a():
d = (int(a) * 64000)
e = str(d)
b.set(e)
b.trace("w", multiply_a)
#Top Label#
top_label = Label(root, fg="red", text="Type the ammount of Coin Stacks you have.")
top_label.grid(row=0, column=0)
#The Output Label#
output_label = Label(root, textvariable=b, width = 20)
output_label.grid(row = 1, column=0)
#User Entry Box#
user_input_box = Entry(root, textvariable=a, width=30)
user_input_box.grid(row=2, column=0)
root.mainloop()
You need to trace the variable that changes: a. Then you need to define multiply_a() to handle an arbitrary number of arguments with *args (happens to be 3 arguments in this case, but *args is fine here).
Go from:
def multiply_a():
d = (int(a) * 64000)
e = str(d)
b.set(e)
b.trace("w", multiply_a)
to:
def multiply_a(*args):
try:
myvar = int(a.get())
except ValueError:
myvar = 0
b.set(myvar * 64000)
a.trace("w", multiply_a)
This will additionally use 0 if the entered value can't be turned into an int.

Categories