How to only call functions once if user presses it multiple times - python

I am currently working on a program which has a simply GUI and 3 buttons. One of them is start, which runs my code. I am trying to make it as user friendly as possible, so if they spam click it it only runs once. Currently, if the user presses the button multiple times, then the images update multiple times a second.
Any help would be appreciated.

One of the most common OOP practices is to view everything in your GUI as a component, a class, which handles its own variables, a component state, which you initialize when you first render the component.
For example, if we want to monitor when a button is clicked, we could disable the button afterwards by updating the component state.
class Button:
"""
Button that self monitors how many clicks have been performed
"""
def __init__(self,):
#Initialize state
self.disabled = False
self.button_clicked = 0
def click(self,):
if not self.disabled: #Only perform action if self.disabled == False
self.disabled = True
self.button_clicked += 1
#Do stuff on click
class ButtonContainer:
"""
Container for a group of associated Buttons
"""
def __init__(self,):
self.button1 = Button()
self.button2 = Button()
self.button3 = Button()
class App:
def __init__(self,):
#Add components to your app
self.button_container = ButtonContainer()
The reason we may want to use a container is because we can group buttons together and monitor then from our main App class.
For example, within the App class we can call
print(self.button_container.button1.disabled)
With this we can see component states outside of the actual component.

use an if-else statement
pressed = 0
Then pay attention to the times it was clicked and use
if pressed == 3:
# do this
else:
# do something different

Related

Creating a state machine in python

I have created various simple 2 dimensional games in python before and because they were simple, I did not need to create such a thing. But I now need it due to needing to go back and fourth.
To go forward though, I need some sort of direction...
I am around 200 lines into a new game and I haven't started on the actual game, its currently all dealing with the window, events and state's
### state machine
def run:
#~setup window
# Current state
state = menu()
running = True
while running:
#~event handler
returns = state.update(events)
if returns == "playgame":
state = game()
state.draw(window)
#menu state
class menu:
def __init__(self):
#~define vars for the menu (buttons, etc...)
self.clickables = [] # filled with gui etc..
self.image = image() # image of the menu
def update(self, events):
for event in events: # go through events
for clickable in self.clickables: # go through clickables
if clickable.testClicked(event.pos) != False: # Returns if clicked
return clickable.testClicked(event.pos)
def draw(self, window):
self.image.draw(window)
#game state
class game(menu): # Exactly the same as menu (just used as example)
def __init__(self):
super(menu).__init__()
#gui button
class button: # Extremely shortened for example use
def __init__(self, do): # do is a string
self.whenClickedDo = do
def testClicked(self, mousePos):
if mousePos in self.myRect: # myRect is the area of the button (Example!)
return self.whenClickedDo
return False
This above example was completely rushed but the question I ponder is... What is a better way to achieve this, or is the above an achievable/smart way to do things?
TLDR; A function "run" has a value "state" which can return a value which would be used to change itself into a different state. Is that a reasonable way to make a state machine?
I wrote a simple game engine in C++ and used a screen based system. I'll try and write a simple version in python for you now. The idea is that each part of the game is a different screen. So the main menu, is a screen, there is a gameplay screen (where all the action takes place), there might be an options screen for the user to change the settings etc etc. These are all managed by a screen list, each screen has a position in the list, and you can switch between screens depending on game events. So if the main menu screen is active, when the user clicks the 'play' button, the gameplay screen is now loaded.
I cant think of the top of my head exactly how I'd port it to python but this should give you somewhere to start at least.
So a screen would be a class something like this:
class Screen:
# you can have objects in the screen as variables of the class here
# e.g self.player = None
def onEntry(self):
# here you would init the assets for your screen
# this could be the interface, sprites etc
# e.g self.player = Player()
def onExit(self):
# do any clean up
# maybe save player data if this is a gameplay screen
# e.g self.player.save()
def update(self):
# main loop for this screen
# e.g self.player.update()
Each specific Screen for your game would inherit from the Screen class and implement those functions with custom logic for that screen. Then the screen list would basically just be a list of these custom Screen classes. Your game would just flip between these Screens.

