Created
January 25, 2018 13:16
-
-
Save eyllanesc/1a09157d17ba13d223c312b28a81c320 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import requests | |
"""from PyQt5.QtCore import * | |
from PyQt5.QtGui import * | |
from PyQt5.QtWidgets import *""" | |
from PyQt4.QtCore import * | |
from PyQt4.QtGui import * | |
from WaitingSpinnerWidget import QtWaitingSpinner | |
class RequestRunnable(QRunnable): | |
def __init__(self, dialog): | |
QRunnable.__init__(self) | |
self.w = dialog | |
def run(self): | |
QThread.msleep(10000) | |
QMetaObject.invokeMethod(self.w, "setData", | |
Qt.QueuedConnection, | |
Q_ARG(str, "finish")) | |
class Dialog(QDialog): | |
def __init__(self, *args, **kwargs): | |
QDialog.__init__(self, *args, **kwargs) | |
self.setLayout(QVBoxLayout()) | |
btn = QPushButton("Submit", self) | |
btn.clicked.connect(self.submit) | |
self.spinner = QtWaitingSpinner(self) | |
self.layout().addWidget(btn) | |
self.layout().addWidget(self.spinner) | |
def submit(self): | |
self.spinner.start() | |
runnable = RequestRunnable(self) | |
QThreadPool.globalInstance().start(runnable) | |
@pyqtSlot(str) | |
def setData(self, data): | |
print(data) | |
self.spinner.stop() | |
self.adjustSize() | |
if __name__ == '__main__': | |
import sys | |
app = QApplication(sys.argv) | |
dial = Dialog() | |
dial.show() | |
sys.exit(app.exec_()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from math import ceil | |
"""from PyQt5.QtCore import * | |
from PyQt5.QtGui import * | |
from PyQt5.QtWidgets import *""" | |
from PyQt4.QtCore import * | |
from PyQt4.QtGui import * | |
class QtWaitingSpinner(QWidget): | |
mColor = QColor(Qt.gray) | |
mRoundness = 100.0 | |
mMinimumTrailOpacity = 31.4159265358979323846 | |
mTrailFadePercentage = 50.0 | |
mRevolutionsPerSecond = 1.57079632679489661923 | |
mNumberOfLines = 20 | |
mLineLength = 10 | |
mLineWidth = 2 | |
mInnerRadius = 20 | |
mCurrentCounter = 0 | |
mIsSpinning = False | |
def __init__(self, centerOnParent=True, disableParentWhenSpinning=True, *args, **kwargs): | |
QWidget.__init__(self, *args, **kwargs) | |
self.mCenterOnParent = centerOnParent | |
self.mDisableParentWhenSpinning = disableParentWhenSpinning | |
self.initialize() | |
def initialize(self): | |
self.timer = QTimer(self) | |
self.timer.timeout.connect(self.rotate) | |
self.updateSize() | |
self.updateTimer() | |
self.hide() | |
@pyqtSlot() | |
def rotate(self): | |
self.mCurrentCounter += 1 | |
if self.mCurrentCounter > self.numberOfLines(): | |
self.mCurrentCounter = 0 | |
self.update() | |
def updateSize(self): | |
size = (self.mInnerRadius + self.mLineLength) * 2 | |
self.setFixedSize(size, size) | |
def updateTimer(self): | |
self.timer.setInterval(1000 / (self.mNumberOfLines * self.mRevolutionsPerSecond)) | |
def updatePosition(self): | |
if self.parentWidget() and self.mCenterOnParent: | |
self.move(self.parentWidget().width() / 2 - self.width() / 2, | |
self.parentWidget().height() / 2 - self.height() / 2) | |
def lineCountDistanceFromPrimary(self, current, primary, totalNrOfLines): | |
distance = primary - current | |
if distance < 0: | |
distance += totalNrOfLines | |
return distance | |
def currentLineColor(self, countDistance, totalNrOfLines, trailFadePerc, minOpacity, color): | |
if countDistance == 0: | |
return color | |
minAlphaF = minOpacity / 100.0 | |
distanceThreshold = ceil((totalNrOfLines - 1) * trailFadePerc / 100.0) | |
if countDistance > distanceThreshold: | |
color.setAlphaF(minAlphaF) | |
else: | |
alphaDiff = self.mColor.alphaF() - minAlphaF | |
gradient = alphaDiff / distanceThreshold + 1.0 | |
resultAlpha = color.alphaF() - gradient * countDistance | |
resultAlpha = min(1.0, max(0.0, resultAlpha)) | |
color.setAlphaF(resultAlpha) | |
return color | |
def paintEvent(self, event): | |
self.updatePosition() | |
painter = QPainter(self) | |
painter.fillRect(self.rect(), Qt.transparent) | |
painter.setRenderHint(QPainter.Antialiasing, True) | |
if self.mCurrentCounter > self.mNumberOfLines: | |
self.mCurrentCounter = 0 | |
painter.setPen(Qt.NoPen) | |
for i in range(self.mNumberOfLines): | |
painter.save() | |
painter.translate(self.mInnerRadius + self.mLineLength, | |
self.mInnerRadius + self.mLineLength) | |
rotateAngle = 360.0 * i / self.mNumberOfLines | |
painter.rotate(rotateAngle) | |
painter.translate(self.mInnerRadius, 0) | |
distance = self.lineCountDistanceFromPrimary(i, self.mCurrentCounter, | |
self.mNumberOfLines) | |
color = self.currentLineColor(distance, self.mNumberOfLines, | |
self.mTrailFadePercentage, self.mMinimumTrailOpacity, self.mColor) | |
painter.setBrush(color) | |
painter.drawRoundedRect(QRect(0, -self.mLineWidth // 2, self.mLineLength, self.mLineLength), | |
self.mRoundness, Qt.RelativeSize) | |
painter.restore() | |
def start(self): | |
self.updatePosition() | |
self.mIsSpinning = True | |
self.show() | |
if self.parentWidget() and self.mDisableParentWhenSpinning: | |
self.parentWidget().setEnabled(False) | |
if not self.timer.isActive(): | |
self.timer.start() | |
self.mCurrentCounter = 0 | |
def stop(self): | |
self.mIsSpinning = False | |
self.hide() | |
if self.parentWidget() and self.mDisableParentWhenSpinning: | |
self.parentWidget().setEnabled(True) | |
if self.timer.isActive(): | |
self.timer.stop() | |
self.mCurrentCounter = 0 | |
def setNumberOfLines(self, lines): | |
self.mNumberOfLines = lines | |
self.updateTimer() | |
def setLineLength(self, length): | |
self.mLineLength = length | |
self.updateSize() | |
def setLineWidth(self, width): | |
self.mLineWidth = width | |
self.updateSize() | |
def setInnerRadius(self, radius): | |
self.mInnerRadius = radius | |
self.updateSize() | |
def color(self): | |
return self.mColor | |
def roundness(self): | |
return self.mRoundness | |
def minimumTrailOpacity(self): | |
return self.mMinimumTrailOpacity | |
def trailFadePercentage(self): | |
return self.mTrailFadePercentage | |
def revolutionsPersSecond(self): | |
return self.mRevolutionsPerSecond | |
def numberOfLines(self): | |
return self.mNumberOfLines | |
def lineLength(self): | |
return self.mLineLength | |
def lineWidth(self): | |
return self.mLineWidth | |
def innerRadius(self): | |
return self.mInnerRadius | |
def isSpinning(self): | |
return self.mIsSpinning | |
def setRoundness(self, roundness): | |
self.mRoundness = min(0.0, max(100, roundness)) | |
def setColor(self, color): | |
self.mColor = color | |
def setRevolutionsPerSecond(self, revolutionsPerSecond): | |
self.mRevolutionsPerSecond = revolutionsPerSecond | |
self.updateTimer() | |
def setTrailFadePercentage(self, trail): | |
self.mTrailFadePercentage = trail | |
def setMinimumTrailOpacity(self, minimumTrailOpacity): | |
self.mMinimumTrailOpacity = minimumTrailOpacity | |
if __name__ == '__main__': | |
import sys | |
app = QApplication(sys.argv) | |
dial = QDialog() | |
w = QtWaitingSpinner(dial) | |
dial.show() | |
w.start() | |
QTimer.singleShot(1000, w.stop) | |
sys.exit(app.exec_()) |
Thanks @eyllanesc. This works with me also, so I'm not sure what's going on in my application at this stage - will just need to dig in.
I'm not sure if you may find this useful or not, but I built a function decorator to executelong execution functions in a separate thread, which additionally manages the QtWaitingSpinner interface.
The following code works nicely (based on your example) - i'm sure there's room for improvement, but it's been stable for me across various applications.
Thanks for the help again.
import sys
import time
from PyQt5.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QLabel, QApplication
from PyQt5.QtCore import QThread
def busy_spinner_decorator(busy_spinner_attr_name=None, parent_callback_func_name=None):
"""
This decorator causes the decorated function to be executed within it's own thread, operates the QtWaitingSpinner,
and calls back the desired function on completion (whether success or fail).
Callback is called from main thread and will be served the return value of the decorated function.
How to use
@busy_spinner_decorator("my_busy_spinner_instance", "function_i_want_callback")
def long_execution_function(self):
@param busy_spinner_attr_name: QtWaitingSpinner instance variable name
@type busy_spinner_attr_name: str
@param parent_callback_func_name: Call back function name
@type parent_callback_func_name: str
"""
# Reference
# https://www.techblog.moebius.space/posts/2019-06-22-third-time-lucky-with-python-decorators/#2-decorator-classes
# https://www.programmersought.com/article/6421908527/
# https://realpython.com/primer-on-python-decorators/#classes-as-decorators
# https://www.datacamp.com/community/tutorials/decorators-python
# https://www.py4u.net/discuss/205775
def deco(function):
def inner(self, *args, **kwargs):
# Get the function 'parent' (ie. 'self'), and get access to the attributes used by this decorator
parent = self
busy_spinner = getattr(self, busy_spinner_attr_name) if busy_spinner_attr_name is not None else None
parent_callback = getattr(self, parent_callback_func_name) if parent_callback_func_name is not None else None
def func_threaded_start(self, *args, **kwargs):
nonlocal busy_spinner
def _callback(result):
nonlocal parent_callback
nonlocal busy_spinner
if parent_callback:
LOGGER.debug(f"calling parent_callback with result = {result}")
parent_callback(result)
if busy_spinner:
busy_spinner.stop()
class Worker(QtCore.QThread):
def __init__(self):
nonlocal parent
super(QtCore.QThread, self).__init__(parent=parent)
self.finished.connect(self._complete)
self.result = None
def _complete(self):
nonlocal _callback
LOGGER.debug("calling self._callback")
_callback(self.result)
def run(self):
nonlocal parent
try:
LOGGER.debug("calling function")
self.result = function(parent, *args, **kwargs)
except Exception as e:
raise e
#self.result = traceback.format_exc()
_my_thread_instance = Worker()
if busy_spinner: busy_spinner.start()
_my_thread_instance.start()
LOGGER.debug("_my_thread_instance started")
return func_threaded_start(self, *args, **kwargs)
return inner
return deco
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.widget_main = QWidget(self)
self.setCentralWidget(self.widget_main)
self.layout_main = QVBoxLayout()
self.widget_main.setLayout(self.layout_main)
self.busy_spinner = QtWaitingSpinner(self.centralWidget())
self.centralWidget().layout().addWidget(self.busy_spinner)
self.label = QLabel("")
self.centralWidget().layout().addWidget(self.label)
@busy_spinner_decorator("busy_spinner", "callback")
def long_operation(self):
self.label.setText("running")
time.sleep(3)
return True
def callback(self, retval):
self.label.setText(f"complete: {str(retval)}")
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
w.long_operation()
sys.exit(app.exec_())
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@cranialhum I just tested my code with:
And works.
On the other hand, QMainWindow has a predefined layout:
So it is not recommended to add it directly.