Launch command in Tkinter based on selected radio button? - python

I would like to change the function and text of a button based on which radio-button is selected. Right now what's happening is that both commands are run at the same time as soon as the application is launched rather than it being based upon which radio-button is selected.
import tkinter as tk
import time
## Time variables for the Pomodoro
pomo = 60 * 25 ## 60 seconds times number of minutes
btime = 60 * 5 ## 60
class ExampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.label = tk.Label(self, text="25:00", width=10, font="Helvetica 20")
self.label.pack()
self.remaining = 0
self.button = tk.Button(self)
self.button.pack()
pomocommand = self.button.configure(text="Pomodoro", state=tk.NORMAL, command= lambda: self.pomodoro(pomo)) #Switch back to the pomodoro timer
breakcommand = self.button.configure(text="Break", state=tk.NORMAL, command= lambda: self.breaktime(btime)) #Switch to the break timer
countercommand = self.button.configure(text="Counter", state=tk.NORMAL, command= print('cheese'))
self.radvar = tk.IntVar()
self.radvar.set('1')
self.radio = tk.Radiobutton(self, text="Pomodoro", variable = self.radvar, value=1, indicatoron=0, command = pomocommand)
self.radio.pack(anchor=tk.W)
self.radio = tk.Radiobutton(self, text="Counter", variable = self.radvar, value=2, indicatoron=0, command = countercommand)
self.radio.pack(side=tk.LEFT)
def pomodoro(self, remaining = None):
self.button.configure(state=tk.DISABLED)
if remaining is not None:
self.remaining = remaining
if self.remaining <= 0:
self.label.configure(text="Time's up!")
breakcommand
else:
self.label.configure(text= time.strftime('%M:%S', time.gmtime(self.remaining))) #Integer to 'Minutes' and 'Seconds'
self.remaining = self.remaining - 1
self.after(1000, self.pomodoro)
def breaktime(self, remaining = None):
self.button.configure(state=tk.DISABLED)
if remaining is not None:
self.remaining = remaining
if self.remaining <= 0:
self.label.configure(text="Time's up!")
pomocommand
else:
self.label.configure(text= time.strftime('%M:%S', time.gmtime(self.remaining))) #Integer to 'Minutes' and 'Seconds'
self.remaining = self.remaining - 1
self.after(1000, self.breaktime)
if __name__ == "__main__":
app = ExampleApp()
app.mainloop()

What you are doing is calling the function self.button.configure(...) instead of passing the function itself. Here is a small example:
def test(a):
return a+4
callback_1 = test(2) # callback_1 will be (2+4) = 6, because you called the function. Notice the parens ()
callback_2 = test # callback_2 will be the function test, you did not call it
# So, you can do:
callback_2(some_value) # will return (some_value + 4), here you called it
Basically what is happening is that you are using the first example, so the function is called in __init__, and what it is supposed to do is done there. What you want is something similar to the second, because Tkinter wants something to call (a function or any callable). So pomocommand and break should be functions not the result of calling a function. (You can try to do print pomocommand and see that it is not a function.)
The solution is either to create a new function, or use lambdas. Here:
def pomocommand(self):
self.button.configure(text="Pomodoro", state=tk.NORMAL, command= lambda: self.pomodoro(pomo)) #Switch back to the pomodoro timer
# and in your __init__ method:
def __init__(self):
# ...
self.radio = tk.Radiobutton(self, text="Pomodoro", variable = self.radvar, value=1, indicatoron=0, command = self.pomocommand)
# ...
And you do the same for the other button. Using a lambda here is not recommended because it is a long statement (the self.button.configure part) so your code will not be readable. But I see you are using lambdas, so you might get the idea.
As a side note, be careful when using lambda like you do. Here pomo is a global variable, but if it is not, you might get into trouble. See this question. (Right now it is alright, so you can just ignore this :D).

Related

Check if a Tkinter button remains clicked

