Making a flashing text in tinker - python-3.x

I want a text that flashes with a clock's seconds. This Link was helpful, but couldn't solve my problem. Below is my little working code:
from tkinter import *
from datetime import datetime
import datetime as dt
import time
def change_color():
curtime=''
newtime = time.strftime('%H:%M:%S')
if newtime != curtime:
curtime = dt.date.today().strftime("%B")[:3]+", "+dt.datetime.now().strftime("%d")+"\n"+newtime
clock.config(text=curtime)
clock.after(200, change_color)
flash_colours=('black', 'red')
for i in range(0, len(flash_colours)):
print("{0}".format(flash_colours[i]))
flashing_text.config(foreground="{0}".format(flash_colours[i]))
root = Tk()
clock = Label(root, text="clock")
clock.pack()
flashing_text = Label(root, text="Flashing text")
flashing_text.pack()
change_color()
root.mainloop()
This line of code: print("{0}".format(flash_colours[i])) prints the alternating colors on the console as the function calls itself every 200s. But the flashing_text Label's text foreground doesn't change colors.
Does anybody have a solution to this problem? Thanks!
Please forgive my bad coding.

Although you have changed the color of the flashing_text in the for loop twice, but the tkinter event handler (mainloop()) can only process the changes when it takes back the control after change_color() completed. So you can only see the flashing_text in red (the last color change).
To achieve the goal, you need to change the color once in the change_color(). Below is a modified change_color():
def change_color(color_idx=0, pasttime=None):
newtime = time.strftime('%H:%M:%S')
if newtime != pasttime:
curtime = dt.date.today().strftime("%B")[:3]+", "+dt.datetime.now().strftime("%d")+"\n"+newtime
clock.config(text=curtime)
flash_colors = ('black', 'red')
flashing_text.config(foreground=flash_colors[color_idx])
clock.after(200, change_color, 1-color_idx, newtime)

I would add it to a class so you can share your variables from each callback.
So something like this.
from tkinter import *
from datetime import datetime
import datetime as dt
import time
class Clock:
def __init__(self, colors):
self.root = Tk()
self.clock = Label(self.root, text="clock")
self.clock.pack()
self.flashing_text = Label(self.root, text="Flashing text")
self.flashing_text.pack()
self.curtime = time.strftime('%H:%M:%S')
self.flash_colours = colors
self.current_colour = 0
self.change_color()
self.root.mainloop()
def change_color(self):
self.newtime = time.strftime('%H:%M:%S')
if self.newtime != self.curtime:
if not self.current_colour:
self.current_colour = 1
else:
self.current_colour = 0
self.curtime = time.strftime('%H:%M:%S')
self.flashing_text.config(foreground="{0}".format(self.flash_colours[self.current_colour]))
self.clock.config(text=self.curtime)
self.clock.after(200, self.change_color)
if __name__ == '__main__':
clock = Clock(('black', 'red'))

Related

How to control the behaviour of QGridLayout

