Tkinter Scale Widget -- Continuous Operation - python-3.x

Newbie question: How to make a tkinter scale operate continuously so that whenever the value is changed, the new value is written (to I2C or to a file) and this repeats until the user terminates operation of the overall program (there will need to be multiple scale controls that are operational on the GUI until the program is exited by user). The purpose is to keep sending volume values as changed by the slider. I've had no problem with buttons, text entry, radiobuttons, but I just don't see what I need to add to accomplish this. Here is code:
from tkinter import * # Python 3
from math import log10
root = Tk()
root.title("Fidelity Science Audio Control GUI")
root.geometry("500x500")
Volume = IntVar
Vol = Scale(root, variable=Volume, from_=0, to=100, tickinterval=10, orient=VERTICAL,
length=400, width=20, borderwidth=10, background="light blue", foreground="black",
troughcolor="light blue", label="Volume")
Vol.set(50)
Vol.grid(row=10, column=1)
if Vol.get() > 0:
Volume = Vol.get()
LogVolume = log10(Volume)
print("Volume = ", Volume)
print("Value sent To I2C =", LogVolume)
root.mainloop()
Do I need to use a loop function like while or if? Thanks!

You can use the callback associated to the Scale via command option to send the volume to I2C when the value of the Scale is changed:
from tkinter import * # Python 3
from math import log10
def on_volume_change(volume):
if volume > 0:
LogVolume = log10(volume)
print("Volume =", volume)
print("Value sent to I2C =", LogVolume)
# send the volume to I2C
root = Tk()
root.title("Fidelity Science Audio Control GUI")
root.geometry("500x500")
Volume = IntVar(value=50)
Vol = Scale(root, variable=Volume, from_=0, to=100, tickinterval=10, orient=VERTICAL,
length=400, width=20, borderwidth=10, background="light blue", foreground="black",
troughcolor="light blue", label="Volume", command=on_volume_change)
Vol.grid(row=10, column=1)
# send the initial volume
on_volume_change(Volume.get())
root.mainloop()

Related

Battery Status and Charging Notifier in Tkinter

I am trying to create a Battery notifier in Tkinter using psutil module. I am new in Tkinter don't know how to solve this particular problem.
This is my code
from tkinter import *
import psutil
root = Tk()
root.title('Battery Notifier')
root.geometry('400x200')
flagBoth = IntVar()
flagBoth.set(0)
flagLow = IntVar()
flagLow.set(0)
flagCharge = IntVar()
flagCharge.set(0)
def batteryLow(percent, xoffset, yoffset):
lowTop = Toplevel(root)
lowTop.geometry('419x116+' + str(xoffset) + '+' + str(yoffset))
lowTop.wm_overrideredirect(True)
lowTop.attributes('-topmost', 'True')
lowLabel = Label(lowTop, image=lowPercentImage, borderwidth=0).place(x=0, y=0)
percentLabel = Label(lowTop, text=percent, font=('Sofia Pro Bold', '20'), borderwidth=0, bg='#fff')
percentLabel.place(x=76, y=25, width=34)
# Closing after 5 seconds
lowTop.after(5000, func=lowTop.destroy)
def batteryCharging(percent, xoffset, yoffset):
chargeTop = Toplevel(root)
chargeTop.geometry('419x116+' + str(xoffset) + '+' + str(yoffset))
chargeTop.wm_overrideredirect(True)
chargeTop.attributes('-topmost', 'True')
chargeLabel = Label(chargeTop, image=batteryChargingImage, borderwidth=0).place(x=0, y=0)
percentLabel = Label(chargeTop, text=percent, font=('Sofia Pro Bold', '20'), borderwidth=0, bg='#fff')
percentLabel.place(x=99, y=58, width=34)
# Closing after 7 seconds
chargeTop.after(7000, func=chargeTop.destroy)
def batteryStatus():
battery = psutil.sensors_battery()
percent = battery.percent
chargeStatus = battery.power_plugged
critical = 90
if (percent < critical and not(flagLow.get())):
batteryLow(percent, 930, 30)
flagLow.set(1)
if chargeStatus:
batteryCharging(percent, 930, 150)
flagBoth.set(1)
elif (chargeStatus and flagCharge.get()):
batteryCharging(percent, 930, 30)
flagCharge.set(1)
else:
if chargeStatus == False and flagLow == 1:
flagLow.set(0)
root.after(1000, batteryStatus)
btn = Button(root, text='check', command=exit).pack()
lowPercentImage = PhotoImage(file='D:\\SE4\\Python\\Codes\\MiniProject\\Low Battery.png')
batteryChargingImage = PhotoImage(file='D:\\SE4\\Python\\Codes\\MiniProject\\Battery Charging.png')
root.after(1000, batteryStatus)
root.mainloop()
In this I am trying to get popup notification when my battery percent is lower than critical value then the popup notification will appear. If at the same time charger is connected it will show charger connected popup notification and then disappear after some time like it happens in OS like Windows. After that if my charger is disconnected and battery is low than critical level then again show battery low popup notification and disappear else check for battery low or charging condition if it happens then show popup notifications again.
In this problem I cannot use loops as it will make tkinter freeze. So intead I am calling the function batteryStatus again and again after 1 seconds. Can anyone just tell me how I can solve this particular problem. I just want a battery notifier exactly like it is in Windows when battery goes to 12% it shows notification when charger connected it disappears and when I remove charger and battery is low it again show notification.
Thanks in advance.
This is the low notification image
This is the battery charging notification image
This how I want the pop notification and after sometime they will disappear.
Is this something You want to do? (test run the code):
from tkinter import Tk, Label
import time
def loop_notification(counter=0):
if 70 > counter > 50:
root.deiconify()
root.overrideredirect(True)
elif counter > 70:
root.withdraw()
counter = 0
counter += 1
root.after(100, loop_notification, counter)
root = Tk()
root.attributes('-topmost', True)
root.geometry(f'400x100+{root.winfo_screenwidth() - 400}+{root.winfo_screenheight() - 100}')
root.withdraw()
Label(root, text='Notification', font='default 20 bold').pack(pady=10)
Label(root, text='Notification body here', font='default 12 normal').pack(pady=5)
loop_notification()
root.mainloop()
I hope You got the idea the loop can obviously be replaced with what You have and You don't necessarily need to use threads this is just as an example
The '-topmost' makes sure that the window is on top of others, overrideredirect hides the frame that is around windows

