在 pyQt 中制作一个覆盖整个对话框的不可见层

问题描述 投票:0回答:1

我想在 pyqt5 应用程序中执行任务时显示一个微调器。我发现了这个很好的旋转器实现,所以我尝试了它:https://github.com/z3ntu/QtWaitingSpinner

演示工作正常,但在演示中,微调器显示在对话框的空白区域中。我希望它是一个覆盖整个对话框的覆盖层。

QtWaitingSpinner 的作者建议“作为替代示例,下面的代码将创建一个微调器,(1) 只要微调器处于活动状态,就会阻止对主应用程序的所有用户输入,(2) 自动将其自身置于其父级中心每次调用“start”时,widget 都会使用默认的形状、大小和颜色设置。使用以下代码:

spinner = QtWaitingSpinner(self, True, True, Qt.ApplicationModal)
spinner.start() # starts spinning

但是我尝试了这个实现作为示例,但它不起作用:

from PyQt5.QtWidgets import QApplication, QDialog, QTabWidget, QWidget, QGroupBox, QPushButton, QVBoxLayout
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
import requests
import urllib
from waitingspinnerwidget import QtWaitingSpinner

class DownloadDataDialog(QDialog):
    def __init__(self, parent=None):
        super(DownloadDataDialog, self).__init__(parent)

        self.spinner = QtWaitingSpinner(self, True, True, Qt.ApplicationModal)

        tabWidget = QTabWidget(self)
        tabWidget.addTab(MyTab(tabWidget), "MyTab")

        mainLayout = QVBoxLayout()
        mainLayout.addWidget(tabWidget)
        self.setLayout(mainLayout)

        self.setWindowTitle("Download option chain data from web")

class MyTab(QWidget):
    def __init__(self, parent=None):
        super(MyTab, self).__init__(parent)

        dataGroup = QGroupBox('Data')

        getButton = QPushButton('Download')
        getButton.clicked.connect(self.download_data)

        dataLayout = QVBoxLayout()
        dataLayout.addWidget(getButton)
        dataGroup.setLayout(dataLayout)

        mainLayout = QVBoxLayout()
        mainLayout.addWidget(dataGroup)
        mainLayout.addStretch(1)
        self.setLayout(mainLayout)

    def download_data(self):
        self.parent().parent().parent().spinner.start()
        url = 'http://www.meff.es/docs/Ficheros/Descarga/dRV/RV180912.zip'
        filepath = None
        try:
            filepath = self.download_data_file(url)
        except Exception as e:
            print(e)

        self.parent().parent().parent().spinner.stop()
        if filepath:
            #TODO doing stuff here
            self.parent().parent().parent().close()
        else:
            pass #TODO show error dialog

    def download_data_file(self, download_url):           
        # Build request URL and download the file
        destination = 'test.zip'
        urllib.request.urlretrieve(download_url, destination)
        return destination

if __name__ == '__main__':

    import sys

    app = QApplication(sys.argv)

    tabdialog = DownloadDataDialog()
    tabdialog.show()
    sys.exit(app.exec_())

所以我的意图是创建一个不可见层,将微调器设置为其唯一的小部件,并在整个对话框窗口上显示半透明层。

知道我应该怎么做吗?

python pyqt pyqt5
1个回答
4
投票

一旦我也遇到了这个问题,所以我修改了库,首先激活标志:

QtCore.Qt.Dialog | QtCore.Qt.FramelessWindowHint
,其他更改必须在 updatePosition() 方法中完成:

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())

结果如下:

等待spinnerwidget.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

通过上面的内容,我们解决了其中一个问题,另一个问题是

urllib.request.urlretrieve()
正在阻塞,因此它将导致 GUI 冻结,因此解决方案是将其移动到另一个线程,使用之前的响应,我们可以在方法如下:

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_())

© www.soinside.com 2019 - 2024. All rights reserved.