Using PyQT6
I am trying to create and app that adds a new frame (stack1) whenever the check box is clicked. It all works well until the app hides the stack when you untick the checkbox.
Clearly what I'd like is for the app to return to the initial shape when I hide the stack. I am aware that since I have added a column to the QTGrid,the window has likely reshaped taking into account the extra space. I am getting the same behaviour using deleteLater. So my question is how can I avoid that? Is there a way to reset the gridLayout to go back to just 1 column?
Here is a reproducible example:
import sys
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from PyQt6.QtWidgets import QWidget, QApplication, QVBoxLayout, QStackedWidget,
QLabel, QPushButton
import sys
from PyQt6.QtGui import QIcon
from PyQt6 import QtGui
class Window(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("Anticoagulant Dose calculator")
self.buttons = {'metrics': ['Age?', 'Height in cm?', 'Serum Creatinine?', 'Weight in kg?', 'Sex?'],
'anticoagulant': ['DOAC', 'warfarin, \nacenocumarol'],
'indication': ['VTE', 'Atrial Fibrilation', 'Mechanical Heart valve'],
'VTE': ['single VTE > 12 months', 'VTE 3-12 months or \n multiple, large volume PE or \n active cancer', 'VTE < 3 months \n or known antithrombin deficiency or \n antiphospholipid syndrome'],
'af': ['No',"Yes, within the past 12 months","Yes, within the past 3 months"],
'Mech': 'mech',
'sexo': ['Male', 'Female']}
self.dialogLayout = QVBoxLayout()
self.anticoagLayout = QVBoxLayout()
self.rejillaLayout = QGridLayout()
self.anticoagButtongroup = QButtonGroup()
self.indicationButtongroup = QButtonGroup()
self.UIcomponents()
self.setLayout(self.rejillaLayout)
self.stack1 = QWidget()
self.stack1UI()
def stack1UI(self):
for a in self.buttons['anticoagulant']:
self.anticoag_rb = QRadioButton(a)
self.anticoagButtongroup.addButton(self.anticoag_rb)
self.dialogLayout.addWidget(self.anticoag_rb)
for i in self.buttons['indication']:
self.indication_rb = QRadioButton(i)
self.indicationButtongroup.addButton(self.indication_rb)
self.dialogLayout.addWidget(self.indication_rb)
self.stack1.setLayout(self.dialogLayout)
def cb_onclicked(self):
self.cbox = self.bridgingCheckBox.sender()
try:
if self.cbox.isChecked():
print('Puenteando')
self.rejillaLayout.addWidget(self.stack1, 0, 2)
self.stack1.show()
else:
self.stack1.deleteLater()
print('No puenteando')
except RuntimeError:
pass
def UIcomponents(self):
self.entries = {}
formas = QFormLayout()
for m in self.buttons['metrics']:
if m != 'Sex?':
self.ent = QLineEdit()
formas.addRow(m, self.ent)
else:
print('Sexo?')
self.sComboBox = QComboBox()
self.sComboBox.addItems(self.buttons['sexo'])
sLabel = QLabel('Sex:?')
sLabel.setBuddy(self.sComboBox)
formas.addRow(sLabel, self.sComboBox)
self.entries[m] = self.ent
self.bridgingCheckBox = QCheckBox("Bridging dose")
self.bridgingCheckBox.toggled.connect(self.cb_onclicked)
formas.addRow(self.bridgingCheckBox, None)
self.rejillaLayout.addLayout(formas, 0, 0)
if __name__ == '__main__':
app = QApplication([])
window = Window()
window.setGeometry(50, 50, 300, 160)
window.show()
sys.exit(app.exec())
Many thanks in advance.
Thanks yo #ekhumoro for the answer:
gridding the stack at the init stage then hiding it, and then adding the code to the checkbox connector does it.
def cb_onclicked(self):
self.cbox = self.bridgingCheckBox.sender()
self.stack1.setVisible(self.bridgingCheckBox.isChecked())
self.adjustSize()

Stop a working function with a button (Python, Tkinter)

I have a little situation with Tkinter. I have a piece of code that constantly receives raw EMG data from Thalmic`s Myo Armband and writes that data (plus the name of a gesture) to a csv file. I designed a little tkinter interface to make it look more user-friendly. What I need is to write a function that will stop the previously started recording function.
Also I have a problem with the label, that shows the number of rows in a csv file, but it is another question.
Here is the code:
from gesture_classificator import MyoRaw
import csv
import tkinter
import tkinter.messagebox
import sys
root = tkinter.Tk()
root.title("Recording data")
m = MyoRaw(sys.argv[1] if len(sys.argv) >= 2 else None)
v = tkinter.StringVar()
entry = tkinter.Entry(root, textvariable=v)
entry.grid(row=0, column=0)
v.set("")
def process_emg(emg, times):
name = v.get()
with open('own_test.csv', 'a+') as file:
writing = csv.writer(file)
writing.writerow(emg+(name,))
def show_row_count():
with open('own_test.csv', 'r') as return_me_the_row_count:
reading = csv.reader(return_me_the_row_count)
data = list(reading)
row_count = len(data)
return row_count
def start_recording():
m.add_emg_handler(process_emg)
m.connect()
def stop_recording():
???
B1 = tkinter.Button(root, text="Start the recording", command=start_recording)
B2 = tkinter.Button(root, text="Stop the recording", command=stop_recording)
rows_number = tkinter.Label(root, text=show_row_count)
rows_number.config(text=show_row_count)
B1.grid(row=1, column=0)
B2.grid(row=1, column=1)
rows_number.grid(row=0, column=1)
root.mainloop()
try:
while True:
m.run(1)
except SystemExit:
pass
finally:
m.disconnect()
According to the MyoRaw code, you would probably like the function MyoRaw.disconnect(), which you would use like this:
def stop_recording():
m.disconnect()
In such cases it would be easier and faster for you to check the documentation and/or the code of the framework you are using.

Timed Availability of controls in tkinter GUI

I'm working on a program that will stop users from changing a label after a random amount of time. There are two buttons, start and next, when the user presses start the start button is destroyed but is supposed to come back after a randomly selected amount of time. I tried to have the start button trigger a flag that starts a timer. When the timer reaches a certain value (count_to+1) the flag should go to zero, the start button should reappear, and the label should read end. The flag never seems to switch and the timer never initiates though. Can anyone tell me what I did wrong? and maybe point me towards a solution? Hear is the code:
import sys
from tkinter import *
import random
import time
mGui = Tk()
mGui.geometry('450x450+200+200')
mGui.title('Letters')
stored = ['A', 'b', 'c', 'd']
count_down = [10,20,30,40,50,60]
global count_to
global countFlag
count_to = IntVar()
countFlag = 0
Sec = 0
def run_counter():
count_to = random.choice(count_down)
while countFlag == 1:
Sec+=1
print(sec)
if Sec == count_to+1:
countFlag = 0
newbutton.destroy()
startbutton.grid(row=2,column=1)
phrase.configure(text='End')
return
def change_phrase():
fish = StringVar()
fish = random.choice(stored)
stored.remove(fish)
phrase.configure(text=fish)
#to help with debug
print(countFlag)
print(Sec)
print(count_to)
return
def start_count():
countFlag = True
count_to = random.choice(count_down)
print(countFlag)
startbutton.destroy()
run_counter
return
phrase = Label(mGui,text='Letter',fg='red',bg='blue')
phrase.grid(row=0,column=0, sticky=S,columnspan=2)
startbutton =Button(mGui, text='start',fg='black',bg='green',command=start_count)
startbutton.grid(row=2,column=1)
newbutton = Button(mGui,text='NEXT',fg='black',bg='red',command=change_phrase)
newbutton.grid(row=2,column=0)
#mEntry = Entry(mGui,textvariable=ment)
#mEntry.grid(row=3,column=0)
mGui.mainloop()
Tkinter programming becomes much less messy and confusing once you learn to use classes. Use Tkinter's after() method to call a function every "x" amount of time until the allotted time has elapsed.
import random
import sys
if sys.version_info[0] < 3:
import Tkinter as tk ## Python 2.x
else:
import tkinter as tk ## Python 3.x
class ButtonDisappear():
def __init__(self, root):
self.root=root
self.startbutton=tk.Button(root, text='disappear', fg='black',
bg='green', command=self.disappear)
self.startbutton.grid(row=2,column=1)
self.lab=tk.Label(self.root, text="", bg="lightblue")
def disappear(self):
## remove button
self.startbutton.grid_forget()
## grid label for time
self.lab.grid(row=0, column=0)
## "random" number
self.stop_time=random.choice([1, 2, 3, 4, 5])*1000
self.elapsed=0
self.root.after(100, self.time_it)
def time_it(self):
self.elapsed += 100
## function calls itself until time has finished
if self.elapsed < self.stop_time:
self.lab["text"]="%d of %d" % (self.elapsed, self.stop_time)
self.root.after(100, self.time_it)
## time elapsed so remove label and restore button
else:
self.lab.grid_forget()
self.startbutton.grid(row=2,column=1)
m_gui = tk.Tk()
m_gui.geometry('450x450+200+200')
m_gui.title('Letters')
B=ButtonDisappear(m_gui)
m_gui.mainloop()

Python Variable in tkinter window

I am building a device that counts how many parts are made off a machine and then turns the machine off at a specific number. I am using an Arduino for all the I/O work and then importing the serial data into Python as variable partCount. I would like to create a simple GUI in tkinter to show the number of parts that have been made and the total number needed. The problem is that I keep getting an error on the label lines that include a variable instead of just a text. I've done a lot of research on it, but I just can't get it for some reason. Any advice would be appreciated.
import serial
import csv
import datetime
import tkinter
#Part Variables
partNumber = "A-33693" #Part Number
stockupTotal = 10
arduinoSerialData = serial.Serial('com3',9600) #Serial Variable
now = datetime.datetime.now()
#GUI
window = tkinter.Tk()
window.title("Troy Screw Products")
titleLabel = tkinter.Label(window, text="Davenport Machine Control")
partNumberLabel = tkinter.Label(window, text="Part #:")
stockUpTotalLabel = tkinter.Label(window, text="Stockup Total:")
partCountLabel = tkinter.Label(window, text="Current Part Count:")
partNumberInfo = tkinter.Label(window, partNumber)
stockUpTotalInfo = tkinter.Label(window, stockupTotal)
partCountInfo = tkinter.Label(window, partCount)
titleLabel.pack()
partNumberLabel.pack()
partNumberInfo.pack()
stockUpTotalLabel.pack()
stockUpTotalInfo.pack()
partCountLabel.pack()
partCountInfo.pack()
window.mainloop()
#Write to CSV File
def writeCsv():
with open("machineRunData.csv", "a") as machineData:
machineDataWriter = csv.writer(machineData)
machineDataWriter.writerow([partNumber, "Stockup Complete",now.strftime("%Y-%m-%d %H:%M")])
machineData.close()
#Serial Import
while (1==1):
if (arduinoSerialData.inWaiting()>0):
partCount = arduinoSerialData.readline()
partCount = int(partCount)
if partCount == 999999:
writeCsv()
print(partCount)
There are several options you can give to a Label widget, as you can see here. You should specify all parameters after the first by name:
partNumberInfo = tkinter.Label(window, text=PartNumber)
To use Python variables in Tkinter you need to special Tk objects, of which there are four: BooleanVar, DoubleVar, IntVar, and StringVar. See this answer for a good example.
So after a ton of research over the last week, I found a solution. The problem was that the program would run the GUI, but not run the code for the serial import until the GUI window was closed. I needed a way to get both the GUI running and updated and get the serial data importing at the same time. I resolved this by creating a thread for both operations. There's probably an easier way to do this, but this what I came up with. The code is below for anyone having a similar problem.
import serial
import time
import threading
from tkinter import *
#Part Variables
partNumber = "A-33693" #Part Number
stockupTotal = 10
partCount = 0
def countingModule():
global partCount
while (1==1):
time.sleep(2)
partCount += 1
print(partCount)
def gui():
global partCount
root = Tk()
pc = IntVar()
pc.set(partCount)
titleLabel = Label(root, text="Machine Control")
partNumberLabel = Label(root, text="Part #:")
stockUpTotalLabel = Label(root, text="Stockup Total:")
partCountLabel = Label(root, text="Current Part Count:")
partNumberInfo = Label(root, text=partNumber)
stockUpTotalInfo = Label(root, text=stockupTotal)
partCountInfo = Label(root, textvariable=pc)
titleLabel.pack()
partNumberLabel.pack()
partNumberInfo.pack()
stockUpTotalLabel.pack()
stockUpTotalInfo.pack()
partCountLabel.pack()
partCountInfo.pack()
def updateCount():
pc.set(partCount)
root.after(1, updateCount)
root.after(1, updateCount)
root.mainloop()
op1 = threading.Thread(target = countingModule)
op2 = threading.Thread(target = gui)
op1.start()
op2.start()

gtk progressbar doesn't work anymore after matplot event?

Hi I'm new at programming in python and gtk.
I'm writing a program to do some measurement.
For plotting the measurement, I use matplotlib.
The program will have a function to turn a heater on and off and to make the measurement.
I want to use separate threads for the heater and the measurement.
For now the communication with the hardware hasn't been implemented yet in this program.
The problem is when I click the "measurebutton", the "progressbar" doesn't work anymore.
I get a message:
gtk.ProgressBar object at 0x29b8460 (uninitialized at 0x0)
When I only use the heaterbutton, the progressbar keeps working
What am I doing wrong ?
This is the code
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
import time
import gobject
import threading
import matplotlib
import numpy as np
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
class measure:
# callback to quit
def delete_event(self, widget, event, data = None):
gtk.main_quit()
return False
def heater_helper(self, widget, heater, progressbar):
print "starting heater thread"
threading.Thread(target=self.heater_cb, args=(widget, heater, progressbar)).start()
def heater_cb(self, widget, heater, progressbar):
heaterstring = "6.3"
heater = eval(heaterstring)
stap = 1
j = 0.1
heatervalue = widget.get_active()
print heatervalue
progressbar.set_fraction(0.1)
while (stap <= 10 ):
if widget.get_active():
print widget.get_active()
fraction = j * stap
print fraction
progressbar.set_fraction(fraction)
stap = stap + 1
time.sleep(1)
else:
stap = 11
progressbar.set_fraction(0.0)
break
def do_measurement_helper(self, widget, fig):
print " Start measurement thread"
threading.Thread(target=self.do_measurement, args=(widget, fig)).start()
def do_measurement(self, widget, fig):
fig.clear()
ax = fig.add_subplot(111)
x = np.arange(0, 5*np.pi, 0.01)
y = np.sin(x**2)*np.exp(-x)
ax.plot(x, y)
fig.canvas.draw()
def __init__(self):
# Create new Window
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("delete_event", self.delete_event)
self.window.show()
mainbox = gtk.HBox(False, 0)
self.window.add(mainbox)
mainbox.show()
leftvbox = gtk.VBox(False, spacing = 10)
mainbox.pack_start(leftvbox, expand = False, fill = False, padding = 0)
leftvbox.show()
rightvbox = gtk.VBox(False, spacing = 10)
mainbox.pack_start(rightvbox, expand = False, fill = False, padding =0)
rightvbox.show()
heaterprogressbar = gtk.ProgressBar()
leftvbox.pack_start(heaterprogressbar, expand = False, fill = False, padding = 0)
heaterprogressbar.show()
heaterbutton = gtk.ToggleButton("Heater")
leftvbox.pack_start(heaterbutton, expand = True, fill = False, padding = 0)
heaterbutton.show()
heaterbutton.connect("toggled", self.heater_helper, heaterbutton, heaterprogressbar)
fig = matplotlib.figure.Figure(figsize=(5,4), dpi=64)
canvas = FigureCanvas(fig)
rightvbox.pack_start(canvas, expand = True, fill = True, padding = 0 )
canvas.show()
measurebutton = gtk.Button("Measure")
rightvbox.pack_start(measurebutton, expand = False, fill = False, padding = 0)
measurebutton.show()
measurebutton.connect("clicked", self.do_measurement_helper, fig)
def main():
gtk.main()
return(0)
if __name__ == "__main__":
gtk.gdk.threads_init()
measure()
main()
gtk.gdk.threads_leave()
Kind regards,
Joris Weijters
Combining threads, Matplotlib, and the GTK main loop is probably not supported and difficult to debug exactly what is going on. My advice is not to do any GUI calls from threads, but instead schedule them using gobject.idle_add().
Threads, Matplotlib and the GTK main loop can be combined, if you keep in mind some things:
I use gobject.threads_init() instead of gtk.gdk.threads_init(), the gtk.gdk variant did not work for me in combination with Matplotlib. I think you can also omit the gtk.gdk.threads_leave().
As ptomato mentioned, you should let the main gtk thread handle anything that has to do with gtk widgets by calling the gobject.idle_add() and gobject.timeout_add() functions.
I usually make a helper function to periodically update the statusbar from a float variable:
def do_measurement(self):
self.data = []
self.progress = 0
self.abort = threading.Event()
gobject.timeout_add(100, self.update_progressbar)
for point in some_generator_yielding_100_values():
if self.abort.is_set():
break
self.data.append(point)
self.progress += 0.01
self.progress = None
def update_progressbar(self):
if self.progress is None:
self.progressbar.set_fraction(0) # reset bar
return False # do not run again
self.progressbar.set_fraction(self.progress)
return True # run again after 100ms
def start_measurement(self):
threading.Thread(target=self.do_measurement).start()
def stop_measurement(self):
self.abort.set()
But you can of course also just call gobject.idle_add(self.progressbar.set_fraction, x) to set the new value x asynchroneously.

Resources