I want to make 2 buttons (+ and -) that can change the volume. But if I want to increase the volume more, I'd like to can keep the button pressed. Any ideas how to check if a button remains pressed by the user?
def volumeUp():
currentVolume = m.getvolume()[0]
volumeSlider.set(currentVolume + 5)
def volumeDown():
currentVolume = m.getvolume()[0]
volumeSlider.set(currentVolume - 5)
volumeDownButton = Button(win, text = "-", font = myFont, command = volumeDown, height = 1, width = 1)
volumeDownButton.pack(side = BOTTOM)
volumeUpButton = Button(win, text = "+", font = myFont, command = volumeUp, height = 1, width = 1)
volumeUpButton.pack(side = BOTTOM)
What you can do is make the Button press fire a function that alters the volume and then schedules itself to be run again after a certain amount of time (e.g. 100 ms). Then when the Button is released, you can cancel the scheduled repeat of the function that alters the volume to break the loop.
I've altered your code a bit to make an example:
from tkinter import *
win = Tk()
def volumeUpPress(e=None):
global up_after
currentVolume = volumeSlider.get()
volumeSlider.set(currentVolume + 2)
up_after = win.after(100, volumeUpPress)
def volumeUpRelease(e=None):
global up_after
win.after_cancel(up_after)
def volumeDownPress(e=None):
global down_after
currentVolume = volumeSlider.get()
volumeSlider.set(currentVolume - 2)
down_after = win.after(100, volumeDownPress)
def volumeDownRelease(e=None):
global down_after
win.after_cancel(down_after)
volumeSlider = Scale(win, from_=0, to=100, orient=HORIZONTAL)
volumeSlider.pack()
volumeDownButton = Button(win, text = "-", height = 1, width = 1)
volumeDownButton.pack(side = BOTTOM)
volumeDownButton.bind("<Button-1>", volumeDownPress)
volumeDownButton.bind("<ButtonRelease-1>", volumeDownRelease)
volumeUpButton = Button(win, text = "+", height = 1, width = 1)
volumeUpButton.pack(side = BOTTOM)
volumeUpButton.bind("<Button-1>", volumeUpPress)
volumeUpButton.bind("<ButtonRelease-1>", volumeUpRelease)
win.mainloop()
Things to note:
I didn't use the Button's command since that doesn't give you the flexibility of knowing when the Button is released. Instead I made two binds, one for when the Button is pressed, one for when it is released.
I use the after method to schedule a new call to the same function after a set number of milliseconds, I save a reference to this scheduled function in a global variabel, to be able to use it again in the release function to cancel it with after_cancel
.bind calls functions with an event object, while after calls a function without arguments, because both call the same function, I made it so that the function can be called both with and without argument (e=None)
An alternative to fhdrsdg's answer that also uses after would be to measure the state value of the Button and detect whether it is currently active, to do this we bind a function to the Button which checks the state and then increments a value if the state is active before using after to call the function again after a short delay:
from tkinter import *
class App():
def __init__(self, root):
self.root = root
self.button = Button(self.root, text="Increment")
self.value = 0
self.button.pack()
self.button.bind("<Button-1>", lambda x: self.root.after(10, self.increment))
def increment(self, *args):
if self.button["state"] == "active":
self.value += 1
print(self.value)
self.root.after(10, self.increment)
root = Tk()
App(root)
root.mainloop()

How to manage Python Tkinter app multithreading

I am developing a Tkinter app with Python. I have a two background operations and one operation with user demand. Here is my sample code:
from threading import Thread
import tkinter as tk
import time
class Controller(object):
def __init__(self, master):
self.master = master
self.btn1 = tk.Button(self.master, text="Start Recording", width=16, height=5, command=lambda: self.start_background_opt())
self.btn1.grid(row=2, column=0)
self.btn3 = tk.Button(self.master, text="Fly", width=16, height=5, command=lambda: self.fly_button())
self.btn3.grid(row=3, column=0)
self.entry = tk.Entry(self.master)
self.entry.grid(row=4, column=0)
self.connect_button_clicked = False
self.thread1 = None
self.thread2 = None
self.thread3 = None
self.flight_completed = False
def background_opt1(self):
while True:
if self.connect_button_clicked:
print("Battery Fetching")
else:
return
def background_opt2(self):
while True:
if self.connect_button_clicked:
print("Signal Fetching")
else:
return
def start_background_opt(self):
if not self.connect_button_clicked:
self.connect_button_clicked = True
self.thread1 = Thread(target=self.background_opt1).start()
self.thread2 = Thread(target=self.background_opt2).start()
else:
self.connect_button_clicked = False
self.thread1 = None
self.thread2 = None
def flight_operation_controller(self):
if self.flight_completed:
self.thread3 = None
def fly_button(self):
self.flight_completed = False
self.thread3 = Thread(target=self.static_sim()).start()
def static_sim(self):
while True:
if not self.flight_completed:
for _ in range(100):
print('Simulating')
time.sleep(0.1)
print("Simulation completed")
self.flight_completed = True
else:
return
if __name__ == '__main__':
root = tk.Tk()
# Set the window size
root.geometry("900x600+0+0")
control = Controller(root)
root.mainloop()
So when user click to "start recording", it starts 2 background operations. They should run as a background. Then when user click to "fly" button, fly operation should be executed.
In order to not blocking my main UI, I have put them in seperate threads.
Actually my all operations are working properly. I have put time.sleep
for replicating my fly operation; but when it runs, it blocks my entire, even though it is running in seperate thread.
Could you please tell me why I am seeing this?
Is my interpretation okey regarding the multithreading in Pyhton tkinter?
Best Regards
Take a look at this line of code:
self.thread3 = Thread(target=self.static_sim()).start()
The above code works exactly the same way as this code;
result = self.static_sim()
self.thread3 = Thread(target=result).start()
See the problem? You are calling your function outside of the thread. Because static_sim() has an infinite loop, it never returns.
When you set the target for Thread, it must be a callable. Change the code to this (note the lack of trailing ()):
self.thread3 = Thread(target=self.static_sim).start()

