WXPYTHON tricky - multithreading

I have a textctrl box - I am reading data from it continuously every 1 second. I have a button which have to be enabled when the value drops below 50. I have a piece of code, which is making the GUI irresponsive. In the code I am presenting here, I am waiting until the value is less than 50. Then Enabling the start button
while self.pressure_text_control.GetValue()>50:
self.start.Disable()
time.sleep(1)
self.start.Enable()
This whole code is inside an another button event.
def OnDone(self, event):
self.WriteToControllerButton([0x04])
self.status_text.SetLabel('PRESSURE CALIBRATION DONE \n DUMP PRESSURE')
self.led1.SetBackgroundColour('GREY')
self.done.Disable()
self.add_pressure.Disable()
while self.pressure_text_control.GetValue()>50:
self.start.Disable()
time.sleep(1)
self.start.Enable()
The value in pressure_text_control is getting updated every 1 second.

Set up a wx.timer to do the work for you, this will free up the GUI main-loop i.e. make it responsive.
The wx.Timer class allows you to execute code at specified intervals.
https://wxpython.org/Phoenix/docs/html/wx.Timer.html
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.timer.Start(1000)
def OnTimer(self, event):
"Check the value of self.pressure_text_control here and do whatever"
Don't forget to self.timer.Stop() before closing the program.

Related

How to make a time limit for answer in python game with GUI?