Unable to read serial data without errors/bugs

I am writing a small application based on tkinter in order to read serial data from my arduino.
The arduino, when it receives a serial text (rf), it will begin sending data to the pc.
Below is the suspicious code:
def readSerial():
ser_bytes = ser.readline()
ser_bytes = ser_bytes.decode("utf-8")
text.insert("end", ser_bytes)
after_id=root.after(100,readSerial)
#root.after(100,readSerial)
def measure_all():
global stop_
stop_ = False
ser.write("rf".encode()) #Send string 'rf to arduino', which means Measure all Sensors
readSerial() #Start Reading data
Now this does not work. The program freezes, and no info is revealed on the terminal.
When i change the line after_id=root.after(100,readSerial) to root.after(100,readSerial) then the program works, but only when i receive serial input.
For example, if there is a 5 second delay to when arduino sends serial, then the program will freeze, until it receives the data. More specificallly, if the program is minimized, and i select to view it as normal, it will not respond unless it receives input from arduino (which will display normally).
So even now, it still does not work properly.
But also have in mind, that i need to have the after_id line, so that i can have a handle, so that i can terminate the readSerial() function (for example when the user presses the 'stop measurement' button).
Can someone understand what is going on, and how i can have the after_id behaviour (so i can stop the continuous function later), while having the program behaving normal, without crashing or stuck until it receives data?
EDIT: This is the modified code after user's acw1668 suggestions. This does not work. I see nothing on the text frame of tkinter.
import tkinter as tk
import tkinter.ttk as ttk
import serial.tools.list_ports #for a list of all the COM ports
from tkinter import scrolledtext
import threading
import time
from queue import SimpleQueue
#to be used on our canvas
HEIGHT = 800
WIDTH = 800
#hardcoded baud rate
baudRate = 9600
# this is the global variable that will hold the serial object value
ser = None #initial value. will change at 'on_select()'
after_id = None
#this is the global variable that will hold the value from the dropdown for the sensor select
dropdown_value = None
# create the queue for holding serial data
queue = SimpleQueue()
# flag use to start/stop thread tasks
stop_flag = None
# --- functions ---
#the following two functtions are for the seria port selection, on frame 1
#this function populates the combobox on frame1, with all the serial ports of the system
def serial_ports():
return serial.tools.list_ports.comports()
#when the user selects one serial port from the combobox, this function will execute
def on_select(event=None):
global ser
COMPort = cb.get()
string_separator = "-"
COMPort = COMPort.split(string_separator, 1)[0] #remove everything after '-' character
COMPort = COMPort[:-1] #remove last character of the string (which is a space)
ser = serial.Serial(port = COMPort, baudrate=9600, timeout=0.1)
#readSerial() #start reading shit. DELETE. later to be placed in a button
# get selection from event
#print("event.widget:", event.widget.get())
# or get selection directly from combobox
#print("comboboxes: ", cb.get())
#ser = Serial(serialPort , baudRate, timeout=0, writeTimeout=0) #ensure non-blocking
def readSerial(queue):
global stop_flag
if stop_flag:
print("Reading task is already running")
else:
print("started")
stop_flag = threading.Event()
while not stop_flag.is_set():
if ser.in_waiting:
try:
ser_bytes = ser.readline()
data = ser_bytes.decode("utf-8")
queue.put(data)
except UnicodeExceptionError:
print("Unicode Error")
else:
time.sleep(0.1)
print("stopped")
stop_flag = None
# function to monitor whether data is in the queue
# if there is data, get it and insert into the text box
def data_monitor(queue):
if not queue.empty():
text.insert("end", f"{queue.get()}\n")
if vsb.get()[1]==1.0: #if the scrollbar is down to the bottom, then autoscroll
text.see("end")
root.after(100, data_monitor, queue)
# this function is triggered, when a value is selected from the dropdown
def dropdown_selection(*args):
global dropdown_value
dropdown_value = clicked.get()
button_single['state'] = 'normal' #when a selection from the dropdown happens, change the state of the 'Measure This Sensor' button to normal
# this function is triggered, when button 'Measure all Sensors' is pressed, on frame 2
def measure_all():
button_stop['state']='normal' #make the 'Stop Measurement' button accessible
ser.write("rf".encode()) #Send string 'rf to arduino', which means Measure all Sensors
sleep(0.05) # 50 milliseconds
threading.Thread(target=readSerial, args=(queue,)).start()
# this function is triggered, when button 'Measure this Sensor' is pressed, on frame 2
def measure_single():
global stop_
stop_=False
button_stop['state']='normal'
ser.write(dropdown_value.encode()) #Send string 'rf to arduino', which means Measure all Sensors!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
readSerial()
# this function is triggered, when button 'STOP measurement(s)' is pressed, on frame 2
def stop_measurement():
button_stop['state']='disabled'
ser.write("c".encode())
if stop_flag:
stop_flag.set()
else:
print("Reading task is not running")
# --- functions ---
# --- main ---
root = tk.Tk() #here we create our tkinter window
root.title("Sensor Interface")
#we use canvas as a placeholder, to get our initial screen size (we have defined HEIGHT and WIDTH)
canvas = tk.Canvas(root, height=HEIGHT, width=WIDTH)
canvas.pack()
#we use frames to organize all the widgets in the screen
'''
relheight, relwidth − Height and width as a float between 0.0 and 1.0, as a fraction of the height and width of the parent widget.
relx, rely − Horizontal and vertical offset as a float between 0.0 and 1.0, as a fraction of the height and width of the parent widget.
'''
# --- frame 1 ---
frame1 = tk.Frame(root)
frame1.place(relx=0, rely=0.05, relheight=0.03, relwidth=1, anchor='nw') #we use relheight and relwidth to fill whatever the parent is - in this case- root
label0 = tk.Label(frame1, text="Select the COM port that the device is plugged in: ")
label0.config(font=("TkDefaultFont", 8))
label0.place(relx = 0.1, rely=0.3, relwidth=0.3, relheight=0.5)
cb = ttk.Combobox(frame1, values=serial_ports())
cb.place(relx=0.5, rely=0.5, anchor='center')
# assign function to combobox, that will run when an item is selected from the dropdown
cb.bind('<<ComboboxSelected>>', on_select)
# --- frame 1 ---
# --- frame 2 ---
frame2 = tk.Frame(root, bd=5) #REMOVED THIS bg='#80c1ff' (i used it to see the borders of the frame)
frame2.place(relx=0, rely=0.1, relheight=0.07, relwidth=1, anchor='nw')
#Button for 'Measure All Sensors'
#it will be enabled initially
button_all = tk.Button(frame2, text="Measure all Sensors", bg='#80c1ff', fg='red', state='normal', command=measure_all) #bg='gray'
button_all.place(relx=0.2, rely=0.5, anchor='center')
#label
label1 = tk.Label(frame2, text="OR, select a single sensor to measure: ")
label1.config(font=("TkDefaultFont", 9))
label1.place(relx = 0.32, rely=0.3, relwidth=0.3, relheight=0.4)
#dropdown
#OPTIONS = [0,1,2,3,4,5,6,7]
OPTIONS = list(range(8)) #[0,1,2,3,4,5,6,7]
clicked = tk.StringVar(master=frame2) # Always pass the `master` keyword argument, in order to run the function when we select from the dropdown
clicked.set(OPTIONS[0]) # default value
clicked.trace("w", dropdown_selection) #When a value from the dropdown is selected, function dropdown_selection() is executed
drop = tk.OptionMenu(frame2, clicked, *OPTIONS)
drop.place(relx = 0.65, rely=0.25, relwidth=0.08, relheight=0.6)
#Button for 'Measure Single Sensor'
#this will be disabled initially, and will be enabled when an item from the dropdown is selected
button_single = tk.Button(frame2, text="Measure this Sensor", bg='#80c1ff', fg='red', state='disabled', command=measure_single) #bg='gray'
button_single.place(relx = 0.85, rely=0.5, anchor='center')
# --- frame 2 ---
# --- frame 3 ---
frame3 = tk.Frame(root, bd=5) #REMOVED THIS bg='#80c1ff' (i used it to see the borders of the frame)
frame3.place(relx=0, rely=0.2, relheight=0.07, relwidth=1, anchor='nw')
#Button for 'STOP Measurement(s)'
#this will be disabled initially, and will be enabled only when a measurement is ongoing
button_stop = tk.Button(frame3, text="STOP measurement(s)", bg='#80c1ff', fg='red', state='disabled', command=stop_measurement)
button_stop.place(relx=0.5, rely=0.5, anchor='center')
# --- frame 3 ---
# --- frame 4 ---
frame4 = tk.Frame(root, bd=5)
frame4.place(relx=0, rely=0.3, relheight=0.09, relwidth=1, anchor='nw')
label2 = tk.Label(frame4, text="Select a sensor to plot data: ")
label2.place(relx = 0.1, rely=0.3, relwidth=0.3, relheight=0.5)
clickedForPlotting = tk.StringVar()
clickedForPlotting.set(OPTIONS[0]) # default value
dropPlot = tk.OptionMenu(frame4, clickedForPlotting, *OPTIONS)
dropPlot.place(relx=0.5, rely=0.5, anchor='center')
#CHANGE LATER
#dropDownButton = tk.Button(frame4, text="Plot sensor data", bg='#80c1ff', fg='red', command=single_Sensor) #bg='gray'
#dropDownButton.place(relx = 0.85, rely=0.5, anchor='center')
# --- frame 4 ---
#frame 5 will be the save to txt file
#frame 6 will be the area with the text field
# --- frame 6 ---
frame6 = tk.Frame(root, bg='#80c1ff') #remove color later
frame6.place(relx=0.0, rely=0.4, relheight=1, relwidth=1, anchor='nw')
text_frame=tk.Frame(frame6)
text_frame.place(relx=0, rely=0, relheight=0.6, relwidth=1, anchor='nw')
text=tk.Text(text_frame)
text.place(relx=0, rely=0, relheight=1, relwidth=1, anchor='nw')
vsb=tk.Scrollbar(text_frame)
vsb.pack(side='right',fill='y')
text.config(yscrollcommand=vsb.set)
vsb.config(command=text.yview)
# --- frame 6 ---
# start data monitor task
data_monitor(queue)
root.mainloop() #here we run our app
# --- main ---
In order to not blocking the main tkinter application, it is recommended to use thread to run the serial reading. Also use queue.SimpleQueue to transfer the serial data to main task so that the serial data can be inserted into the Text widget.
Below is an example:
import threading
import time
from queue import SimpleQueue
import tkinter as tk
import serial
class SerialReader(threading.Thread):
def __init__(self, ser, queue, *args, **kw):
super().__init__(*args, **kw)
self.ser = ser
self.queue = queue
self._stop_flag = threading.Event()
def run(self):
print("started")
while not self._stop_flag.is_set():
if self.ser.in_waiting:
ser_bytes = self.ser.readline()
data = ser_bytes.decode("utf-8")
self.queue.put(data)
else:
time.sleep(0.1)
print("stopped")
def terminate(self):
self._stop_flag.set()
# create the serial instance
ser = serial.Serial(port="COM1") # provide other parameters as well
# create the queue for holding serial data
queue = SimpleQueue()
# the serial reader task
reader = None
def start_reader():
global reader
if reader is None:
# create the serial reader task
reader = SerialReader(ser, queue, daemon=True)
if not reader.is_alive():
# start the serial reader task
reader.start()
else:
print("Reader is already running")
def stop_reader():
global reader
if reader and reader.is_alive():
# stop the serial reader task
reader.terminate()
reader = None
else:
print("Reader is not running")
# function to monitor whether data is in the queue
# if there is data, get it and insert into the text box
def data_monitor(queue):
if not queue.empty():
text.insert("end", f"{queue.get()}\n")
root.after(100, data_monitor, queue)
root = tk.Tk()
text = tk.Text(root, width=80, height=20)
text.pack()
frame = tk.Frame(root)
tk.Button(frame, text="Start", command=start_reader).pack(side="left")
tk.Button(frame, text="Stop", command=stop_reader).pack(side="left")
frame.pack()
# start data monitor task
data_monitor(queue)
root.mainloop()
Update#2021-04-16: Example without using class
import threading
import time
from queue import SimpleQueue
import tkinter as tk
import serial
# create the serial instance
ser = serial.Serial(port="COM1") # provide other parameters as well
# create the queue for holding serial data
queue = SimpleQueue()
# flag use to start/stop thread tasks
stop_flag = None
def readSerial(queue):
global stop_flag
if stop_flag:
print("Reading task is already running")
else:
print("started")
stop_flag = threading.Event()
while not stop_flag.is_set():
if ser.in_waiting:
ser_bytes = ser.readline()
data = ser_bytes.decode("utf-8")
queue.put(data)
else:
time.sleep(0.1)
print("stopped")
stop_flag = None
def start_reader():
threading.Thread(target=readSerial, args=(queue,)).start()
def stop_reader():
if stop_flag:
stop_flag.set()
else:
print("Reading task is not running")
# function to monitor whether data is in the queue
# if there is data, get it and insert into the text box
def data_monitor(queue):
if not queue.empty():
text.insert("end", f"{queue.get()}\n")
root.after(100, data_monitor, queue)
root = tk.Tk()
text = tk.Text(root, width=80, height=20)
text.pack()
frame = tk.Frame(root)
tk.Button(frame, text="Start", command=start_reader).pack(side="left")
tk.Button(frame, text="Stop", command=stop_reader).pack(side="left")
frame.pack()
# start data monitor task
data_monitor(queue)
root.mainloop()

