So I have this button thats made to start stuff, like this
self.buttontext = StringVar()
self.buttontext.set("Start")
self.button = Button(self.bottomframe, textvariable=self.buttontext, command=self.start)
And when it starts, I want the user to be able to cut it short if they need to by changing the same button to a stop button right after it starts
def start(self):
self.button.config(command=self.stop)
self.buttontext.set("Stop")
permission = True
for ...
if update:
run properly
else:
end prematurely
self.button.config(command = self.start)
self.buttontext.set("Start")
That considers a boolean in every iteration of the loop. The stop function will change update to false so that the loop
def stop(self):
permission = False
However, after I click 'Start' I guess control is no longer in the mainloop and the buttons are unresponsive, despite the button changing its properties for the duration of the runtime. How can I make the button responsive so that it can be interrupted?
call self.update() on every iteration of the loop so that the application can service both screen-refresh events and button-press events (assuming self refers to a tkinter widget)
Related
I was hoping someone could help me with this issue. I'm hoping it's fairly simple to fix, but I have been trying for a while to figure this out. I have trimmed my larger code to this, as I believe the issue here is the crux of the problem.
I have a raspberry pi and an external button. This is on python3 on Linux. I am using GPIOZero for the button. The code below I believe is simple to understand, but basically, I want a function to loop at all times. When I press a button I want another function to run, but only if a variable is a certain number. I describe what I ultimately want to happen in a comment below, but my code is unfinished and simplified just for this problem.
I only want button.when_pressed to work when timer = 0. The problem is, once the code naturally gets into the button.when_pressed function, it never "lets go" of the function again. When I successfully redefine the variable to timer = 1, it still prints button is pressed when I press the button. I don't know why. To me, it seems like it should only work once timer = 0.
Any suggestions? I think I have a misunderstanding of global variables I will plan on researching. I'm not sure if that's the issue. I have also tried using break and continue to try to get it "back on its loop" but that hasn't worked either. Also I want to use button.when_pressed instead of btn.is_pressed, because I want the program to only do something when I'm holding the button down once, and not loop when I'm holding it down. In this case, I want button is pressed to print a single time. If I did btn.is_pressed it would print button is pressed every two seconds, which I dont want.
Thanks for any help. I'm happy to learn.
#!/usr/bin/python3
from gpiozero import Button
from time import sleep
import time
button = Button(4)
timer = 0
def press():
print("Button is pressed")
global timer
timer = 1
def loop():
global timer
timer = 1
while True:
if timer == 0:
button.when_pressed = press
else:
loop()
sleep(2)
If you want to disable the callback you had set to button.when_pressed, you need to do another assignment with button.when_pressed = None. This is listed in the documentation:
when_released:
[...] Set this property to None (the default) to disable the event.
It's not exactly clear what behavior you want from your current code. If you want the button to be active for 2 seconds, then be deactivated indefinitely, you can use:
button.when_pressed = press
sleep(2)
button.when_pressed = None
There's no need for a loop, since you don't want to repeat anything.
If you only want the button to be active for a single button press, that needs to happen within 2 seconds, you could instead call button.wait_for_press(2). I hesitate to write a full block of code for that though, as the docs don't specify how a timeout is signaled (it might be by return value, or via an exception). I don't have a Raspberry Pi so I can't test myself, but you could try it out and see what happens.
Treat your whole piece of code as one "black box", ask yourself, what is the input/output? button press or timer mode? (because I don't quite understand what does timer variable mean in your code)
Your code implies timer mode is the top level input to control the flow,
while True:
if timer == 0:
button.when_pressed = press
else:
loop()
sleep(2)
Is it expected?
If you allow user to press the button at any time, suggest you make button press to be your top level input, change the logic to keep when_pressed callback always on, set flag once triggered, and then check if the button has been pressed and still is_pressed in your while loop.
pressed = False
def play_video_1():
pass
def play_video_2():
pass
def press():
print("Button is pressed")
global pressed
pressed = True
button.when_pressed = press
while True:
if pressed and not_playing_video2:
if is_pressed:
play_video_1()
else:
pressed = False
play_video_2()
else:
play_video_2()
I'm using python to create a class object that controls a Tkinter window. My code looks like this:
class MyGui:
def __init__(self):
self.root = tk.Tk()
self.root.title("My Gui")
return
def makeButton(self, text, on_click_function, should_pack = True):
""" Makes a button widget """
button = tk.Button(self.root, text=text, command=on_click_function)
if should_pack:
button.pack()
return button
def start(self):
self.root.mainloop()
def stop(self):
self.root.destroy()
I want to be able to start an instance of the class, then stop it like so:
a = MyGui()
a.makeButton('STOP', a.stop)
a.start()
... // OTHER CODE HERE EVENTUALLY // ...
Everything works fine and the window is successfully created on start() and it vanishes when the button is clicked and stop() executes. The issue is that the mainloop continues to run. I have to manually kill the program using Ctrl+C and it shows it being killed at self.tk.mainloop(n).
If I just do all my tkinter code in a normal file rather than a class, everything works the same but the mainloop also stops when I call destroy on root, and the program ends. If I put it in a class, however, mainloop doesn't stop in spite of the window being destroyed.
Why? How can I get it to stop mainloop too? (I can stop the whole program by calling sys.exit, but I want to keep executing non-Gui stuff after I call stop via the button-press).
I figured out I can solve the problem if I put self.root.quit() and self.root.destroy() together in the stop() function, but not if I use them separately (using just destroy closes the window but leaves the mainloop running; using just quit cancels the mainloop but leaves the window open and in a weird state that causes crashing when you try to close it manually).
So the class function actually needs to look like this:
def stop(self):
self.root.quit()
self.root.destroy()
I have a TKinter application (Python) that reacts to button presses and keyboard input. However, it is possible that a function that is called after one of these events may take some time to execute. I would like for the GUI to reject all user input while the function is executing. [EDIT: Just want to clarify: all user input refers to any and all buttons/key bindings that exist in the app - I am not just trying to reject the event from a particular button.]
Here is a very simple example to show that TKinter currently seems to add events to a queue that gets executed after each event is handled. Each time the button is pressed, the text in the button has a zero appended to it. However, if the button is pressed when the text reads 00, the function takes a while to execute (I put in a self.after(3000)), and if the button is pressed while that function is executing, then each of those presses will register. So if I press the button 5 times in under the 3 seconds that I have it "stall", then each of those clicks registers, and I end up seeing 0000000 on the button.
import tkinter as tk
# To demonstrate that keystrokes/button clicks do register while a function is executing (and then subsequently fire)
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
# Build the app
self.text = tk.StringVar()
self.pack()
self.create_widgets()
def create_widgets(self):
self.btn = tk.Button(self, textvariable=self.text, command=self.DoSomething, padx=30, pady=30)
self.btn.grid(row=0, column=0)
self.text.set('0')
def DoSomething(self):
# self.RejectEventsWhileThisFuncExecutes()
old_msg = self.text.get()
if old_msg == '00':
self.after(3000)
self.text.set(old_msg + '0')
# self.BeginAcceptingEventsAgain()
root = tk.Tk()
app = Application(master=root)
app.mainloop()
Basically, what I'd like is maybe something I can call in DoSomething() at the beginning and end of the function, say self.RejectEventsWhileThisFuncExecutes() and self.BeginAcceptingEventsAgain(), that will make sure that no clicks are registered while the function is executing.
A common strategy is to create an invisible widget (eg: a 1x1 frame in the corner of the window), and do a grab (ie: call grab_set) on that widget. That causes all events to be funneled to that widget. As long as there are no bindings on that window, the net effect is that the events get ignored. You just need to flush the event queue (call the update method) before removing the grab.
You can remove the command from the button at the start of DoSomething:
self.btn.config(command=lambda: None)
And then reset it at the end. Just make sure to update to process all queued events before rebinding:
self.update()
self.btn.config(command=self.DoSomething)
I've set up a basic Pygame graphical interface, but I'm having trouble with my buttons. I created a Button class, and the function to be executed by the button is determined in the __init__() method. In other words, I input the function when I create an instance of Button via lambda expression. The relevant code of the buttons basically looks like this:
class Button():
def __init__(self, action):
self.command = action
def check(self): # To be called while iterating through pygame.event.get()
if event.type == MOUSEBUTTONUP and self.rect.collidepoint(pygame.mouse.get_pos()):
self.command()
I also created a Window class where each instance is list of the buttons to be seen at a time:
class Window():
def __init__(self, buttons):
self.is_visible = False # This determines whether the buttons in this
# window should be updated, checked, and drawn
self.buttons = list(buttons)
def open(self):
self.is_visible = True
def close(self):
self.is_visible = False
def trans(self, new_window):
self.close()
new_window.open()
Next, I set up two instances of Window, each with a Button to toggle back to the other:
WINDOW_1 = Window([Button(lambda: WINDOW_1.trans(WINDOW_2))])
WINDOW_2 = Window([Button(lambda: WINDOW_2.trans(WINDOW_1))])
And finally:
WINDOW_1.is_visible = True
Here comes the problem.
Each button works exactly how it is supposed to: it closes the open window and opens the closed window. Unfortunately, if I click the mouse in a spot where both buttons overlap (or where they would overlap if they were both visible), the function for WINDOW_2's button is called immediately after the function for WINDOW_1's button is called. Basically, WINDOW_1 -> WINDOW_2 -> WINDOW_1, and it all happens in the same loop.
However, if we start from WINDOW_2, then this happens: WINDOW_2 -> WINDOW_1. It appears that the glitch is only one-way. I just can't figure out what's wrong here, I would really appreciate some help. (Just in case, here's a link to the full code so you can maybe reproduce the problem; I've set the buttons' location so that the bottom half of the first button overlaps the top half of the second: http://pastebin.com/m1zCQLRF). Thank you for reading and thank you in advance for answering!
Consider these lines (from the pastebin):
all_windows=[WINDOW_1,WINDOW_2]
....
for event in pygame.event.get():
for window in all_windows:
if window.is_visible:
for button in window.buttons:
button.update()
button.check()
If WINDOW_1's button is clicked, WINDOW_2.is_visible becomes True.
In the next iteration of the for window in all_windows loop, the check method of WINDOW_2 will be called because its is_visible attribute is now True. Because this is still the same iteration of for event in pygame.event.get(), WINDOW_2.check() sees the same MOUSEBUTTONUP event. The Button objects overlap, so the event causes the windows' visibility to toggle a second time, back to the state where WINDOW_1 is visible, and this is what is drawn.
Incidentally, using event.pos would be more accurate than pygame.mouse.get_pos() in the check() method. The former is the mouse's position at the time the event was posted, while the latter is the current position of the mouse.
Edit
I got the pastebin running with some tweaking and verified that what I described above is the problem by applying a quick and dirty fix.
First, I edited Button.check() so it returns a boolean that shows whether or not its check validated:
def check(self):
if event.type==MOUSEBUTTONUP and self.rect.collidepoint(event.pos):
self.command()
return True
return False
Then altered the code shown above to break out of the all_windows loop if check returns True (i.e. if a window is closed).
for window in all_windows:
if window.is_visible:
for button in window.buttons:
button.update()
_break = button.check()
if _break:
break
Now the windows close and open as expected when a button is clicked. Again, that's not a "real" fix just a confirmation of what caused the problem.
I have multiple buttons. When I click one of them, I call a function that iterates over rows of a table (say, changes the background colour of the row). During the execution of this function, in the middle of the iteration, I'd like to "pause" and wait for another button to be clicked in order to resume the execution of the function. During this "pause" I'd like all other buttons (that is, the ones I'm not waiting to be clicked) to be disabled or ignored.
I've looked into QTimer and QThread, but I think that I'm unnecessarily over-complicating things. Looking for suggestions...
First of all you need to call QtGui.qApp.processEvents() method at the end or beginning of the iteration in order to make the gui responsive during the execution of the loop. Then you need to maintain a list of rows (say, self.allRows), in each iteration pop one row from the list change the background color and append it to a second list (say, self.doneRows). Also declare a boolean variable as False (say, self.pause = False) inside __init__ and add a check statement at the beginning of the loop, if the self.pause is True, break the loop. Then you need to add a slot to update the value of self.pause to True.
def pause_execution(self):
self.pause = True
# loop through all the buttons to make them disabled excluding the pause and resume buttons
for btn in self.buttons:
btn.setEnabled(False)
QtGui.qApp.processEvents()
Connect Pause button's clicked signal to this slot. Now the loop will be broken and self.allRows contains only the rows with no background color changed yet. Create another slot to resume the loop again. This time the loop will change the background color of only remaining rows.
def resume(self):
self.stop = False
# loop through all the buttons that were disabled, to make them enabled (if you need to)
for btn in self.buttons:
btn.setEnabled(True)
QtGui.qApp.processEvents()
# call the method which contains the background color changing loop.
I think you are looking for this to execute on each method depending on the mouse button click:
myWidget.setDisabled(true)
Then, you could find all the children of the main window or the root widget whatever that is, this way:
widgets = form.findChildren(QtGui.QWidget)
Then, you could connect your push button click to the desired method as follows:
button.clicked.connect(setDisableWidgets)
So, here is a simple example:
import sys
from PySide.QtCore import *
from PySide.QtGui import *
g_disable = true
g_mainWidget = ...
def setDisableWidgets():
''' This will get more exciting when you have other widgets, too, for sure '''
widgets = mainWidget.findChildren(QtGui.QWidget)
for widget in widgets:
# filter out push button though based objectName for instance
widget.setDisabled(disable)
# Toggle for the next run
disable = not disable
app = QApplication(sys.argv)
button = QPushButton("Click me")
button.clicked.connect(setDisableWidgets)
button.show()
app.exec_()
In your QThread run, you could extend the code that runs that function the way that, for instance is checks against the disable variable after processing each row or even small units if you wish and if that is false, it runs QThread.sleep(100) or so, and then wakes up for a check against disable. If it is false, then it continues the execution, otherwise sleeps back.
Here is the pseudo code:
class MyThread (QThread):
def run():
isEnd = false
while not isEnd:
while disable:
self.msleep(200)
# Process next row
# if we are done, isEnd = true