How to update a GUI window in Python? - python

My goal is to have a window with the latest quote of a stock updating during the day. I chose alpha_vantage as a quote source, pysimplegui to create the window and twisted to run a loop to update the window every minute. The code works as written, prints the correct quote and change, creates the window as desired, but the window does not update.
Why doesn't the window update?
from alpha_vantage.timeseries import TimeSeries
from twisted.internet import task, reactor
import PySimpleGUI as sg
def paintQuote():
quote, quote_meta = av.get_intraday(symbol='spy', interval = '1min')
last = quote.iloc[-1][3]
print('{0:6.2f}'.format(last))
change = (last / yesterday - 1) * 100
print('{0:4.2f}%'.format(change))
event, values = window.read()
window['quote'].update(last)
# window color
sg.theme('BluePurple')
# window layout
layout = [[sg.Text('last price', size=(20, 2), justification='center')],
[sg.Text(''), sg.Text(size=(24,1), key='quote')]]
# create window
window = sg.Window('MikeQuote', layout)
wait = 60.0
av = TimeSeries(key ='your_key', output_format = 'pandas')
yest, yest_meta = av.get_daily(symbol='spy')
yesterday = yest.iloc[-2][3]
loop = task.LoopingCall(paintQuote)
loop.start(wait)
reactor.run()
window.close()

