Tkinter - Changing label text via another function - python-3.x

I know that it's often best practice to write Tkinter GUI code using object-oriented programming (OOP), but I'm trying to keep things simple because I'm new to Python.
I have written the following code to create a simple GUI:
#!/usr/bin/python3
from tkinter import *
from tkinter import ttk
def ChangeLabelText():
MyLabel.config(text = 'You pressed the button!')
def main():
Root = Tk()
MyLabel = ttk.Label(Root, text = 'The button has not been pressed.')
MyLabel.pack()
MyButton = ttk.Button(Root, text = 'Press Me', command = ChangeLabelText)
MyButton.pack()
Root.mainloop()
if __name__ == "__main__": main()
The GUI looks like this.
I thought the text in the GUI (MyLabel) would change to "You pressed the button!" when the button is clicked, but I get the following error when I click the button:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\elsey\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 1549, in __call__
return self.func(*args)
File "C:/Users/elsey/Documents/question code.py", line 6, in ChangeLabelText
MyLabel.config(text = 'You pressed the button!')
NameError: name 'MyLabel' is not defined
What am I doing wrong? Any guidance would be appreciated.

MyLabel is local to main() so the way you can not access it that way from ChangeLabelText().
If you do not want to change the design of your program, then you will need to change the definition of ChangeLabelText() like what follows:
def ChangeLabelText(m):
m.config(text = 'You pressed the button!')
And withing main() you will need to pass MyLabel as an argument to ChangeLabelText().
But again, you will have a problem if you code this command = ChangeLabelText(MyLabel) when you declare and define MyButton because the program will execute directly the body of ChangeLabelText() at the start and you will not have the desired result.
To resolve this later problem, you will have to use (and may be read about) lambda
Full program
So your program becomes:
#!/usr/bin/python3
from tkinter import *
from tkinter import ttk
def ChangeLabelText(m):
m.config(text = 'You pressed the button!')
def main():
Root = Tk()
MyLabel = ttk.Label(Root, text = 'The button has not been pressed.')
MyLabel.pack()
MyButton = ttk.Button(Root, text = 'Press Me', command = lambda: ChangeLabelText(MyLabel))
MyButton.pack()
Root.mainloop()
if __name__ == "__main__":
main()
Demo
Before clicking:
After clicking:

Are your sure you don't want to do it as a class (i think it makes the code a bit more clean as your project grows)? Here is a way to accomplish what you'e looking for:
#!/usr/bin/python3
from tkinter import *
from tkinter import ttk
class myWindow:
def __init__(self, master):
self.MyLabel = ttk.Label(root, text = 'The button has not been pressed.')
self.MyLabel.pack()
self.MyButton = ttk.Button(root, text = 'Press Me', command = self.ChangeLabelText)
self.MyButton.pack()
def ChangeLabelText(self, event=None):
self.MyLabel.config(text = 'You pressed the button!')
if __name__ == "__main__":
root = Tk()
mainWindow = myWindow(root)
root.mainloop()
In a Mac, is looks like this before pressing the button:
And when you press it:
But basically, in order to be able to change the text in a Label or a button, you need to ensure it has an active reference. In this case, we are doing it by creating the window as a class and referencing the widgets in the form self. widget_name = widget().

but I'm trying to keep things simple because I'm new to Python Hopefully this helps in understanding that classes are the simple way, otherwise you have to jump through hoops and manually keep track of many variables. Also, the Python Style Guide suggests that CamelCase is used for class names and lower_case_with_underlines for variables and functions. https://www.python.org/dev/peps/pep-0008/
from tkinter import *
from tkinter import ttk
class ChangeLabel():
def __init__(self):
root = Tk()
self.my_label = ttk.Label(root, text = 'The button has not been pressed.')
self.my_label.pack()
## not necessary to keep a reference to this button
## because it is not referenced anywhere else
ttk.Button(root, text = 'Press Me',
command = self.change_label_text).pack()
root.mainloop()
def change_label_text(self):
self.my_label.config(text = 'You pressed the button!')
if __name__ == "__main__":
CL=ChangeLabel()

Related

Changing order of execution in python

