Check if a Tkinter button remains clicked - python

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()

Related

PYTHON TKINTER > e = Entry() > e.bind('<ENTER>', function)

I am not allowed to add images yet to question posts.
Question below:
My app currently uses a window that is coded in a class.
My ultimate goal is to press enter while entering letters and numbers into an entry widget and press enter, then the function would update text that correlates to a label in my main window.
Detailed description below:
I cannot figure out how to create and entry and then bind the enter key so that when I run my app, I can click in the entry, type a value and press enter.
I see plenty of button references and I can get the button to work, but I am trying to learn how to do things and do not want to rely on buttons in this instance.
I saw in some other posts that if you call .get with an entry object, that the python code will just execute it and move on. I tested with a print statement in the function I want to call upon pressing enter, and the print statement appeared in the terminal before I typed anything in the entry widget. I then tried to type and press enter, and nothing would occur.
Should I abandon binding the ENTER key and stick with buttons in tkinter as a rule, or is there a proper way to do this? In my code example, you will see up_R is the function I am trying to execute when pressing Enter. If I use up_R(), it executes immediately. If I use up_R, then I get a TCL Error.
Specific Partial code located below:
def up_R():
print('Makes it here')
self.R.update_disp(self.e.get())
self.e.bind('<ENTER>',up_R)
The full code is below if required for assistance:
#NOAA SPACE WEATHER CONDITIONS
from tkinter import *
class window:
def __init__(self):
#main window
self.window = Tk()
self.window.title('NOAA SPACE WEATHER CONDITIONS')
self.window.geometry('800x600')
#window organization
self.window.grid_rowconfigure(0, weight = 1)
self.window.grid_rowconfigure(1, weight = 1)
self.window.grid_columnconfigure(0, weight = 1)
self.window.grid_columnconfigure(1, weight = 1)
#temp entry frame
self.e = Entry(self.window)
self.e.grid(row = 1, column = 0, sticky=N)
self.e.insert(END, 'R entry')
#init class R
self.R = R()
#init class S
self.S = S()
#init class g
self.G = G()
#frame for RSG
self.frame = Frame(self.window)
self.frame.grid(row = 0, column = 0, columnspan = 2, padx=10, pady=10)
#disp class R
self.rf = Frame(self.frame, highlightbackground='black', highlightcolor='black', highlightthickness=1)
self.rf.pack(side = LEFT)
self.rl = Label(self.rf, text = self.R.dkey, bg='#caf57a')
self.rl.pack(side=TOP)
self.rl_lower = Label(self.rf, text= self.R.tile_text, bg='#caf57a')
self.rl.pack(side=BOTTOM)
#Value update methods
# self.R.update_disp(self.e.get())
# #action
def up_R():
print('Makes it here')
self.R.update_disp(self.e.get())
self.e.bind('<ENTER>',up_R())
#main window call - goes at end of class
self.window.mainloop()
class R:
def __init__(self):
d = {'R':'None','R1':'Minor','R2':'Moderate','R3':'Strong','R4':'Severe','R5':'Extreme'}
self.dkey = 'R'
self.tile_text = d[self.dkey]
print(d[self.dkey])
def update_disp(self, dkey):
self.dkey = dkey
class S:
d = {'S1':'Minor','S2':'Moderate','S3':'Strong','S4':'Severe','S5':'Extreme'}
pass
class G:
d = {'G1':'Minor','G2':'Moderate','G3':'Strong','G4':'Severe','G5':'Extreme'}
pass
t = window()
The ENTER should be changed with Return, and the function should accept an event
Also, don't forget in a 'class' to use self in the method and self.method to call it.
def up_R(self, event):
print('Makes it here')
self.R.update_disp(self.e.get())
self.rl.config(text=self.R.dkey)
self.e.bind('<Return>', self.up_R)

tkinter change cascade label background color

