Making an invisible layer in pyQt which covers the whole dialog

前端 未结 1 1349
没有蜡笔的小新
没有蜡笔的小新 2021-01-24 06:32

I want to show a spinner while a task is being performed in my pyqt5 application. I found this nice implementation of a spinner, so I tried it: https://github.com/z3ntu/QtWaitin

相关标签:
1条回答
  • 2021-01-24 07:35

    Once I also had that problem so I modified the library, first activate the flags: QtCore.Qt.Dialog | QtCore.Qt.FramelessWindowHint, and the other change must be done in updatePosition() method:

    def updatePosition(self):
        if self.parentWidget() and self._centerOnParent:
            parentRect = QtCore.QRect(self.parentWidget().mapToGlobal(QtCore.QPoint(0, 0)), self.parentWidget().size())
            self.move(QtWidgets.QStyle.alignedRect(QtCore.Qt.LeftToRight, QtCore.Qt.AlignCenter, self.size(), parentRect).topLeft())
    

    The result is as follows:

    waitingspinnerwidget.py

    import math
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class QtWaitingSpinner(QtWidgets.QWidget):
        def __init__(self, parent=None, centerOnParent=True, disableParentWhenSpinning=False, modality=QtCore.Qt.NonModal):
            super().__init__(parent, flags=QtCore.Qt.Dialog | QtCore.Qt.FramelessWindowHint)
            self._centerOnParent = centerOnParent
            self._disableParentWhenSpinning = disableParentWhenSpinning
    
            # WAS IN initialize()
            self._color = QtGui.QColor(QtCore.Qt.black)
            self._roundness = 100.0
            self._minimumTrailOpacity = 3.14159265358979323846
            self._trailFadePercentage = 80.0
            self._revolutionsPerSecond = 1.57079632679489661923
            self._numberOfLines = 20
            self._lineLength = 10
            self._lineWidth = 2
            self._innerRadius = 10
            self._currentCounter = 0
            self._isSpinning = False
    
            self._timer = QtCore.QTimer(self)
            self._timer.timeout.connect(self.rotate)
            self.updateSize()
            self.updateTimer()
            self.hide()
            # END initialize()
    
            self.setWindowModality(modality)
            self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
    
        def paintEvent(self, QPaintEvent):
            self.updatePosition()
            painter = QtGui.QPainter(self)
            painter.fillRect(self.rect(), QtCore.Qt.transparent)
            painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
    
            if self._currentCounter >= self._numberOfLines:
                self._currentCounter = 0
    
            painter.setPen(QtCore.Qt.NoPen)
            for i in range(0, self._numberOfLines):
                painter.save()
                painter.translate(self._innerRadius + self._lineLength, self._innerRadius + self._lineLength)
                rotateAngle = float(360 * i) / float(self._numberOfLines)
                painter.rotate(rotateAngle)
                painter.translate(self._innerRadius, 0)
                distance = self.lineCountDistanceFromPrimary(i, self._currentCounter, self._numberOfLines)
                color = self.currentLineColor(distance, self._numberOfLines, self._trailFadePercentage,
                                              self._minimumTrailOpacity, self._color)
                painter.setBrush(color)
                painter.drawRoundedRect(QtCore.QRect(0, -self._lineWidth / 2, self._lineLength, self._lineWidth), self._roundness,
                                        self._roundness, QtCore.Qt.RelativeSize)
                painter.restore()
    
        def start(self):
            self.updatePosition()
            self._isSpinning = True
            self.show()
    
            if self.parentWidget and self._disableParentWhenSpinning:
                self.parentWidget().setEnabled(False)
    
            if not self._timer.isActive():
                self._timer.start()
                self._currentCounter = 0
    
        def stop(self):
            self._isSpinning = False
            self.hide()
    
            if self.parentWidget() and self._disableParentWhenSpinning:
                self.parentWidget().setEnabled(True)
    
            if self._timer.isActive():
                self._timer.stop()
                self._currentCounter = 0
    
        def setNumberOfLines(self, lines):
            self._numberOfLines = lines
            self._currentCounter = 0
            self.updateTimer()
    
        def setLineLength(self, length):
            self._lineLength = length
            self.updateSize()
    
        def setLineWidth(self, width):
            self._lineWidth = width
            self.updateSize()
    
        def setInnerRadius(self, radius):
            self._innerRadius = radius
            self.updateSize()
    
        def color(self):
            return self._color
    
        def roundness(self):
            return self._roundness
    
        def minimumTrailOpacity(self):
            return self._minimumTrailOpacity
    
        def trailFadePercentage(self):
            return self._trailFadePercentage
    
        def revolutionsPersSecond(self):
            return self._revolutionsPerSecond
    
        def numberOfLines(self):
            return self._numberOfLines
    
        def lineLength(self):
            return self._lineLength
    
        def lineWidth(self):
            return self._lineWidth
    
        def innerRadius(self):
            return self._innerRadius
    
        def isSpinning(self):
            return self._isSpinning
    
        def setRoundness(self, roundness):
            self._roundness = max(0.0, min(100.0, roundness))
    
        def setColor(self, color=QtCore.Qt.black):
            self._color = QColor(color)
    
        def setRevolutionsPerSecond(self, revolutionsPerSecond):
            self._revolutionsPerSecond = revolutionsPerSecond
            self.updateTimer()
    
        def setTrailFadePercentage(self, trail):
            self._trailFadePercentage = trail
    
        def setMinimumTrailOpacity(self, minimumTrailOpacity):
            self._minimumTrailOpacity = minimumTrailOpacity
    
        def rotate(self):
            self._currentCounter += 1
            if self._currentCounter >= self._numberOfLines:
                self._currentCounter = 0
            self.update()
    
        def updateSize(self):
            size = (self._innerRadius + self._lineLength) * 2
            self.setFixedSize(size, size)
    
        def updateTimer(self):
            self._timer.setInterval(1000 / (self._numberOfLines * self._revolutionsPerSecond))
    
        def updatePosition(self):
            if self.parentWidget() and self._centerOnParent:
                parentRect = QtCore.QRect(self.parentWidget().mapToGlobal(QtCore.QPoint(0, 0)), self.parentWidget().size())
                self.move(QtWidgets.QStyle.alignedRect(QtCore.Qt.LeftToRight, QtCore.Qt.AlignCenter, self.size(), parentRect).topLeft())
    
    
        def lineCountDistanceFromPrimary(self, current, primary, totalNrOfLines):
            distance = primary - current
            if distance < 0:
                distance += totalNrOfLines
            return distance
    
        def currentLineColor(self, countDistance, totalNrOfLines, trailFadePerc, minOpacity, colorinput):
            color = QtGui.QColor(colorinput)
            if countDistance == 0:
                return color
            minAlphaF = minOpacity / 100.0
            distanceThreshold = int(math.ceil((totalNrOfLines - 1) * trailFadePerc / 100.0))
            if countDistance > distanceThreshold:
                color.setAlphaF(minAlphaF)
            else:
                alphaDiff = color.alphaF() - minAlphaF
                gradient = alphaDiff / float(distanceThreshold + 1)
                resultAlpha = color.alphaF() - gradient * countDistance
                # If alpha is out of bounds, clip it.
                resultAlpha = min(1.0, max(0.0, resultAlpha))
                color.setAlphaF(resultAlpha)
            return color
    

    With the above we solve one of those problems, the other problem is that urllib.request.urlretrieve() is blocking so it will cause the GUI to freeze, so the solution is to move it to another thread, using a previous response we can do it in the following way:

    from PyQt5 import QtCore, QtGui, QtWidgets
    import urllib.request
    from waitingspinnerwidget import QtWaitingSpinner
    
    
    class RequestRunnable(QtCore.QRunnable):
        def __init__(self, url, destination, dialog):
            super(RequestRunnable, self).__init__()
            self._url = url
            self._destination = destination
            self.w = dialog
    
        def run(self):
            urllib.request.urlretrieve(self._url, self._destination)
            QMetaObject.invokeMethod(self.w, "FinishedDownload", QtCore.Qt.QueuedConnection)
    
    
    class DownloadDataDialog(QtWidgets.QDialog):
        def __init__(self, parent=None):
            super(DownloadDataDialog, self).__init__(parent)
            self.spinner = QtWaitingSpinner(self, True, True, QtCore.Qt.ApplicationModal)
            tabWidget = QtWidgets.QTabWidget(self)
            tabWidget.addTab(MyTab(), "MyTab")
            mainLayout = QtWidgets.QVBoxLayout(self)
            mainLayout.addWidget(tabWidget)
            self.setWindowTitle("Download option chain data from web")
    
    
    class MyTab(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super(MyTab, self).__init__(parent)
    
            dataGroup = QtWidgets.QGroupBox('Data')
    
            getButton = QtWidgets.QPushButton('Download')
            getButton.clicked.connect(self.download_data)
    
            dataLayout = QtWidgets.QVBoxLayout(self)
            dataLayout.addWidget(getButton)
    
            mainLayout = QtWidgets.QVBoxLayout(self)
            mainLayout.addWidget(dataGroup)
            mainLayout.addStretch(1)
    
        def download_data(self):
            self.parentWidget().window().spinner.start()
            url = 'http://www.meff.es/docs/Ficheros/Descarga/dRV/RV180912.zip'
            destination = 'test.zip'
            runnable = RequestRunnable(url, destination, self)
            QtCore.QThreadPool.globalInstance().start(runnable)
    
        @QtCore.pyqtSlot()
        def FinishedDownload(self):
            self.parentWidget().window().spinner.stop()
    
    
    if __name__ == '__main__':
    
        import sys
        app = QtWidgets.QApplication(sys.argv)
        tabdialog = DownloadDataDialog()
        tabdialog.show()
        sys.exit(app.exec_())
    

    0 讨论(0)
提交回复
热议问题