Run Tkinter GUI on serial read

I am using tkinter in python 3.6 on a raspberry pi to display a blue square in the middle of a window. At each click the blue square should disappear and reappear after 2 seconds on a different random location.
I need the code to start after the serial port of the raspberry pi reads information from a RFID scanner. The idea is that my cat will go through the door of the box that will contain the pie and a touchscreen, the RFID reads the animal's chip and starts the procedure described above. If no interaction occurs for a whole minute the screen goes back to black and the task starts listening to the serial port again, until the cat eventually comes back and everything starts again.
Here is a simplified version of the code
try:
import Tkinter as TK
except:
import tkinter as TK
import random as RAN
class THR:
def __init__(self, root):
self.root = root
self.root.config(background='black')
self.screenYpixels = 600
self.screenXpixels = 1024
self.ITI = 2000
self.background = TK.Canvas(root, width=1024, height=600, bg='black',
bd=0, highlightthickness=0, relief='ridge')
self.background.pack()
self.newtrial()
def newtrial(self):
self.xpos = RAN.randrange(200, 1000)
self.ypos = RAN.randrange(100, 500)
self.stimulus = TK.Canvas(root,width=100,height=100,bg='blue', bd=0,
highlightthickness=0, relief='ridge')
self.stimulus.place(x=self.xpos, y=self.ypos, anchor="c")
self.stimulus.bind("<Button-1>", self.response)
self.exitbutton()
def response(self, event):
self.stimulus.place_forget()
self.intertrialinterval()
def intertrialinterval(self, *args):
self.root.after(self.ITI,self.newtrial)
def exitbutton(self):
self.exitButton = TK.Button(self.root, bg="green")
self.exitButton.place(relx=0.99, rely=0.01, anchor="c")
self.exitButton.bind("<Button-1>", self.exitprogram)
def exitprogram(self, root):
self.root.quit()
root = TK.Tk()
THR(root)
root.mainloop()
And here there is some code I manage to put together to read the cat's ID from the implanted chip
import serial
import struct
import binascii
import os
ser = serial.Serial(
port='/dev/ttyUSB0',
baudrate = 19200,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1
)
while 1:
if ser.inWaiting() > 0:
while ser.inWaiting() > 0:
i = ser.readline()
catID = binascii.b2a_hex(i)
catID = catID[10:20]
print(catID)
How can I properly setup the code to achieve a continuous, stand-alone system that:
Detects if the cat crossed the door
Starts the task (shows a blue square on the
screen, etc..) and stops listening to the serial port
if 60 seconds have passed without interaction goes
to black screen
Serial port is set again to listen