I have recently started programming and written a fairly simple program. But stuck at a point. My program does a very simple thing that when you click a button it will say a line and shows that text on the screen. But it is speaking the text first and then displays it on the screen but I want its reverse, i.e it should first display it on the screen and then speak the text.
from tkinter import *
import pyttsx3
root = Tk()
def speak():
engine = pyttsx3.init()
say = "I am speaking."
text.insert("0.0", say)
engine.say(say)
engine.runAndWait()
text = Text(root)
text.pack()
btn = Button(root, text="speak", command=speak)
btn.pack()
root.mainloop()
Thanks in advance.
It is because engine.runAndWait() blocks the application, and so text box cannot be updated by tkinter mainloop until the speaking completed.
You can call text.update_idletasks() to force tkinter to update the text box before the speaking:
engine = pyttsx3.init() # initialize only once
def speak():
say = "I am speaking."
text.insert("0.0", say)
text.update_idletasks() # force tkinter update
engine.say(say)
engine.runAndWait()
You could delay the call to engine.say
from tkinter import *
import pyttsx3
def speak(sentence):
engine.say(sentence)
engine.runAndWait()
def display_and_speak():
sentence = "I am speaking."
text.insert("1.0", sentence)
root.after(1000, speak, sentence) # call to speak delayed 1 second
engine = pyttsx3.init()
root = Tk()
text = Text(root)
text.pack()
btn = Button(root, text="speak", command=display_and_speak)
btn.pack()
root.mainloop()

stop ttk.Entry() from reading keyboard

Environment: macOS Catalina, Python 3.7.4, Tcl/Tk 8.6.9, VSC 1.39.1
I have a situation where I am using a bar/qr code scanner to provide a string to a ttk.Entry() method, which then fires off a function.
The reader is seen by the OS as an HID keyboard, so the text from the QR code is received by the ttk.Entry() widget that I give focus to during code execution. I have bound the widget to the key because the scanner sends a cr/lf at the end of the text string, which works as needed.
However, I am running into an issue where if the qr code lingers over the scanner too long it will rescan the qr code and the widget receives the qr code text again, which then causes it to be processed again.
I have tried disabling the ttk.Entry() in the function, deleting the widget contents, and removing focus to no avail. The behavior I'm seeing is occurring even though the widget is disabled and does not have focus, it is still getting input and executing the function again if the scanner rescans the qr code while the function is executing.
In this first example, I simply tried to disable the widget, but that doesn't work. The widget still gets the later scans while in the function.
# test-ttk-entry1.py
import time
import tkinter as tk
from tkinter import StringVar, ttk
root = tk.Tk()
def print_text(event):
global kbEntry
textValue = kbEntry.get()
kbEntry.configure(state="disabled")
time.sleep(2) # Add in a delay to allow for repeat scan
print(textValue)
time.sleep(2) # Add in a delay to allow for repeat scan
kbEntry.configure(state="active")
kbText = StringVar()
kbEntry = ttk.Entry(root, width=10, textvariable=kbText)
kbEntry.bind("<Return>", print_text)
kbEntry.pack()
kbEntry.focus_set()
root.mainloop()
The second attempt was to disable the entry widget and upon making it active again delete the text in the field.
# test-ttk-entry2.py
import time
import tkinter as tk
from tkinter import END, StringVar, ttk
root = tk.Tk()
def print_text(event):
global kbEntry
textValue = kbEntry.get()
kbEntry.delete(0, END)
kbEntry.configure(state="disabled")
time.sleep(2) # Add in a delay to allow for repeat scan
print(textValue)
time.sleep(2) # Add in a delay to allow for repeat scan
kbEntry.configure(state="active")
kbEntry.delete(0, END)
kbText = StringVar()
kbEntry = ttk.Entry(root, width=10, textvariable=kbText)
kbEntry.bind("<Return>", print_text)
kbEntry.pack()
kbEntry.focus_set()
root.mainloop()
And finally, I was reading about taking focus from a widget and giving focus to the root window, so I added that in and it still prints multiple times to the console like there is a keyboard buffer being read by the ttk.Entry() widget. The weird thing is it seems like widgets don't normally respond to any calls to methods when they are disabled, but it appears the ttk.Entry() widget's properties/attributes (excuse me if my OOP terms are not correct) can be manipulated while disabled.
# test-ttk-entry2.py
import time
import tkinter as tk
from tkinter import END, StringVar, ttk
root = tk.Tk()
def print_text(event):
global kbEntry
textValue = kbEntry.get()
kbEntry.delete(0, END)
kbEntry.configure(state="disabled")
root.focus_set()
time.sleep(2) # Add in a delay to allow for repeat scan
print(textValue)
time.sleep(2) # Add in a delay to allow for repeat scan
kbEntry.configure(state="active")
kbEntry.delete(0, END)
kbEntry.focus_set()
kbText = StringVar()
kbEntry = ttk.Entry(root, width=10, textvariable=kbText)
kbEntry.bind("<Return>", print_text)
kbEntry.pack()
kbEntry.focus_set()
root.mainloop()
So how can I prevent the ttk.Entry() widget from accepting any input from the HID/keyboard device while my function is executing?
Since I am unable to find a way to temporarily disable the .Entry() widget programmatically to keep it from reading successive inputs, I have come up with this solution:
# test-ttk-entry2.py
import time
import tkinter as tk
from tkinter import END, StringVar, ttk
root = tk.Tk()
tempStr = "" # Global temporary string variable to trip repeated scans
def print_text(event):
global tempStr
textValue = kbEntry.get()
if tempStr == textValue:
kbEntry.delete(0, END)
print ("Duplicate scan")
return
tempStr = textValue
kbEntry.delete(0, END)
print(textValue)
time.sleep(3) # Simulate the time it takes for the function to complete
kbEntry = ttk.Entry(root, width=10)
kbEntry.bind("<Return>", print_text)
kbEntry.grid(row=0, column=0, padx=10, pady=10)
kbEntry.focus_set()
root.mainloop()

