повернуть виджет в какой-то степени - PullRequest
1 голос
/ 20 сентября 2019

Я новичок в pyqt и мне нужна помощь с вращением этикетки.Я запутался и не могу понять, как повернуть весь виджет под определенным углом.Не содержимое виджета, а сам виджет.Я ищу решение, но ничего не могу найти.

Ответы [ 2 ]

1 голос
/ 22 сентября 2019

Как правильно объясняет @eyllanesc, в Qt нет поддержки "поворота виджетов" (как в большинстве стандартных фреймворков).

Однако у вас есть несколько хитростей.

«Простая» метка (без использования QLabel)

Это «простое» решение.Поскольку вы говорите о «метке», это может быть реализовано с использованием некоторой математики.

Самым большим преимуществом этого подхода является то, что подсказка по размеру является «простой», то есть она основана только на текстовом содержимом.(как в QFontMetrics.boundingRect()), и всякий раз, когда изменяется основной шрифт, текст или выравнивание, подсказка о размере отражает их.
Хотя он поддерживает многострочные метки, самая большая проблема этого подхода возникаетна месте, если вам нужно использовать расширенный текст, хотя;QTextDocument может использоваться вместо стандартной строки, но это потребует более сложной реализации для вычисления подсказок по размеру.

from math import radians, sin, cos
from random import randrange

from PyQt5 import QtCore, QtGui, QtWidgets

class AngledLabel(QtWidgets.QWidget):
    _alignment = QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop

    def __init__(self, text='', angle=0, parent=None):
        super(AngledLabel, self).__init__(parent)
        self._text = text
        self._angle = angle % 360
        # keep radians of the current angle *and* its opposite; we're using
        # rectangles to get the overall area of the text, and since they use
        # right angles, that opposite is angle + 90
        self._radians = radians(-angle)
        self._radiansOpposite = radians(-angle + 90)

    def alignment(self):
        return self._alignment

    def setAlignment(self, alignment):
        # text alignment might affect the text size!
        if alignment == self._alignment:
            return
        self._alignment = alignment
        self.setMinimumSize(self.sizeHint())

    def angle(self):
        return self._angle

    def setAngle(self, angle):
        # the angle clearly affects the overall size
        angle %= 360
        if angle == self._angle:
            return
        self._angle = angle
        # update the radians to improve optimization of sizeHint and paintEvent
        self._radians = radians(-angle)
        self._radiansOpposite = radians(-angle + 90)
        self.setMinimumSize(self.sizeHint())

    def text(self):
        return self._text

    def setText(self, text):
        if text == self._text:
            return
        self._text = text
        self.setMinimumSize(self.sizeHint())

    def sizeHint(self):
        # get the bounding rectangle of the text
        rect = self.fontMetrics().boundingRect(QtCore.QRect(), self._alignment, self._text)
        # use trigonometry to get the actual size of the rotated rectangle
        sinWidth = abs(sin(self._radians) * rect.width())
        cosWidth = abs(cos(self._radians) * rect.width())
        sinHeight = abs(sin(self._radiansOpposite) * rect.height())
        cosHeight = abs(cos(self._radiansOpposite) * rect.height())
        return QtCore.QSize(cosWidth + cosHeight, sinWidth + sinHeight)

    def minimumSizeHint(self):
        return self.sizeHint()

    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        textRect = self.fontMetrics().boundingRect(
            QtCore.QRect(), self._alignment, self._text)
        width = textRect.width()
        height = textRect.height()
        # we have to translate the painting rectangle, and that depends on which
        # "angle sector" the current angle is
        if self._angle <= 90:
            deltaX = 0
            deltaY = sin(self._radians) * width
        elif 90 < self._angle <= 180:
            deltaX = cos(self._radians) * width
            deltaY = sin(self._radians) * width + sin(self._radiansOpposite) * height
        elif 180 < self._angle <= 270:
            deltaX = cos(self._radians) * width + cos(self._radiansOpposite) * height
            deltaY = sin(self._radiansOpposite) * height
        else:
            deltaX = cos(self._radiansOpposite) * height
            deltaY = 0
        qp.translate(.5 - deltaX, .5 - deltaY)
        qp.rotate(-self._angle)
        qp.drawText(self.rect(), self._alignment, self._text)