Python 3 Radio button controlling label text

I am in the process of learning Python3 and more of a necessity, the TkInter GUI side. I was working my way through a book by James Kelly, when I encountered this problem. All his examples made a new window with just label/canvas/check box etc which seemed to work OK.
But as I wanted to experiment in a more real world scenario I put most things on one window. This where I encountered my problem. I can not get the radio button in the frame to alter the wording of a label in the parent window.
Complete code is:-
#! /usr/bin/python3
from tkinter import *
def win_pos(WL,WH,xo=0,yo=0) :
# Screen size & position procedure
# Screen size
SW = home.winfo_screenwidth()
SH = home.winfo_screenheight()
# 1/2 screen size
sw=SW/2
sh=SH/2
# 1/2 window size
wl=WL/2
wh=WH/2
# Window position
WPx=sw-wl+xo
WPy=sh-wh+yo
# Resulting string
screen_geometry=str(WL) + "x" + str(WH) + "+" + str(int(WPx)) + "+" \ + str(int(WPy))
return screen_geometry
# Create a window
home=Tk()
home.title("Radio buttons test")
# Set the main window
home.geometry(win_pos(600,150))
lab1=Label(home)
lab1.grid(row=1,column=1)
fraym1=LabelFrame(home, bd=5, bg="red",relief=SUNKEN, text="Label frame text")
fraym1.grid(row=2,column=2)
laybl1=Label(fraym1, text="This is laybl1")
laybl1.grid(row=0, column=3)
var1=IntVar()
R1=Radiobutton(fraym1, text="Apple", variable=var1, value=1)
R1.grid(row=1, column=1)
R2=Radiobutton(fraym1, text="Asus", variable=var1, value=2)
R2.grid(row=1, column=2)
R3=Radiobutton(fraym1, text="HP", variable=var1, value=3)
R3.grid(row=1, column=3)
R4=Radiobutton(fraym1, text="Lenovo", variable=var1, value=4)
R4.grid(row=1, column=4)
R5=Radiobutton(fraym1, text="Toshiba", variable=var1, value=5)
R5.grid(row=1, column=5)
# Create function used later
def sel(var) :
selection="Manufacturer: "
if var.get() > 0 :
selection=selection + str(var.get())
lab1.config(text=selection)
R1.config(command=sel(var1))
R2.config(command=sel(var1))
R3.config(command=sel(var1))
R4.config(command=sel(var1))
R5.config(command=sel(var1))
R1.select()
mainloop()
I realise that there is room for improvement using classes/functions but I need to get this resolved in my head before I move on. As it can be hopefully seen, I'm not a complete novice to programming, but this is doing my head in.
Can a solution, and reasoning behind the solution, be given?
You can modify your label's text by assigning the same variable class object, var1 as its textvariable option as well but since lab1's text is slightly different, try removing:
R1.config(command=sel(var1))
R2.config(command=sel(var1))
R3.config(command=sel(var1))
R4.config(command=sel(var1))
R5.config(command=sel(var1))
R1.select()
and modify sel to:
def sel(*args) :
selection="Manufacturer: "
selection=selection + str(var1.get())
lab1.config(text=selection)
and then call var1.trace("w", sel) somewhere before mainloop as in:
...
var1.trace("w", sel)
mainloop()
Also for a simple example:
import tkinter as tk
root = tk.Tk()
manufacturers = ["man1", "man2", "man3", "man4", "man5"]
lbl = tk.Label(root, text="Please select a manufacturer.")
lbl.pack()
# create an empty dictionary to fill with Radiobutton widgets
man_select = dict()
# create a variable class to be manipulated by radiobuttons
man_var = tk.StringVar(value="type_default_value_here_if_wanted")
# fill radiobutton dictionary with keys from manufacturers list with Radiobutton
# values assigned to corresponding manufacturer name
for man in manufacturers:
man_select[man] = tk.Radiobutton(root, text=man, variable=man_var, value=man)
#display
man_select[man].pack()
def lbl_update(*args):
selection="Manufacturer: "
selection=selection + man_var.get()
lbl['text'] = selection
#run lbl_update function every time man_var's value changes
man_var.trace('w', lbl_update)
root.mainloop()
Example with label's identical to that of radiobutton's value:
import tkinter as tk
root = tk.Tk()
# radiobutton group will the button selected with the value=1
num = tk.IntVar(value=1)
lbl = tk.Label(root, textvariable=num)
zero = tk.Radiobutton(root, text="Zero", variable=num, value=0)
one = tk.Radiobutton(root, text="One", variable=num, value=1)
#display
lbl.pack()
zero.pack()
one.pack()
root.mainloop()