Remove buttons for a few seconds in Python/Tkinter?

I'm writing a program where the user will make a selection based on a target image. I'm trying to get the program to remove the selection buttons and wait 2 seconds after updating the target image before the selection choices are re-presented. The code that I have seems to "disable" the clicked button for 2 seconds, but does not remove either button.
from tkinter import *
import random
root = Tk()
root.geometry("500x500")
def click_b(event):
btn_b.pack_forget()
btn_c.pack_forget()
new_a()
root.update()
root.after(2000, show_btns())
def click_c(event):
btn_b.pack_forget()
btn_c.pack_forget()
new_a()
root.update()
root.after(2000, show_btns())
def new_a():
k = random.randrange(1, 3)
if k == 1:
btn_a.configure(image=a1)
elif k == 2:
btn_a.configure(image=a2)
def show_btns():
btn_b.pack(side=LEFT)
btn_c.pack(side=RIGHT)
a1 = PhotoImage(file="A1.gif")
a2 = PhotoImage(file="A2.gif")
orange = PhotoImage(file="orange_btn.gif")
green = PhotoImage(file="yellowgreen_btn.gif")
btn_a = Button(root, image=a1)
btn_a.pack()
btn_b = Button(root, image=orange)
btn_b.bind('<Button-1>', click_b)
btn_b.pack(side=LEFT)
btn_c = Button(root, image=green)
btn_c.bind('<Button-1>', click_c)
btn_c.pack(side=RIGHT)
root.mainloop()
the issues is in your after() methods.
You need to remove the brackets for the show_btns function call or else tkinter will not run this command properly. If you have a function with no arguments you leave off the () portion.
If you do have arguments then you will need to either provide those arguments in the after statement IE after(2000, some_func, arg1, arg2) or use lambda to create a one off function to do the work like after(2000, lambda: some_func(arg1, arg2)). lambda can be more complicated but this is the basic concept.
change:
after(2000, show_btns())
To:
after(2000, show_btns)
As long as your paths to your images work fine the below code should work as intended.
from tkinter import *
import random
root = Tk()
root.geometry("500x500")
def click_b(event):
btn_b.pack_forget()
btn_c.pack_forget()
new_a()
root.update()
root.after(2000, show_btns)
def click_c(event):
btn_b.pack_forget()
btn_c.pack_forget()
new_a()
root.update()
root.after(2000, show_btns)
def new_a():
k = random.randrange(1, 3)
if k == 1:
btn_a.configure(image=a1)
elif k == 2:
btn_a.configure(image=a2)
def show_btns():
btn_b.pack(side=LEFT)
btn_c.pack(side=RIGHT)
a1 = PhotoImage(file="A1.gif")
a2 = PhotoImage(file="A2.gif")
orange = PhotoImage(file="orange_btn.gif")
green = PhotoImage(file="yellowgreen_btn.gif")
btn_a = Button(root, image=a1)
btn_a.pack()
btn_b = Button(root, image=orange)
btn_b.bind('<Button-1>', click_b)
btn_b.pack(side=LEFT)
btn_c = Button(root, image=green)
btn_c.bind('<Button-1>', click_c)
btn_c.pack(side=RIGHT)
root.mainloop()