PyQt5 connect Enter key to Widget

Currently I am working with my own custom widget that consists of a QLineEdit and a QPushButton. A user is limited to entering an integer between 0 and 1000 in the text field. Upon clicking the button, the widget's custom clicked signal emits the value in the field.
Here is the code:
class ResizeWidget(QWidget):
clicked = pyqtSignal(int)
def __init__(self):
super().__init__()
#NumField and Button
self.field = QLineEdit()
self.field.setPlaceholderText("Resize Percentage [0,1000]")
self.resizeButton = QPushButton("Resize")
#Layout
self.lay = QHBoxLayout()
self.setLayout(self.lay)
#Add to Widget
self.lay.addWidget(self.field)
self.lay.addWidget(self.resizeButton)
#Field limits
self.field.setMaxLength(4)
self.field.setValidator(QIntValidator(0,1000))
#Connection
self.resizeButton.clicked.connect(self.onClick)
#pyqtSlot()
def onClick(self):
val = int(self.field.text())
self.clicked.emit(val)
Now what I'd like to add to the class is some way of allowing the user to press enter when the blinking cursor | sometimes called a 'caret' is in the text field.
I am able to find documentation on the mouse in general, mouseEvent and mousePressEvent as a method within QWidgets. But I can't find any documentation that refers to the blinking cursor within the text field.
I would like to add some sort of pseudocode like this within init():
if(cursor == inQLineEdit and pressedEnter):
self.onClick()
I know QLineEdit::returnPressed plays a major role in creating the correct function but I only want the enter key to be valid if the user is using the ResizeWidget. Not some other part of my GUI. I would think the enter key isn't binded to only 1 widget in my entire application but I'd be interested to find out.
It was as simple as adding the following line:
self.field.returnPressed.connect(self.onClick)
As long as the caret (blinking cursor) isn't in the text field, pressing the Enter key doesn't cause any reaction from my custom widget.

How to set up and react to multiple display options?

I'm working on a GUI that will eventually run one of several data analyses depending on which the user selects. In this part of the GUI, I have four radio buttons with options, and then a display button. I would like one of four imported functions to run when the user hits display.
It boils down to something like this
import myFunction
class myUi(QtGui.QWidget):
def retranslateUi(self, myUi):
self.option1.clicked.connect(self.option1task)
self.option2.clicked.connect(self.option2task)
self.display.clicked.connect(self.displaytask)
def option1task(self):
#do something
def option2task(self):
#do something
def displaytask(self):
#if option 1 was clicked, run myFunction.option1()
#if option 2 was clicked, run myFunction.option2()
I'm just having trouble making it work. Is there some way to solve it just by passing variables or will I need to use the signal/slot method?
First of all you do not want to react immediately when a radio button is clicked, so you do not need to connect to their clicked signal.
Instead the radio buttons (which are automatically exclusive within the same parent) can be selected by the user and at the moment the display button is clicked, you just read out which of the radio buttons is selected (can be at most one) and do something according to which one is selected.
For four radio buttons you can do that by a if else clause on isChecked() of the buttons.
For larger numbers of button I recommend additionally using a QButtonGroup (documentation) which allowes to assign an integer to each button within the addButton() method and then easily retrieve the integer of the selected button with checkedId(). If no button is selected the return value is -1.
My example (using PySide which is very similar to PyQt):
from PySide import QtGui
def do_something():
id = g.checkedId()
if id == -1:
print('no option selected')
else:
print('selected option {}, read for display'.format(id))
app = QtGui.QApplication([])
w = QtGui.QWidget()
l = QtGui.QVBoxLayout(w)
# three radio buttons
b1 = QtGui.QRadioButton('Option 1')
l.addWidget(b1)
b2 = QtGui.QRadioButton('Option 2')
l.addWidget(b2)
b3 = QtGui.QRadioButton('Option 3')
l.addWidget(b3)
# a button group (mapping from buttons to integers)
g = QtGui.QButtonGroup(w)
g.addButton(b1, 1)
g.addButton(b2, 2)
g.addButton(b3, 3)
# display button
b = QtGui.QPushButton('Display')
b.clicked.connect(do_something)
l.addWidget(b)
w.show()
app.exec_()
And it looks like:

