Martin Special
Martin Special

Reputation: 151

No changes of GUI during event? (Python3.6, PyQt5)

I'm trying to change style of button during action.

When button is pressed, I want to make them blue, then read rfid card and then by the ID change color of button to green or red (in code is just red to make it easier).

The problem is that changing style of button to blue do nothing (green and red works fine). It is waiting to finish 'clicked()' method? How to tell to PyQt5 "Do it now!" ?

Edit: I modified the pseudocode to reproducible Example.

#!/usr/bin/python3
import sys
import time
from PyQt5.QtWidgets import *

class MainScreen(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.button = QPushButton("TEXT")
        self.button.clicked.connect(self.clicked)
        self.changeButtonColor("black")  # set default style
        self.grid = QGridLayout(self)
        self.grid.addWidget(self.button)
        self.show()

    def changeButtonColor(self, color):
        self.button.setStyleSheet("QPushButton{ \
                color: "+color+"; font: bold 18px;}")

    def clicked(self):
        self.changeButtonColor("blue")  # nothing happening (this is my problem!)
        uid = self.readCard()  # reading ID from rfid card
        self.changeButtonColor("red")

    def readCard(self):
        #return rfid.read() # in real case
        time.sleep(2)
        return "12345678"

def main():
    app = QApplication(sys.argv)
    window = MainScreen()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

Upvotes: 1

Views: 95

Answers (2)

oetzi
oetzi

Reputation: 1052

install quamash and qtawesome

#!/usr/bin/python3
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QSize
import asyncio
from quamash import QEventLoop
import qtawesome as qt


class MainScreen(QWidget):
    oop = None

    def __init__(self, _loop):
        try:
            QWidget.__init__(self)
            self.loop = _loop
            self.button = QPushButton("TEXT")
            self.button.clicked.connect(self.synced_click)
            self.button.setStyleSheet("color: black;")
            self.grid = QGridLayout(self)
            self.grid.addWidget(self.button)
            self.show()
        except Exception as e:
            print(e)

    def synced_click(self):
        try:
            asyncio.ensure_future(self.clicked(), loop=self.loop)
        except Exception as e:
            print('error')
            print(e)

    @asyncio.coroutine
    async def clicked(self):
        try:
            spin_icon = qt.icon('fa5s.spinner', color='red', animation=qt.Spin(self.button))
            self.loop.call_soon_threadsafe(self.button.setIconSize, QSize(12, 12))
            self.loop.call_soon_threadsafe(self.button.setIcon, spin_icon)
            self.loop.call_soon_threadsafe(self.button.setText, "progress")
            self.loop.call_soon_threadsafe(self.button.setStyleSheet, "color: red;")
            await asyncio.sleep(3)
            tick_icon = qt.icon('fa5s.check-circle', color='blue')
            self.loop.call_soon_threadsafe(self.button.setStyleSheet, "color:blue;")
            self.loop.call_soon_threadsafe(self.button.setText, "done")
            self.loop.call_soon_threadsafe(self.button.setIcon, tick_icon)
            await asyncio.sleep(1)
        except Exception as e:
            print('error')
            print(e)


if __name__ == "__main__":
    import sys

    loop = QEventLoop(QApplication(sys.argv))
    asyncio.set_event_loop(loop=loop)
    with loop:
        window = MainScreen(loop)
        loop.run_forever()

complete

inprogress

Upvotes: 0

S. Nick
S. Nick

Reputation: 13651

void QTimer::singleShot(int msec, const QObject *receiver, const char *member)

This static function calls a slot after a given time interval.

It is very convenient to use this function because you do not need to bother with a timerEvent or create a local QTimer object.

import sys
import time
from PyQt5.QtWidgets import *
from PyQt5 import QtCore

class MainScreen(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.button = QPushButton("TEXT")
        self.button.clicked.connect(self.clicked)
        self.changeButtonColor("black")             
        self.grid = QGridLayout(self)
        self.grid.addWidget(self.button)
        self.show()

    def changeButtonColor(self, color):
        self.button.setStyleSheet("QPushButton{ \
                color: "+color+"; font: bold 18px;}")

    def clicked(self):
        self.changeButtonColor("blue")  # nothing happening (this is my problem!)
#        time.sleep(2)                  # reading ID from rfid card
#        self.changeButtonColor("red")
        QtCore.QTimer.singleShot(2000, lambda: self.changeButtonColor("red"))             # <---

def main():
    app = QApplication(sys.argv)
    window = MainScreen()
    sys.exit(app.exec_())

enter image description here


Update

import sys
import time
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import *

class Worker(QtCore.QObject):                              # +++

    started  = QtCore.pyqtSignal()
    finished = QtCore.pyqtSignal()
    data     = QtCore.pyqtSignal(str)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.running = False
        self.stop = 5

    @QtCore.pyqtSlot()
    def read_data_from_sensor(self):
        self.started.emit()
        time.sleep(1)                                  # We simulate the blocking process
        while self.running and self.stop:
            dt  = time.strftime("%Y-%m-%d %H:%M:%S")
            self.data.emit(dt)
            time.sleep(1)                              # We simulate the blocking process
            self.stop -= 1
            
        self.finished.emit()


class MainScreen(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.button = QPushButton("TEXT Start")
        self.button.clicked.connect(self.clicked)
        self.changeButtonColor("black") 
        
        self.label_data = QtWidgets.QLabel(self, alignment=QtCore.Qt.AlignCenter)
        self.label_data.setText('Pending')
        
        self.grid = QGridLayout(self)
        self.grid.addWidget(self.label_data)
        self.grid.addWidget(self.button)
        self.show()
### vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv       
        self._worker = Worker()
        self._worker.started.connect(self.on_started)
        self._worker.finished.connect(self.on_finished)
        self._worker.data.connect(self.update_label)

        self._thread = QtCore.QThread(self)
        self._thread.start()
        self._worker.moveToThread(self._thread)      

    @QtCore.pyqtSlot()
    def on_started(self):
        self.label_data.setText("Start to read")
        self.button.setText("TEXT Stop")
        self.button.setEnabled(True) 
        self._worker.stop = 5
        
    @QtCore.pyqtSlot()
    def on_finished(self):
        self.label_data.setText("Pending")
        self.button.setText("TEXT Start")
        self.button.setEnabled(True)
        self.changeButtonColor("red")                      # <---
        self._worker.running = False
        self._worker.stop = 5
        
    @QtCore.pyqtSlot(str)
    def update_label(self, data):
        self.label_data.setText(data)
### ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        

    def changeButtonColor(self, color):
        self.button.setStyleSheet("QPushButton{ \
                color: "+color+"; font: bold 18px;}")

    def clicked(self):
        self.changeButtonColor("blue")  # nothing happening (this is my problem!)
#        time.sleep(2)                  # reading ID from rfid card
#        self.changeButtonColor("red")
#        QtCore.QTimer.singleShot(2000, lambda: self.changeButtonColor("red"))             # <---

### vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv       
        if self._worker.running:
            self._worker.running = False
        else:
            self._worker.running = True
            QtCore.QTimer.singleShot(0, self._worker.read_data_from_sensor)
        self.button.setEnabled(False)        
### ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^       
        

def main():
    app = QApplication(sys.argv)
    window = MainScreen()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

enter image description here

Upvotes: 1

Related Questions