How to call back methods in python?

I am making a project in which whenever I stop the stopwatch, it updates the time into a Listbox. It can be updated in the self.m listbox. However it cannot be updated into topscoreslistbox.
In the printFunctionStop function, i call back the Stop method in a class. However i do not know how to call the TopscoresStop in the main function.
The Stop method and TopscoresStop method do the same thing. However the different thing is one is in the class and one is in the main function.
So how do i call the method TopscoresStop in the main function in printFunctionStop anyone knows?
class StopWatch(Frame):
def Stop(self):
""" Stop the stopwatch, ignore if stopped. """
tempo = self._elapsedtime - self.lapmod2
if self._running:
self.after_cancel(self._timer)
self._elapsedtime = time.time() - self._start
self._setTime(self._elapsedtime)
self._running = 0
if len(self.laps) == 3:
return
self.laps.append(self._setLapTime(tempo))
self.m.insert(END, (str(self.one.get()) , "%.2f" % self._elapsedtime))
self.m.yview_moveto(1)
def main():
def Topscores():
toplevel() = Toplevel()
toplevel.title("Top Scores")
topscoreslistbox = Listbox(toplevel, selectmode=EXTENDED, height=3, width=20, font=("Helvetica", 26))
topscoreslistbox.pack(side=RIGHT, fill=BOTH, expand=1, pady=5, padx=2)
first = Label(toplevel, text=("1st"), font=("Algerian", 30))
first.pack(side=TOP)
second = Label(toplevel, text=("2nd"), font=("Algerian", 30))
second.pack(side=TOP)
third = Label(toplevel, text=("3rd"), font=("Algerian", 30))
third.pack(side=TOP)
def TopscoresStop():
tempo = sw._elapsedtime - sw.lapmod2
if sw._running:
sw.after_cancel(sw._timer)
sw._elapsedtime = time.time() - sw._start
sw._setTime(sw._elapsedtime)
sw._running = 0
if len(sw.laps) == 3:
return
sw.laps.append(sw._setLapTime(tempo))
topscoreslistbox.insert(END, (str(sw.one.get()) , "%.2f" % sw._elapsedtime))
topscoreslistbox.yview_moveto(1)
def printFunctionStop(channel):
sw.event_generate("<<Stop>>", when = "tail")
GPIO.add_event_detect(16, GPIO.FALLING, callback = printFunctionStop, bouncetime=300)
sw.bind("<<Stop>>", lambda event:sw.Stop())
Have you tried passing the function as an argument to printfunctionstop?
sorry, can't make comments yet.
E.g.
def a():
print('hi')
def b(func):
func()
def main():
b(a)
main()
The main should print 'hi'

Making a countdown timer with Python and Tkinter?

I want to set a label in Tkinter using my countdown timer function. Right now all it does is set the lable to "10" once 10 is reached and I don't really understand why. Also, even if I have the timer print to a terminal instead the "Time's up!" bit never prints.
import time
import tkinter as tk
class App():
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(text="null")
self.label.pack()
self.countdown()
self.root.mainloop()
# Define a timer.
def countdown(self):
p = 10.00
t = time.time()
n = 0
# Loop while the number of seconds is less than the integer defined in "p"
while n - t < p:
n = time.time()
if n == t + p:
self.label.configure(text="Time's up!")
else:
self.label.configure(text=round(n - t))
app=App()
Tkinter already has an infinite loop running (the event loop), and a way to schedule things to run after a period of time has elapsed (using after). You can take advantage of this by writing a function that calls itself once a second to update the display. You can use a class variable to keep track of the remaining time.
import Tkinter as tk
class ExampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.label = tk.Label(self, text="", width=10)
self.label.pack()
self.remaining = 0
self.countdown(10)
def countdown(self, remaining = None):
if remaining is not None:
self.remaining = remaining
if self.remaining <= 0:
self.label.configure(text="time's up!")
else:
self.label.configure(text="%d" % self.remaining)
self.remaining = self.remaining - 1
self.after(1000, self.countdown)
if __name__ == "__main__":
app = ExampleApp()
app.mainloop()

Categories

Resources