Tkinter GUI Freezes - Tips to Unblock/Thread?

New to python3 and started my first project of using a raspberry pi 3 to create an interface to monitor and control elements in my greenhouse. Currently the program reads Temperature and Humidity via a DHT11 sensor, and controls a number of relays and servo via the GPIO pins.
I created a GUI to display the Temperature and Humidity that reads and updates every 250ms. There is also a number of buttons that control the specific relays/servo.
I am now running into some issues with the tkinter GUI freezing on a button press. I have looked on the forum a bit but don't understand how to implement threading or a check function to keep my GUI from freezing.
Code Below:
from tkinter import *
import tkinter.font
import RPi.GPIO as GPIO
import time
import Adafruit_DHT
#Logic Setup
temp = 0
humd = 0
#GPIO Setup
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(16, GPIO.OUT) #Water Pump
GPIO.setup(18, GPIO.IN) #Tank Float Switch
GPIO.output(16, GPIO.LOW)
#Window Setup
win = Tk()
win.title("Test")
win.geometry("200x300+0+0")
#Label Setup
Label (win, text="Temperature", fg="red", bg="black", font="24").grid(row=0, column=0)
Label (win, text="Humidity", fg="red", bg="black", font="24").grid(row=0, column=2)
Label (win, text="Water System", fg="red", bg="black", font="24").grid(row=3, column=0)
TEMP = Label (win, text="", fg="black", bg="white", font="36")
TEMP.grid(row=1, column=0)
HUMD = Label (win, text="", fg="black", bg="white", font="36")
HUMD.grid(row=1, column=2)
#Functions
def wait(time_lapse):
time_start = time.time()
time_end = (time_start+time_lapse)
while time_end >= time.time():
pass
def RTEMP ():
global temp
humidity, temperature = Adafruit_DHT.read_retry(11, 27)
temp = temperature * 9/5.0 + 32
TEMP.configure(text=str(temp))
def RHUMD ():
global humd
humidity, temperature = Adafruit_DHT.read_retry(11, 27)
humd = humidity
HUMD.configure(text=str(humd))
def READ ():
RTEMP()
RHUMD()
win.after(250, READ)
def PUMP ():
if GPIO.input(18):
WTR.config(bg="green")
GPIO.output(16, GPIO.HIGH)
wait (10)
GPIO.output(16, GPIO.LOW)
WTR.config(text="Water", bg="grey")
else:
GPIO.output(16, GPIO.LOW)
WTR.config(text="LOW WATER", bg="red")
#Buttons
WTR = Button(win, text="Water", bg="grey", command = PUMP, height = 2, width = 8)
WTR.grid(row=4, column=0) #Water Pump Control
#Function Calls
READ()
mainloop()
Tkinter GUIs (as well as most other GUIs) are in a perpetual wait state. There's no reason to introduce explicit waiting for events.
If you need to run some function, and then run some other function 10ms later, you would schedule the other code to run with after. For example:
GPIO.output(16, GPIO.HIGH)
win.after(10, GPIO.output, 16, GPIO.LOW)
Naturally, if you want to do more than one thing you can use lambda or write another function.

Resources