【问题标题】:rotate the widget for some degree将小部件旋转一定程度
【发布时间】:2020-01-21 01:50:21
【问题描述】:

我是 pyqt 的新手,需要帮助旋转标签。我很困惑,无法理解如何以特定角度旋转整个小部件。不是小部件的内容,而是小部件本身。我正在寻找解决方案,但找不到任何东西。

【问题讨论】:

    标签: python pyqt


    【解决方案1】:

    一个QWidget不支持旋转,但是一个解决方法是将widget插入到一个QGraphicsProxyWidget中并添加到一个QGraphicsScene中,然后旋转QGraphicsProxyWidget,在视觉上产生同样的widget旋转效果。

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

    【讨论】:

      【解决方案2】:

      正如@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 视觉提示(边框和背景)。但是,由于我们正在讨论可能必须插入图形界面的小部件,因此需要注意大小(及其提示[s])。 这种方法的主要优点是几乎任何类型的小部件都可以添加到界面中,但由于每个小部件大小策略和 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_())
      

      如您所见,“随机化”函数的结果非常不同。虽然第二种方法允许使用更复杂的小部件,但第一种方法可以更好地响应内容的变化。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-05-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多