I am making a game that requires from a user to type a proper phylum, based on a given photo of a species. I provide a GUI for my game. Currently, I am struggling to limit the time that user have to give his answer. I tried with Timer (threading), but idk how to cancel a thread when user doesn't exceed a maximum time.
Here is my code for the button that is used to confirm answer:
time_answer = 10
def confirm_action():
global img_label, answer, n
from_answer = answer.get().lower()
if n == len(files) - 1:
answer_check(from_answer, round_up(end - start, 2))
confirm.configure(text="Done")
answer.configure(state=DISABLED)
n += 1
elif n == len(files):
root.quit()
else:
answer_check(from_answer, "(Press shift)")
answer.bind("<Shift_L>", insert_text)
n += 1
img_f = ImageTk.PhotoImage(Image.open(f"program_ib_zdjecia/faces/{directories[n]}/{files[n]}"))
img_label.configure(image=img_f)
img_label.image = img_f
t = Timer(time_answer, confirm_action)
t.start()
confirm = Button(root, text="Confirm", command=confirm_action)
As #Bryan Oakley said, you can use tkinter's after() method to set a timer that disables user input, but only if the user doesn't submit input within the certain amount of time.
I'll have the explanations here, and a simple example will be at the bottom.
Setting a timer using after()
First, how to set a timer using after(). It takes two arguments:
The number of milliseconds to wait before calling the function, and
The function to call when it's time.
For example, if you wanted to change a label's text after 1 second, you could do something like this:
root.after(1000, lambda: label.config(text="Done!")
Canceling the timer using after_cancel()
Now, if the user does submit the input within the given amount of time, you'll want some way of cancelling the timer. That's what after_cancel() is for. It takes one argument: the string id of the timer to cancel.
To get the id of a timer, you need to assign the return value of after() to a variable. Like this:
timer = root.after(1000, some_function)
root.after_cancel(timer)
Example
Here's a simple example of a cancel-able timer using a button. The user has 3 seconds to press the button before it becomes disabled, and if they press the button before time is up, the timer gets canceled so that the button never gets disabled.
import tkinter
# Create the window and the button
root = tkinter.Tk()
button = tkinter.Button(root, text="Press me before time runs out!")
button.pack()
# The function that disables the button
def disable_button():
button.config(state="disabled", text="Too late!")
# The timer that disables the button after 3 seconds
timer = root.after(3000, disable_button)
# The function that cancels the timer
def cancel_timer():
root.after_cancel(timer)
# Set the button's command so that it cancels the timer when it's clicked
button.config(command=cancel_timer)
root.mainloop()
If you have any more questions, let me know!

How to make both the GUI and the while loop containing button switch connected through Arduino Uno work Simultaneously

I wrote a code for an app which will directly compare images and give output using Pyside2. The output will be displayed in the GUI as well as in LED connected through Arduino Uno using Pyfirmata. Now, I want my program to be executed in both Virtual mode (through GUI 'Run' button) and Physical mode (through a pushbutton connected in Arduino Uno. When this button is pushed, the Run part of the code will execute).
I tried to do this by programming the while loop (which reads the pushbutton status and execute the code whenever it is clicked) inside the main of Pyside2 code. But, when executed, only the Physical mode (through the pushbutton) is working and I'm unable to use the GUI's Run button. That is, the GUI executes the program when push button is clicked, and then goes to not responding. It stays that way until the pushbutton is clicked again. I'm unable to use the GUI's Run button completely.
I'm new to Python, Pyside and Pyfirmata. Could someone please help me with this?
The code is something like this.
class Ui_xxx(object):
def setupUi(self, xxx):
content
self.retranslateUi(xxx)
QtCore.QMetaObject.connectSlotsByName(xxx)
self.button1.clicked.connect(self.button1)
self.button2.clicked.connect(self.button2)
self.button3.clicked.connect(self.button3)
self.button4.clicked.connect(self.button4)
self.say_run.clicked.connect(self.say_run)
def retranslateUi(self, xxx):
content
def button1(self):
content
def button2(self):
content
def button3(self):
content
self.say_run()
def button4(self):
content
def say_run(self):
QtCore.QCoreApplication.processEvents()
content
if os.listdir(dir1) == []:
content
else:
content
#goto.with_goto
def main():
global board
board = pyfirmata.Arduino('COM3')
import sys
app = QtWidgets.QApplication(sys.argv)
xxx = QtWidgets.QMainWindow()
ui = Ui_xxx()
ui.setupUi(xxx)
xxx.show()
time.sleep(5)
it = pyfirmata.util.Iterator(board)
it.start()
digital_input = board.get_pin('d:5:i')
def ws():
while True:
sw = digital_input.read()
if sw is True:
board.digital[12].write(1)
time.sleep(5)
board.digital[12].write(0)
time.sleep(5)
ui.say_run()
QtCore.QCoreApplication.processEvents()
break
label .loop101
ws()
QtCore.QCoreApplication.processEvents()
goto .loop101
board.exit()
sys.exit(app.exec_())

popup in tkinter with pygame that doest go away unless closed/pressed ok

I am trying to get a popup for my pygame screen using tkinter, but i just want a simple messagebox that i pass in the message, and message type (like: "error"). What i don't know how to do is make it so that they can't avoid not answering it, if they click somewhere else it will not let the user do anything till they answer it, not even go to desktop sort of thing.
what i have so far:
def popUp(self, message, messagetype='Error'):
#Tk().wm_withdraw() #to hide the main window
messagebox.showinfo(messagetype, message)
For this to work your games's mainloop must be in a function (let's call it play). Let's say you have very simple code.
When a condition is met you can access the new function popUp. Which can have your tkinter window. When the button is pressed you can have its command as ...command=play) if the player wants to restart. As the popUp function is not inside the mainloop the game will be unresponsive. An example:
def play():
while carryOn:
for event in pygame.event.get():
if event.type==pygame.QUIT:
carryOn=False
#Checking if a key is pressed and then responding e=with function in the sprite class
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
player.moveLeft(5)
if keys[pygame.K_RIGHT]:
player.moveRight(5)
if keys[pygame.K_UP]:
player.moveUp(5)
if keys[pygame.K_DOWN]:
player.moveDown(5)
if keys[pygame.K_a]:
player.moveLeft(5)
if keys[pygame.K_d]:
player.moveRight(5)
if keys[pygame.K_w]:
player.moveUp(5)
if keys[pygame.K_s]:
player.moveDown(5)
#yourcondition
# if ... :
# carryOn = False
# popUp()
#Game Logic
all_sprites_list.update()
#Setting the background
screen.fill(GREEN)
#Now let's draw all the sprites
all_sprites_list.draw(screen)
#Refresh Screen
pygame.display.flip()
#Number of frames per second
clock.tick(60)
This is not full code - missing sprite class and code before that. You could then have your tkinter window (after importing tkinter) like:
global window #so it can be destroyed in the other function
window = Tk()
#message = ...
#message.grid ...
button = Button(window, text="restart", width=5, command=play)
button.grid # where you want it
The game will be frozen until the user presses restart (you may also want a window .destroy() in your code somewhere. You can adapt this to pretty much whatever you want but it is no where close to complete code. I hope this was useful.

Stopping, restarting and changing variables in a thread from the main program (Python 3.5)

I'm very new to threading am and still trying to get my head around how to code most of it. I am trying to make what is effectively a text editor-type input box and so, like every text editor I know, I need a cursor-bar thing to indicate the location at which the text is being typed to. Thus I also want to be able to flicker/blink the cursor, which i thought would also prove good practice for threading.
I have a class cursor that creates a rectangle on the canvas based on the bounding box of my canvas text, but I then need to change it's location as more characters are typed; stop the thread and instantaneously hide the cursor rectangle when the user clicks outside of the input box; and lastly restart the thread/a loop within the thread (once again, sharing a variable) - the idea here being that the cursor blinks 250 times and after then, disappears (though not necessary, I thought it would make a good learning exercise).
So assuming that I have captured the events needed to trigger these, what would be the best way to go about them? I have some code, but I really don't think it will work, and just keeps getting messier. My idea being that the blinking method itself was the thread. Would it be better to make the whole class a thread instead? Please don't feel restricted by the ideas in my code and feel free to improve it. I don't think that the stopping is working correctly because every time I alt+tab out of the window (which i have programmed to disengage from the input box) the Python shell and tkinter GUI stop responding.
from tkinter import *
import threading, time
class Cursor:
def __init__(self, parent, xy):
self.parent = parent
#xy is a tuple of 4 integers based on a text object's .bbox()
coords = [xy[2]] + list(xy[1:])
self.obj = self.parent.create_rectangle(coords)
self.parent.itemconfig(self.obj, state='hidden')
def blink(self):
blinks = 0
while not self.stop blinks <= 250:
self.parent.itemconfig(self.obj, state='normal')
for i in range(8):
time.sleep(0.1)
if self.stop: break
self.parent.itemconfig(self.obj, state='hidden')
time.sleep(0.2)
blinks += 1
self.parent.itemconfig(self.obj, state='hidden')
def startThread(self):
self.stop = False
self.blinking = threading.Thread(target=self.blink, args=[])
self.blinking.start()
def stopThread(self):
self.stop = True
self.blinking.join()
def adjustPos(self, xy):
#I am not overly sure if this will work because of the thread...
coords = [xy[2]] + list(xy[1:])
self.parent.coords(self.obj, coords)
#Below this comment, I have extracted relevant parts of classes to global
#and therefore, it may not be completely syntactically correct nor
#specifically how I initially wrote the code.
def keyPress(e):
text = canvas.itemcget(textObj, text)
if focused:
if '\\x' not in repr(e.char) and len(e.char)>0:
text += e.char
elif e.keysym == 'BackSpace':
text = text[:-1]
canvas.itemconfig(textObj, text=text)
cursor.adjustPos(canvas.bbox(textObj))
def toggle(e):
if cursor.blinking.isAlive(): #<< I'm not sure if that is right?
cursor.stopThread()
else:
cursor.startThread()
if __name__=="__main__":
root = Tk()
canvas = Canvas(root, width=600, height=400, borderwidth=0, hightlightthickness=0)
canvas.pack()
textObj = canvas.create_text(50, 50, text='', anchor=NW)
root.bind('<Key>', keyPress)
cursor = Cursor(canvas, canvas.bbox(textObj))
#Using left-click event to toggle thread start and stop
root.bind('<ButtonPress-1', toggle)
#Using right-click event to somehow restart thread or set blinks=0
#root.bind('<ButtonPress-3', cursor.dosomething_butimnotsurewhat)
root.mainloop()
If there is a better way to do something written above, please also tell me.
Thanks.

Separate user interaction from programmical change: PyQt, QComboBox

I have several QComboBoxes in my PyQt4/Python3 GUI and they are filled with some entries from a database during the initialisation. Initial CurrentIndex is set to 0. There is also a tick box which changes the language of the items in my combo boxes. To preserve current user selection I backup index of the current item and setCurrentIndex to this number after I fill in ComboBox with translated items. All those actions emit currentIndexChanged signal.
Based on the items selected in QComboBoxes some plot is displayed. The idea is to redraw the plot online - as soon as the user changes any of ComboBox current item. And here I have a problem since if I redraw the plot every time signal currentIndexChanged is emited, I redraw it also several times during initialization and if the translation tick box selection was changed.
What is the best way to separate these cases? In principle I need to separate programmical current Index Change from the user, and update the plot only in the later case (during GUI initialisation I can programically call update plot function once). Should I write/rewrite any signal? If so, I never did that before and would welcome any hint or a good example. Use another signal? Or maybe there is a way to temporary block all signals?
There are a few different things you can try.
Firstly, you can make sure you do all your initialization before you connect up the signals.
Secondly, you could use the activated signal, which is only sent whenever the user selects an item. (But note that, unlike currentIndexChanged, this signal is sent even if the index hasn't changed).
Thirdly, you could use blockSignals to temporarily stop any signals being sent while the current index is being changed programmatically.
Here's a script that demonstrates these possibilities:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
self.combo = QtGui.QComboBox()
self.combo.setEditable(True)
self.combo.addItems('One Two Three Four Five'.split())
self.buttonOne = QtGui.QPushButton('Change (Default)', self)
self.buttonOne.clicked.connect(self.handleButtonOne)
self.buttonTwo = QtGui.QPushButton('Change (Blocked)', self)
self.buttonTwo.clicked.connect(self.handleButtonTwo)
layout.addWidget(self.combo)
layout.addWidget(self.buttonOne)
layout.addWidget(self.buttonTwo)
self.changeIndex()
self.combo.activated['QString'].connect(self.handleActivated)
self.combo.currentIndexChanged['QString'].connect(self.handleChanged)
self.changeIndex()
def handleButtonOne(self):
self.changeIndex()
def handleButtonTwo(self):
self.combo.blockSignals(True)
self.changeIndex()
self.combo.blockSignals(False)
def changeIndex(self):
index = self.combo.currentIndex()
if index < self.combo.count() - 1:
self.combo.setCurrentIndex(index + 1)
else:
self.combo.setCurrentIndex(0)
def handleActivated(self, text):
print('handleActivated: %s' % text)
def handleChanged(self, text):
print('handleChanged: %s' % text)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

Resources