Allowing a button to be pressed when control has been passed away

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)

Python Tkinter use to emulate blinking with interaction of multiple buttons

I am looking for a solution to emulate the behavior of the UI of an electronic component and the user interaction (which should be pushing buttons) with LEDs reporting an internal state of the electronic component.
I am using python and the tKinter module to do so.
My code runs and my GUI window displays correctly. However, when I push several times on buttons the behavior is not as expected.
I have 4 possible state for each LED (OFF, ON, (Blinking) SLOW, (Blinking) FAST).
I have 4 buttons which can have an impact on the state. Each button has an interaction function defined in the widget class I have defined, and each of this function, once called, redefines the internal state of the widget.
In order to control the blinking of the LED, I use a single loop and the self.after( ..) function. This function is the following:
def toggleLeds(self):
for led in [self.ledTxIP, self.ledRxIP, self.ledTxRS, self.ledRxRS, self.ledPower, self.ledRun, self.ledStatus, self.ledConfig]:
if (((led[1] == "SLOW") and (self._FastBlinking == 0)) or (led[1] =="FAST")):
bg = led[0].cget("background")
bg = "green" if bg == "black" else "black"
led[0].configure(background=bg)
elif((led[1] == "OFF") and (self._update == 1)):
led[0].configure(background="black")
self._update = 0
elif (self._update == 1):
led[0].configure(background="green")
self._update = 0
self._FastBlinking = (self._FastBlinking + 1)%2
self.update_idletasks()
self.after(self._FastBlinkTime, self.toggleLeds)
This one is called recursively through the self.after function, and at the end of the interaction function I have defined for each button.
Here is how I have defined a single LED:
self.ledTxIP = [tk.Label(self, width=1, borderwidth=2, relief="groove"),"OFF"]
And here is an example of the button interaction function:
def pushMode(self):
if (re.search("Reset",self.state) == None):
if (self.clickModCnt == 0):
self.state = "Status"
self._stateTimer = int(time.gmtime()[5])
elif (self.clickModCnt == 1):
if(int(time.gmtime()[5]) - self._stateTimer < 3):
self.state = "Config"
else:
self.state = "RunMode"
else:
self.state = "RunMode"
self.clickModCnt = (self.clickModCnt + 1)%3
self._update = 1
self.updateLedState()
If anybody has an advice on this, it would be more than welcome.
I don't know why this didn't jump out at me sooner, but I think the problem is listed in your own question text, referring to the toggleLeds method:
This one is called recursively through the self.after function, and at the end of the interaction function I have defined for each button.
When the program initially runs, I'm assuming that you call toggleLeds somewhere to kick off the initial pattern for the LEDs. That sets up a single recursive loop via the self.after call at the end of the method. However, if you also call that same method every time you click a button to change state, you're setting up a new loop with every button click, and each new loop may or may not be in sync with your initial loop.
There are a couple ways that I can think of to handle this possible conflict. One is to avoid making new calls to toggleLeds, but that way there could be a delay between the button click and the new LED pattern. If you don't mind that delay, that's probably the best solution.
If you want the light/blink pattern to change immediately, you need to interrupt the current loop and start a new one with the new light/blink states. According to the Tkinter reference produced by New Mexico Tech, the after method:
...returns an integer “after identifier” that can be passed to the .after_cancel() method if you want to cancel the callback.
Here's how you could take advantage of that. First make sure that you're storing that identifier when calling the after method:
self.after_id = self.after(self._FastBlinkTime, self.toggleLeds)
Then change your toggleLeds method definition to accept an optional "interrupt" argument, and to cancel the existing after loop if that argument is True:
def toggleLeds(self, interrupt=False):
if interrupt:
self.after_cancel(self.after_id)
# Existing code follows
Finally, pass True to that argument when calling the method after a button has been clicked:
# Existing button processing code here
self.toggleLeds(interrupt=True)
With these changes in place, each button click would cancel the current after cycle and start a new one, preventing more than one cycle from running at once, which should keep the LEDs in sync.

Categories