class TestWindow(QtWidgets.QWidget):
    def __init__(self):
        super(TestWindow, self).__init__()
        layout = QtWidgets.QGridLayout()
        self.setLayout(layout)

        self.randomizeButton = QtWidgets.QPushButton('Randomize!')
        layout.addWidget(self.randomizeButton, 0, 0, 1, 3)
        self.randomizeButton.clicked.connect(self.randomize)

        layout.addWidget(QtWidgets.QLabel('Standard label'), 1, 0)
        text = 'Some text'
        layout.addWidget(QtWidgets.QLabel(text), 1, 2)
        self.labels = []
        for row, angle in enumerate([randrange(360) for _ in range(8)], 2):
            angleLabel = QtWidgets.QLabel(u'{}°'.format(angle))
            angleLabel.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
            layout.addWidget(angleLabel, row, 0)
            label = AngledLabel(text, angle)
            layout.addWidget(label, row, 2)
            self.labels.append((angleLabel, label))

        separator = QtWidgets.QFrame()
        separator.setFrameShape(separator.VLine|separator.Sunken)
        layout.addWidget(separator, 1, 1, layout.rowCount() - 1, 1)

    def randomize(self):
        for angleLabel, label in self.labels:
            angle = randrange(360)
            angleLabel.setText(str(angle))
            label.setAngle(angle)
        self.adjustSize()


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = TestWindow()
    w.show()
    sys.exit(app.exec_())

QGraphicsView реализация

Я также хотел бы расширить решение, предложенное eyllanesc, так как оно более модульное и позволяет использовать «любой» виджет;к сожалению, хотя его ответ работает, как и ожидалось, я боюсь, что этот ответ действителен «только для аргумента».
С графической точки зрения очевидными проблемами являются визуальные подсказки QGraphicsView (границыи фон).Но, поскольку мы говорим о виджетах, которые могут быть вставлены в графический интерфейс, размер (и его подсказка) требуют некоторого внимания.Основным преимуществом этого подхода является то, что практически любой тип виджета может быть добавлен к интерфейсу, но из-за особенностей политики размера для каждого виджета и реализаций QGraphicsView, если содержимое "повернутого" виджетаизменения, идеальное рисование всегда будет чем-то трудным для достижения.

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

class AngledObject(QtWidgets.QGraphicsView):
    _angle = 0

    def __init__(self, angle=0, parent=None):
        super(AngledObject, self).__init__(parent)
        # to prevent the graphics view to draw its borders or background, set the
        # FrameShape property to 0 and a transparent background
        self.setFrameShape(0)
        self.setStyleSheet('background: transparent')
        self.setScene(QtWidgets.QGraphicsScene())
        # ignore scroll bars!
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)

    def angle(self):
        return self._angle

    def setAngle(self, angle):
        angle %= 360
        if angle == self._angle:
            return
        self._angle = angle
        self._proxy.setTransform(QtGui.QTransform().rotate(-angle))
        self.adjustSize()

    def resizeEvent(self, event):
        super(AngledObject, self).resizeEvent(event)
        # ensure that the scene is fully visible after resizing
        QtCore.QTimer.singleShot(0, lambda: self.centerOn(self.sceneRect().center()))

    def sizeHint(self):
        return self.scene().itemsBoundingRect().size().toSize()

    def minimumSizeHint(self):
        return self.sizeHint()


class AngledLabel(AngledObject):
    def __init__(self, text='', angle=0, parent=None):
        super(AngledLabel, self).__init__(angle, parent)
        self._label = QtWidgets.QLabel(text)
        self._proxy = self.scene().addWidget(self._label)
        self._label.setStyleSheet('background: transparent')
        self.setAngle(angle)
        self.alignment = self._label.alignment

    def setAlignment(self, alignment):
        # text alignment might affect the text size!
        if alignment == self._label.alignment():
            return
        self._label.setAlignment(alignment)
        self.setMinimumSize(self.sizeHint())

    def text(self):
        return self._label.text()

    def setText(self, text):
        if text == self._label.text():
            return
        self._label.setText(text)
        self.setMinimumSize(self.sizeHint())