Answer:
Your script is not calling paintQuote more than once. Add print lines in there and you'll see it never calls it more than once.
Suggested solutions:
I don't know much about that reactor or loopingCall thing or how it works. A simpler solution is just to use a while loop with a sleep in it. Here is my solution that seemed to work well:
import PySimpleGUI as sg
from alpha_vantage.timeseries import TimeSeries
import time
sg.theme('BluePurple')
layout = [[sg.Text('Last Price', size=(20, 2), justification='center')],
[sg.Text('', size=(10, 2), font=('Helvetica', 20),
justification='center', key='quote')]]
window = sg.Window('MikeQuote', layout)
av = TimeSeries(key = 'key')
spy, _ = av.get_quote_endpoint(symbol='SPY')
last = spy['05. price']
yest = spy['08. previous close']
wait = 1 # Wait is in seconds
while True:
event, values = window.read(timeout=10)
if event in (None, 'Quit'):
break
spy, _ = av.get_quote_endpoint(symbol='SPY')
last = spy['05. price']
window['quote'].update(last)
time.sleep(wait)
I added a few tweaks including:
Calling just the "GLOBAL_QUOTE" endpoint (so you're not returning the entire massive intraday dataset)
Remove twisted package for a simple while loop with a time.sleep function.
Added a 'Quit' event so it actually stop when you close the window.
Removed the paintQuote() function. I think clean code ideally would have this function not removed, but you can add it back in however you like.
Removed the pandas integration. You're not dealing with massive data manipulation so it's easier and faster to just use the JSON format.

Related

Tkinter using mainloop and another loop

I'm doing a project where I read info from a socket and then intend to display it on a gui using tkinter. The thing is, my read info from socket is a loop and for the gui I need another loop.
I'm pretty inexperienced with both Python and Tkinter, which probably explains my mistake here.
The fd_dict is a dictionary with the properties and respective values of a car ex: gear, power, speed, etc (theme of my project).
The main problem is either I get the values from the socket or I display the gui, never both obviously, since it stays on the earlier loop.
while True:
# UDP server part of the connection
message, address = server_socket.recvfrom(1024)
del address
fdp = ForzaDataPacket(message)
fdp.wall_clock = dt.datetime.now()
# Get all properties
properties = fdp.get_props()
# Get parameters
data = fdp.to_list(params)
assert len(data) == len(properties)
# Zip into a dictionary
fd_dict = dict(zip(properties, data))
# Add timestamp
fd_dict['timestamp'] = str(fdp.wall_clock)
# Print of various testing values
print('GEAR: ', fd_dict['gear'])
print('SPEED(in KMH): ', fd_dict['speed'] * 3.6) #speed in kph
print('POWER(in HP): ', fd_dict['power'] * 0.0013596216173) #power in hp
#print('PERFORMANCE INDEX: ', fd_dict['car_performance_index'])
print('\n')
The tkinter code:
window = Tk()
window.title('Forza Horizon 5 Telemetry')
window.geometry("1500x800")
window.configure(bg="#1a1a1a")
frame = Frame(window)
frame.pack()
label_gear = Label(text = '0')
label_gear.configure(bg="darkgrey")
label_gear.pack()
I read about using after() and using classes, but I've never used them, and can't figure out how to apply them here.
Thanks in advance.

Button callback function not completely executed by Panel library in Python

I am currently struggling trying to use the panel library in Python, in order to build an interactive dashboard to analyze and display CSV data. My current goal is to let the user enter an initial and a final date, which will be used to filter a DataFrame once a button is pressed. However, whenever I press the button, the on_click function is not completely executed before the script stops running. The code snippet is the following:
import panel as pn
pn.extension()
def acquire_data(dateBeginning, dateEnd):
eventDF = pd.read_csv('multi.csv')
eventDF['Date']= pd.to_datetime(eventDF['Date'])
dateDF = eventDF[eventDF.upvotes > 8]
print(eventDF)
def register_dates(event, save=True):
dateBeginning = date1Picker.value
dateEnd = date2Picker.value
if dateBeginning < dateEnd:
text = pn.widgets.StaticText(name='Static Text', value='A string')
spinner = pn.indicators.LoadingSpinner(width=50, height=50, value=True, color='info', bgcolor='light')
layout = pn.Column(text, spinner, align='center')
layout.app()
print('getting in')
acquire_data(dateBeginning, dateEnd)
print('getting out')
spinner.value = False
else:
print('Not working')
#pn.pane.Alert('## Alert\nThis is a warning!')
return save
date1Picker = pn.widgets.DatePicker(name='Date Initiale', margin=25)
date2Picker = pn.widgets.DatePicker(name='Date Finale', margin=25)
button = pn.widgets.Button(name="Analyse", button_type='primary', margin=(25, 0, 20, 200), width=200)
button.on_click(register_dates)
dateLayout = pn.Row(date1Picker, date2Picker)
layout = pn.Column(dateLayout, button, width=200, align='center')
layout.app()
I was also aiming at having the first layout be replaced by the one with the spinner and the text once the button is pressed, but I haven't found anything in the doc mentioning how to do so. If anyone could give me a hint regarding these issues, that would really help me!
In def acquire_data(dateBeginning, dateEnd):
pd.read_csv('multi.csv'), pd.to_datetime(eventDF['Date'])
For start, in this function I think you forgot to import panda and your app just crash.
add: import pandas as pd
Ex:
import panel as pn
import pandas as pd

nonetype object not subscriptable pysimplegui

I'm trying to learn python and pysimplegui at the same time. Also I am old which doesn't help!
I am writing a practice program with my 10 year old son(blind leading the blind) and am running into a problem which i cant fix.
Basically the program lets you enter how many numbers to pick from and how many numbers to pick, then calculates the odds of winning. Hit generate to randomly pick the numbers for you and Print the results to a txt file for a record of your picks.
It all works fine but when i close the window i get a nonetype error which I can't work out.
Can any of ye genius's help?
This is the offending line
n=int(values['--tn--'])
from os import close
import random
from tkinter import Scrollbar
import PySimpleGUI as sg
import datetime
import math
from time import sleep, time
from PySimpleGUI.PySimpleGUI import Open, WIN_CLOSED, main
import sys
sg.theme('Reddit')
layout = [
[sg.In(size=(5,1),k="--tn--" ) ]+[sg.Text('Enter total amount of
numbers',size=(35,1))],
[sg.In(size=(5,1),k="--pn--")]+[sg.Text('Enter how many numbers
you are picking',size=(35,1))],
[sg.Text('Win odds')]+[sg.ML(background_color='light
coral',text_color='white',key='--oddout--',size=(50,2))],
[sg.ML(size=(20,30), key='--main--')],
[sg.Submit('Odds',key='--odds--')]+[sg.Submit('Generate',key='--
gen--')]+ [sg.Cancel('Cancel')]+[sg.Save(key='--save--')]+
[sg.CloseButton('Close',pad=(100,0))]
]
window = sg.Window('Lotto number generator',layout)
while True:
event, values = window.read()
n=int(values['--tn--'])
rr=int(values['--pn--'])
nf = math.factorial(n)
rf = math.factorial(rr)
winodds = (nf/(rf*math.factorial(n-rr)))
winodds = int(winodds)
now = datetime.datetime.now()
if event == WIN_CLOSED:
window['--tn--'].update('1')
break
if event == '--gen--':
r = random.sample(range(1,n),rr)
for i in r:
window['--main--'].print(i)
if event == '--odds--':
window['--oddout--'].print("Your chances of winning are
",f'{winodds:,d}', " to 1, Good Luck")
if event == 'Cancel':
window['--oddout--'].update('')
window['--tn--'].update('')
window['--pn--'].update('')
if event == '--save--':
sys.stdout = open("lotto.txt", "w")
print(values['--main--'])
sys.stdout=close(fd=0)
window.close()
event, values = window.read() is returning None. None['--tn--'] does not exist as it doesn't make sense for None to have a property, hence the error message. You have used the test to avoid this but moved it below an attempt to use the missing property. Hence the error.
It's also worth using a linting tool prompt you to make adjustments to syntax that will break your code and good practice warnings. I use pylint and flake8. The following addresses your specific error message with some tidying for the linter messages. There are still some warnings - good learning exercise :).
"""Learning program."""
from os import close
import random
import PySimpleGUI as sg
import datetime
import math
from PySimpleGUI.PySimpleGUI import Open, WIN_CLOSED, main
import sys
sg.theme('Reddit')
layout = [
[sg.In(size=(5, 1), k="--tn--")] +
[sg.Text('Enter total amount of numbers', size=(35, 1))],
[sg.In(size=(5, 1), k="--pn--")] +
[sg.Text('Enter how many numbers you are picking', size=(35, 1))],
[sg.Text('Win odds')] +
[sg.ML(
background_color='light coral', text_color='white', key='--oddout--', size=(50, 2)
)],
[sg.ML(size=(20, 30), key='--main--')],
[sg.Submit('Odds', key='--odds--')] +
[sg.Submit('Generate', key='--gen--')] +
[sg.Cancel('Cancel')] +
[sg.Save(key='--save--')] +
[sg.CloseButton('Close', pad=(100, 0))]
]
window = sg.Window('Lotto number generator', layout)
while True:
event, values = window.read()
# Moved the next three lines up and commented update which also errors
if event == WIN_CLOSED:
# window['--tn--'].update('1')
break
n = int(values['--tn--'])
rr = int(values['--pn--'])
nf = math.factorial(n)
rf = math.factorial(rr)
winodds = (nf/(rf*math.factorial(n-rr)))
winodds = int(winodds)
now = datetime.datetime.now()
if event == '--gen--':
r = random.sample(range(1, n), rr)
for i in r:
window['--main--'].print(i)
if event == '--odds--':
window['--oddout--'].print(
"Your chances of winning are", f'{winodds:,d}', " to 1, Good Luck"
)
if event == 'Cancel':
window['--oddout--'].update('')
window['--tn--'].update('')
window['--pn--'].update('')
if event == '--save--':
sys.stdout = open("lotto.txt", "w")
print(values['--main--'])
sys.stdout = close(fd=0)
window.close()
Flake8 in particular will prompt you to follow practices that don't have an obvious practical purpose. Later as you use more of the language the benefit of flake8 prompts are good habits that eventually pay large benefits.
There're something not good,
You should check the window close event first, not to processing event, values for other cases first, like following code. You may get event, values as None, None if not, then values['--tn--'] will be same as None['--tn--']. That's why you got TypeError: 'NoneType' object is not subscriptable.
while True:
event, values = window.read()
if event in (sg.WINDOW_CLOSED, 'Close'):
break
# process other events from here
window.close()
In your input fields, values['--tn--'] or values['--pn--'] maybe not with correct format for integer number, so following code may get failure ValueError: invalid literal for int() with base 10
n=int(values['--tn--'])
rr=int(values['--pn--'])
Here's my way to avoid issue,
def integer(string):
try:
value = int(string)
except:
value = None
return value
for string in ("10.5", "", "10"):
value = integer(string)
if value is None:
print(f"{repr(string)} is not a legal integer string !")
else:
print(f"{repr(string)} converted to {value} !")
'10.5' is not a legal integer string !
'' is not a legal integer string !
'10' converted to 10 !
Basically, window destroied after you click close button X of window, so you should not update anything on it.
if event == WIN_CLOSED:
# window['--tn--'].update('1')
break
When you close a window, event and values are not set, see my example below.
While debugging, it's a good practice to print out the current values of event and values to be able to check whether you get what you thought you'd get, like this:
def test():
layout = [[sg.In(size=(5, 1), k="--tn--"), sg.Text('Enter total amount of numbers', size=(35, 1))],
[sg.In(size=(5, 1), k="--pn--"), sg.Text('Enter how many numbers you are picking', size=(35, 1))],
[sg.Text('Win odds'),
sg.ML(background_color='light coral', text_color='white', key='--oddout--', size=(50, 2))],
[sg.ML(size=(20, 30), key='--main--')],
[sg.Submit('Odds', key='--odds--'), sg.Submit('Generate', key='--gen--'),
sg.Cancel('Cancel'), sg.Save(key=' - -save - -'), sg.CloseButton('Close', pad=(100, 0))]
]
window = sg.Window('Lotto number generator', layout)
while True:
event, values = window.read()
print(f'event = {event}, values = {values}')
if event == WIN_CLOSED:
break
window.close()
When you close the window, you get
event = None, values = {'--tn--': None, '--pn--': None, '--oddout--': None, '--main--': None}
so, it is important to start your main loop with if event == WIN_CLOSED: (and break the loop in that case). Only after that, you can go on to process various events and values.

How To Read File Input In pySimpleGUI Then Pass It On To A Number-Crunching Processor

I would like to take input from pySimpleGUI, feed it into a normal Python var, then feed it into a music processor as I love music.
I had already tried to use wxPython for this but was unable to even get a simple fileDialog without crashing.
from pydub import AudioSegment
from os import listdir
import numpy as np
import math
import PySimpleGUI as sg
class Dankify():
song_dir = "songs"
attenuate_db = 0
accentuate_db = 2
yeet = sg.Window('Dankify ALL THE THINGS!'). Layout([[sg.Text('Filename')], [sg.Input(), sg.FileBrowse()], [sg.OK(), sg.Cancel()] ]).Read()
event, values = yeet.Read()
yeet1 = event, values
def bass_line_freq(track):
sample_track = list(track)
# c-value
est_mean = np.mean(sample_track)
# a-value
est_std = 3 * np.std(sample_track) / (math.sqrt(2))
bass_factor = int(round((est_std - est_mean) * 0.005))
return bass_factor
songfile = yeet1
for filename in listdir(songfile):
sample = AudioSegment.from_mp3(songfile)
filtered = sample.low_pass_filter(bass_line_freq(sample.get_array_of_samples()))
combined = (sample - attenuate_db).overlay(filtered + accentuate_db)
combined.export("exports/" + filename.replace(".mp3", "") + "-export.mp3", format="mp3")
However, it just does nothing, not even processing it. A reminder that I am using some open-source code and that I'm a beginner which knows nothing about how all this works and am trying to build real stuff to gain experience. Thanks!
I guess you are missing the "event loop".
Try something like this, hope it helps.
import sys
if sys.version_info[0] >= 3:
import PySimpleGUI as sg
else:
import PySimpleGUI27 as sg
layout = [[sg.Text('Your typed chars appear here:'), sg.Text('', key='_OUTPUT_') ],
[sg.Input(do_not_clear=True, key='_IN_')],
[sg.Button('Show'), sg.Button('Exit')]]
window = sg.Window('Window Title').Layout(layout)
while True: # Event Loop
event, values = window.Read()
print(event, values)
if event is None or event == 'Exit':
break
if event == 'Show':
# change the "output" element to be the value of "input" element
window.FindElement('_OUTPUT_').Update(values['_IN_'])
window.Close()
You're doing 2 Read calls.
Try changing to this:
yeet = sg.Window('Dankify ALL THE THINGS!').Layout(
[[sg.Text('Filename')], [sg.Input(), sg.FileBrowse()], [sg.OK(), sg.Cancel()]])
event, values = yeet.Read()
Without the Read on the end of the first statement.
You are instantiating this class, right?
d = Dankify()

Tkinter window not playing well with threads

I've got a program that will eventually receive data from an external source over serial, but I'm trying to develop the display-side first.
I've got this "main" module that has the simulated data send and receive. It updates a global that is used by a Matplotlib stripchart. All of this works.
#-------------------------------------------------------------------------------
# Name: BBQData
# Purpose: Gets the data from the Arduino, and runs the threads.
#-------------------------------------------------------------------------------
import time
import math
import random
from threading import Thread
import my_globals as bbq
import sys
import BBQStripChart as sc
import serial
import BBQControl as control
ser = serial.serial_for_url('loop://', timeout=10)
def simData():
newTime = time.time()
if not hasattr(simData, "lastUpdate"):
simData.lastUpdate = newTime # it doesn't exist yet, so initialize it
simData.firstTime = newTime # it doesn't exist yet, so initialize it
if newTime > simData.lastUpdate:
simData.lastUpdate = newTime
return (140 + 0.05*(simData.lastUpdate - simData.firstTime), \
145 + 0.022*(simData.lastUpdate - simData.firstTime), \
210 + random.randrange(-10, 10))
else:
return None
def serialDataPump():
testCtr = 0;
while not bbq.closing and testCtr<100:
newData = simData()
if newData != None:
reportStr = "D " + "".join(['{:3.0f} ' for x in newData]) + '\n'
reportStr = reportStr.format(*newData)
ser.write(bytes(reportStr, 'ascii'))
testCtr+=1
time.sleep(1)
bbq.closing = True
def serialDataRcv():
while not bbq.closing:
line = ser.readline()
rcvdTime = time.time()
temps = str(line, 'ascii').split(" ")
temps = temps[1:-1]
for j, x in enumerate(temps):
bbq.temps[j].append(float(x))
bbq.plotTimes.append(rcvdTime)
def main():
sendThread = Thread(target = serialDataPump)
receiveThread = Thread(target = serialDataRcv)
sendThread.start()
receiveThread.start()
# sc.runUI()
control.runControl() #blocks until user closes window
bbq.closing = True
time.sleep(2)
exit()
if __name__ == '__main__':
main()
## testSerMain()
However, I'd like to add a SEPARATE tkinter window that just has the most recent data on it, a close button, etc. I can get that window to come up, and show data initially, but none of the other threads run. (and nothing works when I try to run the window and the plot at the same time.)
#-------------------------------------------------------------------------------
# Name: BBQ Display/Control
# Purpose: displays current temp data, and control options
#-------------------------------------------------------------------------------
import tkinter as tk
import tkinter.font
import my_globals as bbq
import threading
fontSize = 78
class BBQControl(tk.Tk):
def __init__(self,parent):
tk.Tk.__init__(self,parent)
self.parent = parent
self.labelFont = tkinter.font.Font(family='Helvetica', size=int(fontSize*0.8))
self.dataFont = tkinter.font.Font(family='Helvetica', size=fontSize, weight = 'bold')
self.makeWindow()
def makeWindow(self):
self.grid()
btnClose = tk.Button(self,text=u"Close")
btnClose.grid(column=1,row=5)
lblFood = tk.Label(self,anchor=tk.CENTER, text="Food Temps", \
font = self.labelFont)
lblFood.grid(column=0,row=0)
lblPit = tk.Label(self,anchor=tk.CENTER, text="Pit Temps", \
font = self.labelFont)
lblPit.grid(column=1,row=0)
self.food1Temp = tk.StringVar()
lblFoodTemp1 = tk.Label(self,anchor=tk.E, \
textvariable=self.food1Temp, font = self.dataFont)
lblFoodTemp1.grid(column=0,row=1)
#spawn thread to update temps
updateThread = threading.Thread(target = self.updateLoop)
updateThread.start()
def updateLoop(self):
self.food1Temp.set(str(bbq.temps[1][-1]))
def runControl():
app = BBQControl(None)
app.title('BBQ Display')
app.after(0, app.updateLoop)
app.mainloop()
bbq.closing = True
if __name__ == '__main__':
runControl()
Your title sums up the problem nicely: Tkinter doesn't play well with threads. That's not a question, that's the answer.
You can only access tkinter widgets from the same thread that created the widgets. If you want to use threads, you'll need your non-gui threads to put data on a queue and have the gui thread poll the queue periodically.
One way of getting tkinter to play well with threads is to modify the library so all method calls run on a single thread. Two other questions deal with this same problem: Updating a TKinter GUI from a multiprocessing calculation and Python GUI is not responding while thread is executing. In turn, the given answers point to several modules that help to solve the problem you are facing. Whenever I work with tkinter, I always use the safetkinter module in case threads appear to be helpful in the program.

Categories