When i click the Submit answer button, additional codes can only be run after that.
import ipywidgets as widgets
Here are a few lines of code that are responsible for the look of the button etc.
selector = widgets.RadioButtons(
options=['Valid', 'Invalid', 'Skip'],
value=None,
description='',
disabled=False
)
button = widgets.Button(
description='Submit answer',
disabled=False,
button_style='',
)
def evaluate(button):
selection = selector.get_interact_value()
if (selection == 'Valid'):
f = open(r"C:\Users\asd\Desktop\asd.txt", "a", encoding='utf-8')
f.write('asd')
f.close()
elif (selection == 'Invalid'):
pass
elif (selection == 'Skip'):
pass
button.on_click(evaluate)
left_box = widgets.VBox([selector, button])
widgets.HBox([left_box])
print('nvm') **#If I click Submit answer button, then run this code**
How can i do that?
a simple trick to do that (without doing an empty while loop that will abuse your cpu) is to put your code in a sleep loop untill the button is pressed.
answer_pressed = False
def evaluate(button):
global answer_pressed
answer_pressed = True
# rest of function here
button.on_click(evaluate)
import time
while answer_pressed == False: # put the interpreter to sleep until the button is pressed.
time.sleep(0.01) # or 0.1 depending on the required resposivity.
Edit: you might want to move the answer_pressed = True to the end of the function instead of the beginning, so you will be sure the function has executed fully before breaking the while loop.
Related
I'm trying to use event callbacks to flip the two colors of whichever button is pressed. I would have liked to use something similar to window[elementkey].update(button_color=flipped_colors), but since I've used a lambda expression to call a function in lieu of my button keys, I am unsure how to accomplish this. Thanks in advance!
import PySimpleGUI as sg
test1={
'button':'1',
'color1':'red',
'color2':'blue',
'key':'-button1-'
}
test2={
'button':'2',
'color1':'yellow',
'color2':'orange',
'key':'-button2-'
}
def button_handler(button_schema):
return button_schema
col_pop = [
[sg.T('I AM A BOX')],
[sg.B(button_text=test1['button'],button_color=(test1['color1'],test1['color2']),key=lambda: button_handler(test1)),
sg.B(button_text=test2['button'],button_color=(test2['color1'],test2['color2']),key=lambda: button_handler(test2)),
sg.B('3'),
]
]
layout_pop = [[sg.Column(col_pop,element_justification='c')]]
window_pop = sg.Window(layout=layout_pop,title='',modal=True,no_titlebar=False,font='Arial 12',resizable=True)
while True:
event, values=window_pop.read()
if event == sg.WIN_CLOSED or event == 'Exit':
window_pop.close()
break
if callable(event):
print(event())
pos1, pos2 = 1,2
colors_tup = list(event().items())
colors_tup[pos1], colors_tup[pos2] = colors_tup[pos2], colors_tup[pos1]
flipped_colors = colors_tup[1][1],colors_tup[2][1]
print(flipped_colors)
** Edit: This allows the button to invert colors each time it's pressed, and thus solves my problem using list reversal:
if event == '-1-':
foo = window[event].ButtonColor[::-1]
window[event].update(button_color = foo)
I want to add another widget, in this case a scale widget, to a menu widget in tkinter.
Right now the only solutions I see are creating a new command and open a new window with the scale widget or creating the scale widget elsewhere. Both don't seem too appealing to me.
Any ideas how to archive this are welcome :)
You cant add a scrollbar to it, but I have coded something similar to this. Its a hacky way and maybe its hard to understand but I can try to explain.
Note as Bryan mentioned in the linked Thread, this seems to be a a Windows only solution.
import tkinter as tk
def my_first_function():
print('first')
def my_second_function():
print('second')
def check_for_scroll(event):
check = root.call(event.widget, "index","active")
if check == 0: #index of button up
scroll_up()
root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms
if check == file_menu.index('end'):
scroll_down()
root.after(100,lambda e=event:check_for_scroll(e))
def scroll_up():
index_of_first_command=1
index_of_last_command=1
label_of_last_command = file_menu.entrycget(index_of_first_command, 'label')
try:
for i, k in enumerate(dict_of_commands):
if k == label_of_last_command:
previous_command_label = list(dict_of_commands)[i-1]
previous_command = list(dict_of_commands.values())[i-1]
if i != 0: #avoid to get the last as first
file_menu.delete(index_of_first_command) #first before pull down button
file_menu.insert_command(index_of_first_command,
label=previous_command_label,
command=previous_command)
except Exception as e:
print(e)
def scroll_down():
index_of_first_command=1
index_of_last_command=1
label_of_last_command = file_menu.entrycget(index_of_last_command, 'label')
try:
for i, k in enumerate(dict_of_commands):
if k == label_of_last_command:
next_command_label = list(dict_of_commands)[i+1]
next_command = list(dict_of_commands.values())[i+1]
file_menu.delete(index_of_first_command) #first after pull up button
file_menu.insert_command(index_of_last_command,
label=next_command_label,
command=next_command)
except:
pass
space = ' '
dict_of_commands = {'first' : my_first_function,
'second': my_second_function}
root = tk.Tk()
menubar = tk.Menu(root)
root.config(menu=menubar)
file_menu = tk.Menu(menubar,tearoff=0)
menubar.add_cascade(label='File', menu=file_menu)
file_menu.bind('<<MenuSelect>>', check_for_scroll)
file_menu.add_command(label=space+u'\u25B2'+space, font=["Arial", 8,'bold'])
file_menu.add_command(label='first', command=my_first_function)
file_menu.add_command(label=space+u'\u25BC'+space, font=["Arial", 8,'bold'])
root.mainloop()
So this code creates your window and a menubar on it as usal:
root = tk.Tk()
menubar = tk.Menu(root)
root.config(menu=menubar)
file_menu = tk.Menu(menubar,tearoff=0)
menubar.add_cascade(label='File', menu=file_menu)
file_menu.add_command(label=space+u'\u25B2'+space, font=["Arial", 8,'bold'])
file_menu.add_command(label='first', command=my_first_function)
file_menu.add_command(label=space+u'\u25BC'+space, font=["Arial", 8,'bold'])
root.mainloop()
Important for you, is this line here:
file_menu.bind('<<MenuSelect>>', check_for_scroll)
This line binds the event MenuSelect and it happens/triggers if your cursor hovers over a command of your menu. To this event I have bound a function called check_for_scroll and it looks like this:
def check_for_scroll(event):
check = root.call(event.widget, "index","active")
if check == 0: #index of button up
scroll_up()
root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms
if check == file_menu.index('end'):
scroll_down()
root.after(100,lambda e=event:check_for_scroll(e))
The line below checks for the index of the command that has triggered the event. With this we check if its button of our interest like the first or last, with the arrows.
check = root.call(event.widget, "index","active")
if its the first for example this code here is executed:
if check == 0: #index of button up
scroll_up()
root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms
it calls/triggers the function scroll_up and uses then the after method of tkinter to retrigger itself, like a loop. The scroll_up function is build like the scroll_down just in the opposite direction. Lets have a closer look:
def scroll_up():
index_of_first_command=1
index_of_last_command=1
label_of_last_command = file_menu.entrycget(index_of_first_command, 'label')
try:
for i, k in enumerate(dict_of_commands):
if k == label_of_last_command:
previous_command_label = list(dict_of_commands)[i-1]
previous_command = list(dict_of_commands.values())[i-1]
if i != 0: #avoid to get the last as first
file_menu.delete(index_of_first_command) #first before pull down button
file_menu.insert_command(index_of_first_command,
label=previous_command_label,
command=previous_command)
except Exception as e:
print(e)
In this function we need to know the first and the last position of commands, because we want to delete one and insert another on that position/index. To achieve this I had created a dictionary that contains the label and the the function of the command item of tkinter like below. (This could be created dynamically, but lets keep it for another question)
dict_of_commands = {'first' : my_first_function,
'second': my_second_function}
So we iterate over this enumerated/indexed dictionary in our function and check if the k/key/label is our item of interest. If true, we get the previous_command by listing the dictionary keys and get the extract the key before by this line:
next_command_label = list(dict_of_commands)[i+1]
similar to the value of the dictionary with this line:
next_command = list(dict_of_commands.values())[i+1]
After all we can delete one and insert one where we like to with this:
file_menu.delete(index_of_first_command) #first after pull up button
file_menu.insert_command(index_of_last_command,
label=next_command_label,
command=next_command)
I know that this code can improved by a lot but it seems hard enough to understand as it is. So dont judge me please.
I hope this solves your question, even if it isnt in the way you wanted to. But this code avoids you from hardly code a own menubar.
If there are questions left on my answer, let me know.
My GUI holds 8 Buttons. Each Button click calls the event handler which then forwards to another function. All of these actions take about 4 seconds. My problem is that this causes the Button to stay in SUNKEN state while the event is handled and also causes the other Buttons to be non-responsive.
What I would like is to release the Button from the SUNKEN state immediately after the click and continue the event handling in the background.
How can this be solved ? Is there a way to make the button released before the event handler finished its job ?
After Editing:
Here is my code:
from tkinter import Tk, Menu, Button
import telnetlib
from time import sleep
On_Color = '#53d411'
Off_Color = '#e78191'
def Power_On_Off (PowerSwitchPort, Action):
'''
Control the Power On Off Switch
'''
on_off = telnetlib.Telnet("10.0.5.9", 2016)
if Action == 'On' or Action =='ON' or Action == 'on':
StringDict = {'1': b"S00D1DDDDDDDE", '2': b"S00DD1DDDDDDE", '3': b"S00DDD1DDDDDE", '4': b"S00DDDD1DDDDE",
'5': b"S00DDDDD1DDDE", '6': b"S00DDDDDD1DDE", '7': b"S00DDDDDDD1DE", '8': b"S00DDDDDDDD1E"}
elif Action == 'Off' or Action =='OFF' or Action == 'off':
StringDict = {'1': b"S00D0DDDDDDDE", '2': b"S00DD0DDDDDDE", '3': b"S00DDD0DDDDDE", '4': b"S00DDDD0DDDDE",
'5': b"S00DDDDD0DDDE", '6': b"S00DDDDDD0DDE", '7': b"S00DDDDDDD0DE", '8': b"S00DDDDDDDD0E"}
PowerSwitchPort = str(PowerSwitchPort)
on_off.read_eager()
on_off.write(b"S00QLE\n")
sleep(4)
on_off.write(StringDict[PowerSwitchPort])
on_off.close()
def OnButtonClick(button_id):
if button_id == 1:
# What to do if power_socket1 was clicked
Power_On_Off('1', 'Off')
elif button_id == 2:
# What to do if power_socket2 was clicked
Power_On_Off('1', 'On')
def main ():
root = Tk()
root.title("Power Supply Control") #handling the application's Window title
root.iconbitmap(r'c:\Users\alpha_2.PL\Desktop\Power.ico') # Handling the application icon
power_socket1 = Button(root, text = 'Socket 1 Off', command=lambda: OnButtonClick(1), bg = On_Color)
power_socket1.pack()
power_socket2 = Button(root, text = 'Socket 1 On', command=lambda: OnButtonClick(2), bg = On_Color)
power_socket2.pack()
'''
Menu Bar
'''
menubar = Menu(root)
file = Menu(menubar, tearoff = 0) # tearoff = 0 is required in order to cancel the dashed line in the menu
file.add_command(label='Settings')
menubar.add_cascade(label='Options', menu = file)
root.config(menu=menubar)
root.mainloop()
if __name__ == '__main__':
main()
As can be seen, I create 2 Buttons one Turns a switch On and the other turns it Off. On/Off actions are delayed in about 4 seconds. This is only small part of my Application that I took for example. In my original code I use a Class in order to create the GUI and control it
Trivia
Your problem stems from these lines of code:
on_off.write(b"S00QLE\n")
sleep(4)
especially from sleep. It's very weak pattern, because you're expecting a completion of S00QLE in four seconds. No more and no less!
And while telnet actually works for these four seconds, the GUI sleeps.
Therefore your GUI is in unresponsive state and can't redraw the relief of the button.
The good alternative to sleep is the after - so you can schedule an execution:
# crude and naive fix via closure-function and root.after
def Power_On_Off (PowerSwitchPort, Action):
...
def write_switch_port_and_close():
on_off.write(StringDict[PowerSwitchPort])
on_off.close()
on_off.write(b"S00QLE\n")
# sleep(4)
root.after(4000, write_switch_port_and_close)
...
Solution
To overcome this problem, you can use generic after self-sheduling loop.
In my example I connect to public telnet server, which broadcasts Star Wars Episode IV (not an add!), to simulate a long-running process.
Of course, your execute (write) two commands in telnet, to represent this behaviour we recive a telnet broadcast until the famous "Far away" line is found (first long-run operation (write)). After that, we update the label-counter and then we connect to the broadcast again (second long-run operation (write)).
Try this code:
import tkinter as tk
import telnetlib
class App(tk.Tk):
def __init__(self):
super().__init__()
# store telnet client without opening any host
self.tn_client = telnetlib.Telnet()
# label to count "far aways"
self.far_aways_encountered = tk.Label(text='Far aways counter: 0')
self.far_aways_encountered.pack()
# start button - just an open telnet command ("o towel.blinkenlights.nl 23")
self.button_start = tk.Button(self, text='Start Telnet Star Wars', command=self.start_wars)
self.button_start.pack()
# start button - just an close telnet command ("c")
self.button_stop = tk.Button(self, text='Stop Telnet Star Wars', command=self.close_wars, state='disabled')
self.button_stop.pack()
# simple counter
self.count = 0
def start_wars(self):
# "o towel.blinkenlights.nl 23"
self.tn_client.open('towel.blinkenlights.nl', 23)
# enabling/disabling buttons to prevent mass start/stop
self.button_start.config(state='disabled')
self.button_stop.config(state='normal')
# scheduling
self.after(100, self.check_wars_continiously)
def close_wars(self):
# "c"
self.tn_client.close()
# enabling/disabling buttons to prevent mass start/stop
self.button_start.config(state='normal')
self.button_stop.config(state='disabled')
def check_wars_continiously(self):
try:
# we're expect an end of a long-run proccess with a "A long time ago in a galaxy far far away...." line
response = self.tn_client.expect([b'(?s)A long time ago in a galaxy far,.*?far away'], .01)
except EOFError:
# end of file is found and no text was read
self.close_wars()
except ValueError:
# telnet session was closed by the user
pass
else:
if response[1]:
# The "A long time ago in a galaxy far far away...." line is reached!
self.count += 1
self.far_aways_encountered.config(text='Far aways counter: %d' % self.count)
# restart via close/open commands (overhead)
self.close_wars()
self.start_wars()
else:
if response[2] != b'':
# just debug-print line
print(response[2])
# self-schedule again
self.after(100, self.check_wars_continiously)
app = App()
app.mainloop()
So the answer is: the simplest alternative to your specific sleep commands is combination of two functions: after and expect (or only expect if it's a console-application)!
Links
How do you run your own code alongside Tkinter's event loop?
after
Telnet.expect
Regular expression operations
If you have to handle a computation heavy process in your Gui while allowing the rest to run you can just multi-process:
import multiprocess as mp
def OnButtonClick(bid):
mp.Process(target=Power_On_Off,args=('1',('Off','On')[bid-1])).start()
Answers to comments
mp.Process defines a Process object. Once I .start() it the process will call the function in target, with arguments args. You can see the use here for example.
You can of course separate, and pass each id on with different on off choices. I implemented what you wrote.
No need for a queue here if I just want it to run and do something while I continue clicking my buttons.
Your problem was that the button is not released until action ends. By delegating the action to a different process you can return immediately, thus allowing the button to raise.
Your problem isn't really "button isn't rising fast enough". Rather it's the Gui is stuck until Power_On_Off completes. If I run that function in a different process, then my Gui won't be stuck for any noticeable time.
If you want something in the main process to happen when the other process ends, you may need to keep the Process in some sort of list for future pinging. Otherwise, this is fine.
Just started with Tkinter and posting only the snippet of the code.
label and button are Label and Button class objects respectively.
When the button is pressed for the first time on_button_click_organisation is called which changes the button configuration and now when we click it on_button_click_compute is called, which is expected to perform a time taking action via the main method.
But the GUI gets stuck until the main() ends. How to stop this and make the button active once the excecution returns after computing main() and inactive till then?
def on_button_click_organisation(self):
self.organisation_file = askopenfilename(filetypes=[("*.csv", "CSV")])
print("Organisation File : " + self.organisation_file)
self.label.config(text = "Start Code")
self.button.config(text = "START", command = self.on_button_click_compute)
def on_button_click_compute(self):
self.label.config(text = "Wait..\nResult being computed\n(Might take up to 15 minutes)")
self.button.config(state = DISABLED)
#Time taking task
main(self.filename, self.organisation_file, self.result_folder)
sleep(5) #To mimic time taking main() if main() returns in less time
self.button.config(state = NORMAL)
self.label.config(text = "Done..\nCheck the Result Folder")
self.button.configure(text = "Finish", command=root.quit())
I am trying to figure out how to change a rectangle's color continuously, with a second between each change. Right now I have this simple function which makes a window with a square above a button, that changes the color of the square after every button click:
def junk():
def random_color():
red = int(random.random()*256)
green = int(random.random()*256)
blue = int(random.random()*256)
return '#' + ('{:0>#02X}'*3).format(red,green,blue)
def change_color():
c.itemconfig(r, fill=random_color())
x = Tkinter.Tk()
c = Tkinter.Canvas(master=x)
c['width'] = 400; c['height'] = 400
r = c.create_rectangle(0,0,400,400)
b = Tkinter.Button(master=x, command=change_color)
b['text'] = 'change color'
c.pack(); b.pack(); x.mainloop()
What I want is to be able to click once, and then have the colors change automatically. I know I want to use a CheckButton instead of a Button for this, so that one click will start the loop, and and the next click will stop it.
Also, this is not how I am structuring my "real" code, this is how I am testing from the IDLE shell. Defining the helper functions inside the junk function makes it easy to get at all the relevant code at once, without having the bloat of a full class. So please don't give me comments on style, this is quick and dirty on purpose.
TL;DR I'm not sure how to get a continuous loop running to change the color, while being able to start and stop the loop with a button click.
I figured it out. Before I show my solution, I want to correct a mistaken statement I made above: I don't to use a Checkbutton to make this work. I can make a normal button into a toggle button by changing the 'relief' option of the button. Here is my solution:
def junk():
def color_poll():
global alarm
c.itemconfig(r, fill=random_color())
if keep_going:
alarm = c.after(1000, color_poll)
def change_color():
global keep_going, alarm
if not keep_going:
keep_going = True
b['text']='STOP';b['fg']='red';b['relief']=Tkinter.SUNKEN
color_poll()
else:
keep_going = False; c.after_cancel(alarm); alarm = None
b['text']='GO';b['fg']='green';b['relief']=Tkinter.RAISED
x = Tkinter.Tk()
c = Tkinter.Canvas(master=x)
c['width'] = 400; c['height'] = 400
r = c.create_rectangle(0,0,400,400)
global keep_going, alarm
keep_going = False; alarm = None
b = Tkinter.Button(master=x, command=change_color)
b['text'] = 'GO';b['fg']='green';b['font']='Arial 16';b['relief']=Tkinter.RAISED
c.pack(); b.pack(); x.mainloop()
I'm using the same random_color function, but I moved it out because it out of the junk function because it didn't need to be there.