class AngledButton(AngledObject):
    def __init__(self, text='', angle=0, parent=None):
        super(AngledButton, self).__init__(angle, parent)
        self._button = QtWidgets.QPushButton(text)
        self._proxy = self.scene().addWidget(self._button)
        self.setAngle(angle)


class TestWindow(QtWidgets.QWidget):
    def __init__(self):
        super(TestWindow, self).__init__()
        layout = QtWidgets.QGridLayout()
        self.setLayout(layout)

        self.randomizeButton = QtWidgets.QPushButton('Randomize!')
        layout.addWidget(self.randomizeButton, 0, 0, 1, 3)
        self.randomizeButton.clicked.connect(self.randomize)

        layout.addWidget(QtWidgets.QLabel('Standard label'), 1, 0)
        text = 'Some text'
        layout.addWidget(QtWidgets.QLabel(text), 1, 2)
        self.labels = []
        for row, angle in enumerate([randrange(360) for _ in range(4)], 2):
            angleLabel = QtWidgets.QLabel(u'{}°'.format(angle))
            angleLabel.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
            layout.addWidget(angleLabel, row, 0)
            label = AngledLabel(text, angle)
            layout.addWidget(label, row, 2)
            self.labels.append((angleLabel, label))

        for row, angle in enumerate([randrange(360) for _ in range(4)], row + 1):
            angleLabel = QtWidgets.QLabel(u'{}°'.format(angle))
            angleLabel.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
            layout.addWidget(angleLabel, row, 0)
            label = AngledButton('Button!', angle)
            layout.addWidget(label, row, 2)
            self.labels.append((angleLabel, label))

        separator = QtWidgets.QFrame()
        separator.setFrameShape(separator.VLine|separator.Sunken)
        layout.addWidget(separator, 1, 1, layout.rowCount() - 1, 1)

    def randomize(self):
        for angleLabel, label in self.labels:
            angle = randrange(360)
            angleLabel.setText(str(angle))
            label.setAngle(angle)
        self.adjustSize()


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = TestWindow()
    w.show()
    sys.exit(app.exec_())

Как видите, функции "randomize" дают очень разные результаты.Хотя второй подход позволяет использовать более сложные виджеты, первый лучше реагирует на изменения содержимого.

1 голос
/ 20 сентября 2019

QWidget не поддерживает вращение, но обходной путь - вставить виджет в QGraphicsProxyWidget и добавить его в QGraphicsScene, а затем повернуть QGraphicsProxyWidget, который визуально генерирует тот же эффект вращения виджета.

from PyQt5 import QtCore, QtGui, QtWidgets


def main():
    import sys

    app = QtWidgets.QApplication(sys.argv)

    label = QtWidgets.QLabel("Stack Overflow", alignment=QtCore.Qt.AlignCenter)

    graphicsview = QtWidgets.QGraphicsView()
    scene = QtWidgets.QGraphicsScene(graphicsview)
    graphicsview.setScene(scene)

    proxy = QtWidgets.QGraphicsProxyWidget()
    proxy.setWidget(label)
    proxy.setTransformOriginPoint(proxy.boundingRect().center())
    scene.addItem(proxy)

    slider = QtWidgets.QSlider(minimum=0, maximum=359, orientation=QtCore.Qt.Horizontal)
    slider.valueChanged.connect(proxy.setRotation)

    label_text = QtWidgets.QLabel(
        "{}°".format(slider.value()), alignment=QtCore.Qt.AlignCenter
    )
    slider.valueChanged.connect(
        lambda value: label_text.setText("{}°".format(slider.value()))
    )

    slider.setValue(45)

    w = QtWidgets.QWidget()
    lay = QtWidgets.QVBoxLayout(w)
    lay.addWidget(graphicsview)
    lay.addWidget(slider)
    lay.addWidget(label_text)
    w.resize(640, 480)
    w.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...