I'm trying to create a class that allows you to have a MenuBar that can be personalized in windows. It's nothing special since I'm new to coding. I'm trying different ways to change the background of the label behind the cascade buttons but I can only change the buttons ones.
from tkinter import *
TESTING = True
root = Tk()
root.attributes('-fullscreen',True)
class menubar():
"""You can use this to create a menu bar
There are some functions:
ButtonCascadeAdd: Lets you create a cascade button to which you will be able to add more buttons with
AddButtons function
AddButtons: just adds buttons to the cascades. you can chose cascades changing the input number from
0 to 9
Soon will be added color switch and others althought you can already do those things by yourself
since every btns[] object is a tkinter.Menubutton()"""
global labelframe
global contx
global btns
contx = 0
labelframe = LabelFrame(root)
btns = [Menubutton(labelframe) for i in range(10)]
def place(self, width = 300,height = 30,rely=0):
labelframe.place(width=width, height=height, rely=rely)
def ButtonCascadeAdd(self,text = "btns[",tearoff = 0):
"""Adds a cascade button.
You can pass in text to change the button title default is the name of the button in the code
so maybe it will prove useful to leave it like that for develop purpose.
You cant add a command to that from here.
If you need a button with a command and not a cascade look aat the FunctionButtonAdd function"""
global contx
if text == "btns[":
text = text+str(contx)+"]"
if contx == 10:
print("Max number of buttons exceeded the program will be stopped")
exit()
b = btns[contx]
b.config(text = text)
b.pack(side=LEFT)
b.menu = Menu(b, tearoff=tearoff)
b["menu"] = b.menu
labelframe.update()
contx += 1
print(contx)
def maincolor(self,bg = "light green",fg = "black",activebg = "green",activefg = "dark blue"):
global contx
for x in range(contx):
btns[x].config(bg=bg, fg=fg, activebackground=activebg, activeforeground=activefg)
btns[x].menu.config(bg = "black",fg = "white")
labelframe.config(bg=bg)
def doNothing():
print("Doing Nothing ^^")
if TESTING == True:
m = menubar()
m.place(width = 1980,height = 20)
m.ButtonCascadeAdd()
m.ButtonCascadeAdd()
btns[0].menu.add_command(label="test")
#print(dir(btns[0].menu))
m.maincolor()
root.mainloop()

countdown timer overlay in python with keypress Tkinter