print current selected file name

I am using the following function to set the text of a Label to be the file name
i just picked , but i want to get the name of the file i'm currently selecting before canceling or opening the file from the dialog
is there any way to do so by using tkinter or any other framework/lib
here's the code
def pick_csv(self):
filename = filedialog.askopenfilename()
filelabel.config(text=filename)
#filelabel should get the name of the selected file
#before closing the dialog
I don't think you can use the native askopenfilename to achieve this. But you can write your own class that inherit from FileDialog. It will look ugly and needs some work on the layout.
from tkinter import *
from tkinter import filedialog
import os
root = Tk()
a = Label(root,text="Waiting for selection")
a.pack()
class CustomDialog(filedialog.FileDialog):
def __init__(self,master,path=""):
filedialog.FileDialog.__init__(self,master)
self.files.bind("<Button-1>",self.set_label)
self.selected = None
self.go(path)
def set_label(self,*args):
a.config(text=self.files.get(self.files.curselection()))
self.selected = os.path.join(self.directory, self.files.get(self.files.curselection()))
def cancel_command(self, event=None):
self.selected = None
self.quit()
def click():
f = CustomDialog(root)
if f.selected:
print (f.selected)
b = Button(root,text="Browse",command=click)
b.pack()
root.mainloop()

How to get input from a function in Python and print in tkinter GUI?

from tkinter import *
def printSomething():
inputValue=textBox.get("1.0","end-1c")
res=response(inputValue)
label = Label(root, text=res)
#this creates a new label to the GUI
label.pack()
root = Tk()
button = Button(root, text="Print Me", command=printSomething)
button.pack()
textBox=Text(root, height=2, width=10)
textBox.pack()
root.mainloop()
I have written a python code that returns text. and print that in tkinter label.while i try to execute it shows "None" in label.
It would probably be better to create the label in the global namespace once and then just update the label every time you press the button.
I also recommend using import tkinter as tk vs from tkinter import * as it provides better maintainability as your code grows and you do not end up overwriting built in methods.
I have updated your code and changed a few things to better fit the PEP8 standard.
import tkinter as tk
def print_something():
label.config(text=text_box.get("1.0", "end-1c"))
root = tk.Tk()
tk.Button(root, text="Print Me", command=print_something).pack()
text_box = tk.Text(root, height=2, width=10)
text_box.pack()
label = tk.Label(root)
label.pack()
root.mainloop()
Just changing your line:
res = response(inputValue)
to
res = inputValue
worked for me, creating a new label every time I pressed the button.

TKinter auto updating label from urllib

I'm trying make auto updating label from url.
I want to make something like pager. When file on server is changed, label should changes too. with button I can download it manually but I want to automate it. Where I'm making mistake?
from tkinter import *
import urllib.request
import time
root = Tk()
check = ""
#functions
def auto():
time.sleep(5) #becouse I don't want kill server
page = "http://howan.pl/pychal/plik.txt"
g = urllib.request.urlopen(page)
data = g.read()
g.close()
return (str(data, encoding='utf-8'))
def click():
page = "http://howan.pl/pychal/plik.txt"
g = urllib.request.urlopen(page)
data = g.read()
g.close()
label.config(text=str(data, encoding='utf-8'))
#Widgets
label = Label(root, text="zer0")
button = Button(root, text="hey", command= click)
if auto() == check:
check = auto
label.config(text=check)
print(auto())
label.pack()
button.pack()
root.mainloop()
To automate it you need to make a function that does the work, and then use root.after() to call that function on a regular basis. Since you have all the work in "click" already, you could just add:
def auto_click():
click()
root.after(5000, auto_click) # call this function again in 5,000 ms (5 seconds)
auto_click() # start the autoclick loop.

Resources