How do I make a PySimpleGUI calculate the options? - python

Trying to make the GUI application calculate cost. Unsure how to do it
So I've built my GUI Application using PySimpleGUI but i'd like to make it functional and calculate the total membership cost in the pop up window I've created. Can someone point me in the right direction? How do I apply numerical values to radio buttons and checkboxes to calculate costs?
Thanks! sorry I'm very new to this so I'm not well versed in Python.

Most of time, I use dictionary to map the item to a value for calculation later. With enable_Events=True in Checkbox or Radio element, or another Submit Button, to generate an event when click for the calculation and GUI update.
import PySimpleGUI as sg
discounts = {"75%":0.75, "85%":0.85, "95%":0.95, "No":1.0}
prices = {"Summer Dress":545, "Coat":275, "Elegant Suit":685, "Casual Shirt":98, "Bridesmaid Dress":440, "Shoes":195}
layout = [
[sg.Text("Discount")],
[sg.Radio(discount, "Discount", enable_events=True, key=("Discount", discount)) for discount in discounts],
[sg.Text("Price List")]] + [
[sg.Checkbox(price, enable_events=True, key=("Price", price)), sg.Push(), sg.Text(prices[price])] for price in prices] + [
[sg.Text("Total: 0", justification='right', expand_x=True, key='Total')],
]
window = sg.Window('Example', layout)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif isinstance(event, tuple) and event[0] in ('Discount', 'Price'):
dis = 1
for discount in discounts:
if values[('Discount', discount)]:
dis = discounts[discount]
break
total = sum([prices[price] for price in prices if values[("Price", price)]])
window['Total'].update(f"Total: {int(total*dis)}")
window.close()

Related

Is it possible to secure Tabs in my PySimpleGUI code?

Dears,
Is it possible to secure Tabs in my PySimpleGUI code ? Means that only 1st Tab can be kept accessible and the other ones request password:
Knowing that I'm able to do that using Collapsible function as follows :
def Collapsible(layout, key, title='', arrows=(sg.SYMBOL_DOWN, sg.SYMBOL_UP),
collapsed=False):
return sg.Column([[sg.T((arrows[1] if collapsed else arrows[0]), enable_events=True,
text_color='DeepSkyBlue2', k=key+'-BUTTON-'),
sg.T(title, enable_events=True, text_color='yellow', key=key+'-TITLE-')],
[sg.pin(sg.Column(layout, key=key, visible=not collapsed,
metadata=arrows))]], pad=(0,0))
==> Here's teh Layout Part
#### 1st part ####
[Collapsible(Menu1, SEC1_KEY,, collapsed=True)],
#### 2nd part ####
[Collapsible(Menu2, SEC2_KEY, collapsed=True)],
while True: # Event Loop
event, values = window.read()
#print(event, values)
if event == s
if event.startswith(SEC2_KEY):
window[SEC2_KEY].update(visible=not window[SEC2_KEY].visible)
else:
window[SEC2_KEY+'-BUTTON-'].update(window[SEC2_KEY].metadata[0] if
window[SEC2_KEY].visible else window[SEC2_KEY].metadata[1])
Any one can help on that ? Thanks
Information for a question here, IMO, it will be better.
Add everything required
Remove everything not related.
Most simple layout if GUI required.
Here, just for how to set which tab accessible. tkinter code required here.
import PySimpleGUI as sg
accessible = [0, 3]
layout = [[sg.TabGroup([[sg.Tab(f'TAB {i}',[[sg.Text(f'This is the Tab {i}')]],key=f'Tab {i}',) for i in range(5)]], key='TabGroup')]]
window = sg.Window('Tab Group', layout, finalize=True)
window['TabGroup'].bind('<Button-1>', ' Change', propagate=False)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event == 'TabGroup Change':
e = window['TabGroup'].user_bind_event
if e.widget.identify(e.x, e.y) == 'label':
index = e.widget.index(f'#{e.x},{e.y}')
if index in accessible:
window[f'Tab {index}'].select()
""" Password secured
else:
if sg.popup_get_text("Password") == 'Hello':
window[f'Tab {index}'].select()
"""
window.close()

In PySimpleGUI, how can I have a hyperlink in a text field?