Hello I am trying to make a simple script which on key press x starts a timer of 45 sec after that when it reaches 10 sec color text changes to red and when countdown comes 0 I want to destroy the timer gui but not the program, and when i press x again program starts doing stuff again and repeats the process
So far I managed this I tried all day I also tried adding on keypress but it become so complicated and didn't worked so I am asking help here
from tkinter import *
root = Tk()
root.geometry("112x55")
root.overrideredirect(True)
root.lift()
root.wm_attributes("-topmost", True)
root.wm_attributes("-disabled", True)
root.wm_attributes("-transparentcolor", "white")
root.resizable(0, 0)
seconds = 45
def timer():
global seconds
if seconds > 0:
seconds = seconds - 1
mins = seconds // 60
m = str(mins)
if mins < 10:
m = '0' + str(mins)
se = seconds - (mins * 60)
s = str(se)
if se < 10:
s = '0' + str(se)
time.set(m + ':' + s)
timer_display.config(textvariable=time)
# call this function again in 1,000 milliseconds
root.after(1000, timer)
elif seconds == 0:
seconds.delete("1.0","end")
frames = Frame(root, width=500, height=500)
frames.pack()
time = StringVar()
timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'))
timer_display.place(x=0, y=0)
timer() # start the timer
root.mainloop()
As you used wm_attributes('-transparentcolor', 'white') (but you never set the background color of root and the timer_display to white, so it don't have effect) and overrideredirect(True), that means you want the window totally transparent and borderless. However this has side effect that you may never get the window focus back after the window loses focus.
Also you used wm_attributes('-disabled', True), then you can't have any key press event triggered. It should not be used.
Suggest to use wm_attributes('-alpha', 0.01) to simulate the transparent effect without the above issues.
Below is an example:
from tkinter import *
root = Tk()
root.geometry("+0+0")
root.overrideredirect(True)
root.wm_attributes("-topmost", True)
root.wm_attributes("-alpha", 0.01)
root.resizable(0, 0)
seconds = 45
def countdown(seconds):
if seconds > 0:
mins, secs = divmod(seconds, 60)
timer_display.config(text="{:02d}:{:02d}".format(mins, secs),
fg='red' if seconds <= 10 else 'white')
root.after(1000, countdown, seconds-1)
else:
root.wm_attributes('-alpha', 0.01) # make the window "disappear"
def start_countdown(event):
root.wm_attributes('-alpha', 0.7) # make the window "appear"
countdown(seconds)
timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'), bg='black')
timer_display.pack()
root.bind('x', start_countdown)
root.bind('q', lambda e: root.destroy()) # provide a way to close the window
root.mainloop()
NOTE #1: If the window loses focus, you can still click somewhere near the top-left corner of the screen to resume window focus, although you cannot see the window.
NOTE #2: If you want system-wise key handler, tkinter does not support it. You need to use other module, like pynput.
If you want to destroy the timer GUI, You'll need to make a class like this:
class Timer(Frame):
def __init__(self, master):
super().__init__(master)
# Paste the code you want to run here, make sure you put "self." before it.
# For example:
def clicked():
print('Clicked!')
self.myButton = Button(self, text="Click me!", border=0, width=25, height=1, command=self.clicked)
self.logbtn.grid(columnspan=2)
self.pack()
if seconds == 0:
self.destroy() # by using self.destroy(), you tell it to delete the class.
else:
# You can put whatever you want it to do if it's not 0 here.
tm = Timer(root)
You can bind functions to keystroke events with root.bind(event, callback).
If you are using Linux or Mac, root.overrideredirect(True) will
prevent your application from receiving keystroke events. You can read
more here: Tkinter's overrideredirect prevents certain events in Mac and Linux
Example:
def keydown(e):
print(f"Key pressed: ")
print("Key code:", e.keycode)
print("Key symbol:", e.keysym)
print("Char:", e.char)
def keyup(e):
print(f"Key '{e}' released")
root.bind("<KeyPress>", keydown)
root.bind("<KeyRelease>", keyup)
root.focus_set()
Alternatively, you can also bind to specific keys with <Key-KEYSYM>, e.g. <Key-space> for the spacebar. A list with all keysyms can be found here
Some more events are listed here
Implementation example
Here is an example with a custom CountdownLabel class that is derived from tkinter.Label and automatically binds to the spacebar key event.
app.py
from countdown import CountdownLabel
from tkinter import Frame, StringVar, Tk, Button
root = Tk()
root.geometry("120x60")
root.lift()
root.wm_attributes("-topmost", True)
root.resizable(0, 0)
# Not supported on Linux and MacOS
# root.overrideredirect(True)
# root.wm_attributes("-disabled", True)
# root.wm_attributes("-transparentcolor", "white")
timer_display = CountdownLabel(root, 10, 5)
timer_display.pack(fill="both", expand=True)
timer_display.configure(background="white")
timer_display.configure(font=('Trebuchet MS', 26, 'bold'))
timer_display.focus_set()
root.mainloop()
countdown.py
from tkinter import Label
class CountdownLabel(Label):
# context : A reference to the Label in order to change the text and
# to close it later on
# duration: Total time in seconds
# critical: Length of the last timespan before the countdown finishes
# in seconds
def __init__(self, context, duration, critical):
super().__init__(context)
self.duration = duration
self.critical = critical if duration >= critical else duration
self.update_ui()
self.bound_sequence = "<Key-space>"
self.bound_funcid = self.bind(self.bound_sequence, self.get_handler())
# Returns a function for the event binding that still has access to
# the instance variables
def get_handler(self):
# Gets executed once when the counter starts through handler() and calls
# itself every second from then on to update the GUI
def tick():
self.after(1000, tick)
self.update_ui()
self.duration -= 1
# Gets executed when time left is less than <critical> (default = 10s)
# Sets the font color to red
def change_font_color():
self.configure(foreground="red")
# Destroys itself after the countdown finishes
self.after((self.critical + 1) * 1000, lambda : self.destroy())
def handler(event):
self.unbind(self.bound_sequence, self.bound_funcid)
self.bound_funcid = -1
self.bound_sequence = None
self.after((self.duration - self.critical) * 1000, change_font_color)
tick()
return handler
# Updates the displayed time in the label
def update_ui(self):
mm = self.duration // 60
ss = self.duration % 60
self.config(text="%02d:%02d" % (mm, ss))
def change_binding(self, sequence):
if self.bound_funcid > 0:
self.unbind(self.bound_sequence, self.bound_funcid)
self.bound_sequence = sequence
self.funcid = self.bind(self.bound_sequence, self.get_handler())

Python How to use entry text value in another function using GUI

I am using tkinter GUI. Created GUI, Now at one of my GUI ask Input value and I need to display that Input Value in another function and print that value.
Code is as below:
def EventOnBtnIO():
#some commutation
ForceNumericItem() #Called the function to open the new GUI
request = 'FF'+Value
print request
return
This is my GUI which consists of entry box and buttons
def ForceNumericItem():
global Subtop
Subtop = Toplevel(top)
Subtop.geometry("350x110+500+200")
Subtop.title("Force Numeric Items")
Subtop.grab_set()
frame = Frame(Subtop,width=15, height=200,bd = 1)
frame.pack(fill = 'both',padx=3, pady=3)
# frame.config(relief=RIDGE)
global entry2
global entrytext2
entrytext2 = IntVar()
entry2 = Entry(frame,textvariable=entrytext2, width = 45)
entry2.pack(padx = 5, pady = 5)
entry2.focus_set()
entry2.selection_range(0, END)
topButtonShow = Button(frame, text = 'OK',command = ForceValue,width = 10)
topButtonBack = Button(frame, text = 'Cancel',command = okclose,width = 10)
topButtonShow.pack(side = LEFT,padx=45,pady=5)
topButtonBack.pack(side = RIGHT,padx=45,pady=5)
return
After OK click, it should take the Value and display in my EventOnBtnIO function
def ForceValue():
global Value
Value = int(entrytext2.get())
print Value
Subtop.destroy()
top.deiconify()
return
I think it is related to local or global varibale, but not able to fix it. Getting value now
FF
Expected value
FF + Value
The function in which you create your popup window returns before the user has input a number in it. To fix this, you could use the .wait_window() method:
def EventOnBtnIO():
ForceNumericItem()
top.wait_window(Subtop)
request = 'FF'+str(Value)
print request

Launch command in Tkinter based on selected radio button?

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).

Categories

Resources