Qt - Показать виджет или метку над всеми виджетами - PullRequest
0 голосов
/ 01 августа 2020

Я хочу отображать экран загрузки каждый раз, когда пользователь нажимает кнопку (выполняется процесс, который занимает несколько секунд).

Я хочу что-то вроде этого

QSplashScreen мне не помогает, потому что он используется только перед открытием приложения, а QDialog мне не нужен, потому что я хочу, чтобы при перетаскивании окна приложение перемещалось вместе с сообщением «Загрузка». ..

Что мне нужно использовать?

Ответы [ 3 ]

0 голосов
/ 02 августа 2020

Вы можете использовать этот слот : void QWidget::raise(). Поднимает этот виджет до вершины стека родительского виджета . После этого вызова виджет будет визуально перед любыми перекрывающимися родственными виджетами .

0 голосов
/ 03 августа 2020

Единственный (безопасный) способ добиться этого - добавить дочерний виджет без добавления его в какой-либо менеджер компоновки.

Единственное, о чем вам нужно заботиться, это чтобы виджет всегда поднят , как только он отображается, и что геометрия всегда обновляется до родительского виджета (или, лучше, окна верхнего уровня).

Это - это немного более сложный пример, но он имеет то преимущество, что вы можете просто подклассифицировать любой виджет , добавив класс LoadingWidget к базовым классам, чтобы реализовать механизм загрузки.

скриншот примера загрузчика

from random import randrange
from PyQt5 import QtCore, QtGui, QtWidgets

class Loader(QtWidgets.QWidget):
    def __init__(self, parent):
        super().__init__(parent)

        self.gradient = QtGui.QConicalGradient(.5, .5, 0)
        self.gradient.setCoordinateMode(self.gradient.ObjectBoundingMode)
        self.gradient.setColorAt(.25, QtCore.Qt.transparent)
        self.gradient.setColorAt(.75, QtCore.Qt.transparent)

        self.animation = QtCore.QVariantAnimation(
            startValue=0., endValue=1., 
            duration=1000, loopCount=-1, 
            valueChanged=self.updateGradient
            )

        self.stopTimer = QtCore.QTimer(singleShot=True, timeout=self.stop)

        self.focusWidget = None
        self.hide()
        parent.installEventFilter(self)

    def start(self, timeout=None):
        self.show()
        self.raise_()
        self.focusWidget = QtWidgets.QApplication.focusWidget()
        self.setFocus()
        if timeout:
            self.stopTimer.start(timeout)
        else:
            self.stopTimer.setInterval(0)

    def stop(self):
        self.hide()
        self.stopTimer.stop()
        if self.focusWidget:
            self.focusWidget.setFocus()
            self.focusWidget = None

    def updateGradient(self, value):
        self.gradient.setAngle(-value * 360)
        self.update()

    def eventFilter(self, source, event):
        # ensure that we always cover the whole parent area
        if event.type() == QtCore.QEvent.Resize:
            self.setGeometry(source.rect())
        return super().eventFilter(source, event)

    def showEvent(self, event):
        self.setGeometry(self.parent().rect())
        self.animation.start()

    def hideEvent(self, event):
        # stop the animation when hidden, just for performance
        self.animation.stop()

    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        qp.setRenderHints(qp.Antialiasing)
        color = self.palette().window().color()
        color.setAlpha(max(color.alpha() * .5, 128))
        qp.fillRect(self.rect(), color)

        text = 'Loading...'
        interval = self.stopTimer.interval()
        if interval:
            remaining = int(max(0, interval - self.stopTimer.remainingTime()) / interval * 100)
            textWidth = self.fontMetrics().width(text + ' 000%')
            text += ' {}%'.format(remaining)
        else:
            textWidth = self.fontMetrics().width(text)
        textHeight = self.fontMetrics().height()
        # ensure that there's enough space for the text
        if textWidth > self.width() or textHeight * 3 > self.height():
            drawText = False
            size = max(0, min(self.width(), self.height()) - textHeight * 2)
        else:
            size = size = min(self.height() / 3, max(textWidth, textHeight))
            drawText = True

        circleRect = QtCore.QRect(0, 0, size, size)
        circleRect.moveCenter(self.rect().center())

        if drawText:
            # text is going to be drawn, move the circle rect higher
            circleRect.moveTop(circleRect.top() - textHeight)
            middle = circleRect.center().x()
            qp.drawText(
                middle - textWidth / 2, circleRect.bottom() + textHeight, 
                textWidth, textHeight, 
                QtCore.Qt.AlignCenter, text)

        self.gradient.setColorAt(.5, self.palette().windowText().color())
        qp.setPen(QtGui.QPen(self.gradient, textHeight))
        qp.drawEllipse(circleRect)


class LoadingExtension(object):
    # a base class to extend any QWidget subclass's top level window with a loader
    def startLoading(self, timeout=0):
        window = self.window()
        if not hasattr(window, '_loader'):
            window._loader = Loader(window)
        window._loader.start(timeout)

        # this is just for testing purposes
        if not timeout:
            QtCore.QTimer.singleShot(randrange(1000, 5000), window._loader.stop)

    def loadingFinished(self):
        if hasattr(self.window(), '_loader'):
            self.window()._loader.stop()


class Test(QtWidgets.QWidget, LoadingExtension):
    def __init__(self):
        super().__init__()
        layout = QtWidgets.QGridLayout(self)

        # just a test widget
        textEdit = QtWidgets.QTextEdit()
        layout.addWidget(textEdit, 0, 0, 1, 2)
        textEdit.setMinimumHeight(20)

        layout.addWidget(QtWidgets.QLabel('Timeout:'))
        self.timeoutSpin = QtWidgets.QSpinBox(maximum=5000, singleStep=250, specialValueText='Random')
        layout.addWidget(self.timeoutSpin, 1, 1)
        self.timeoutSpin.setValue(2000)

        btn = QtWidgets.QPushButton('Start loading...')
        layout.addWidget(btn, 2, 0, 1, 2)
        btn.clicked.connect(lambda: self.startLoading(self.timeoutSpin.value()))


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    test = Test()
    test.show()
    sys.exit(app.exec_())
0 голосов
/ 02 августа 2020

Пожалуйста, проверьте Qt :: WindowFlags. Флаг Qt :: SplashScreen даст вам опыт работы с экраном spla sh без использования QSplashScreen (вы можете использовать его со всеми виджетами, как показано) или, что лучше, использовать QDialog с этим флагом. Для перемещения, вероятно, прекрасное решение недоступно, но вы можете просто использовать родительский moveEvent для излучения сигнала. Например: Главное окно: moveEvent -> сигнал перемещен Диалог: перемещение сигнала -> повторно центрировать окно. Выглядит как не сложно.

Кстати, блокировать все GUI при запуске приложения считаю не лучшим решением. Как вы думаете, используете QProgressBar?

...