I am creating a search engine based on this Youtube tutorial which gives the output of the search result in a sg.Output element. I want each result to be clickable and open in Windows File Explorer with the file selected.
My issues is in a PySimpleGUI output box (sg.Output) I can only seem to have text.
How can I have text with a link attached to run a subprocess like this? My guess is it is something like what was discussed here:
sg.Text('Here', is_link=True, key='link') # then link the key to an event
However, as previously mentioned, if I add anything but text to sg.Output it does not work, i.e., the following does not work:
sg.window.FindElement('-OUTPUT-').Update(sg.Text('Here', is_link=True, key='link'))
It will be much complex to enable hyperlink function for sg.Output or sg.Multiline.
Here's simple code to provide hyperlink function for sg.Text, also work for some other elements, by using options enable_events=True and tooltip.
import webbrowser
import PySimpleGUI as sg
urls = {
'Google':'https://www.google.com',
'Amazon':'https://www.amazon.com/',
'NASA' :'https://www.nasa.gov/',
'Python':'https://www.python.org/',
}
items = sorted(urls.keys())
sg.theme("DarkBlue")
font = ('Courier New', 16, 'underline')
layout = [[sg.Text(txt, tooltip=urls[txt], enable_events=True, font=font,
key=f'URL {urls[txt]}')] for txt in items]
window = sg.Window('Hyperlink', layout, size=(250, 150), finalize=True)
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
elif event.startswith("URL "):
url = event.split(' ')[1]
webbrowser.open(url)
print(event, values)
window.close()
This took awhile, however I'm sure I'll use it a lot. xD
Additional you can specify the tooltip parameter to show the site it'll go to.
import PySimpleGUI as sg
import webbrowser
layout = [
[
sg.Text("My Awesome Link", text_color="#0000EE", font=(None, 20), enable_events=True, key="-LINK-")
]
]
window = sg.Window("Hyperlink", layout, finalize=True)
window["-LINK-"].set_cursor("hand2")
window["-LINK-"].Widget.bind("<Enter>", lambda _: window["-LINK-"].update(font=(None, 20, "underline")))
window["-LINK-"].Widget.bind("<Leave>", lambda _: window["-LINK-"].update(font=(None, 20)))
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event == "-LINK-":
webbrowser.open("https://www.pysimplegui.org/")
window.close()

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 empty out Listbox in PySimpleGUI

I am brand new with PySimpleGUI but it's turning out to be really easy as advertised. After just a couple of hours I already have a halfway-working application.
I'm using a Listbox to display several rows of items read in from a disk file. When I click my Show Connections button it reads the file and displays the items like I want. But if I click the button again, it reads the file again and now I have two copies in the box. I want to empty out the listbox before it gets updated from the next disk file read so that it always shows just exactly what is in the file. I have tried Update and set_value but can't seem to get anything to work.
layout_showdata = [
[
sg.Text('Time',size=(10,1)),
sg.Text('Destination',size=(14,1)),
sg.Text('Source',size=(14,1))],
[sg.Listbox(size=(38,10),values=[''],key='_display_')]
]
.
.
.
if event == 'Show Connections':
print('Show Connections')
window['panel_show'].update(visible=True)
window['panel_edit'].update(visible=False)
window['panel_entry'].update(visible=False)
window['_display_'].set_value([]) ***#<==This should do it I thought***
with open('connections.txt','r') as cfile:
schedule=csv.reader(cfile,dialect="pipes")
for row in schedule:
items.append(row[0]+':'+row[1]+':'+row[2]+' '+row[3]+' '+row[4])
print(items[itemnum])
itemnum+=1
window.FindElement('_display_').Update(items)
I'm sure I'm missing something simple and would appreciate whatever help I can get.
[EDIT] Added some running code to illustrate what happens. Instead of the listbox clearing when the button is pushed, it just reads the file again and adds to what is already there:
import PySimpleGUI as sg
import csv
items = []
itemnum = 0
csv.register_dialect('pipes', delimiter='|')
file = [
'01|23|45|12345678|87654321',
'04|35|23|24680864|08642468',
'01|23|45|12345678|87654321',
'04|35|23|24680864|08642468',
'01|23|45|12345678|87654321',
'23|23|23|12341234|43214321'
]
layout_showdata = [
[
sg.Text('Time',size=(10,1)),
sg.Text('Destination',size=(14,1)),
sg.Text('Source',size=(14,1))],
[sg.Listbox(size=(38,10),values=[''],key='_display_')],
[sg.Button('Show Connections')]
]
window = sg.Window('XY Scheduler', layout_showdata)
while True:
event, values = window.Read(timeout=1)
if event in (None, 'Quit'):
break
#Show Existing Connections
if event == 'Show Connections':
print('Show Connections')
window['_display_'].update([])
schedule=csv.reader(file,dialect="pipes")
for row in schedule:
items.append(row[0]+':'+row[1]+':'+row[2]+' '+row[3]+' '+row[4])
print(items[itemnum])
itemnum+=1
window.FindElement('_display_').Update(items)
You want the update method if you want to change the values. You'll find it has a values parameter. The call reference document or docstrings will help you quite a bit, but the general rule of thumb to use is that update is the method to change something about an element.
You had already been calling it for the other elements. You just needed to call it for the listbox.
window['_display_'].update([])
The set_values call has this description in the docs:
"Set listbox highlighted choices"
It sets what has been selected, not the choices you have to select.
[EDIT]
Here is a full example of how to remove all entries in a listbox. Maybe I'm misunderstanding the question.
import PySimpleGUI as sg
def main():
layout = [ [sg.Text('My Window')],
[sg.Listbox([1,2,3,4,5], size=(5,3), k='-LB-')],
[sg.Button('Go'), sg.Button('Exit')] ]
window = sg.Window('Window Title', layout)
while True: # Event Loop
event, values = window.read()
print(event, values)
if event == sg.WIN_CLOSED or event == 'Exit':
break
if event == 'Go':
window['-LB-'].update([])
window.close()
if __name__ == '__main__':
main()

How to update a GUI window in 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.

Categories