Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. QGraphicsLayout not properly resizing to change of content
Forum Updated to NodeBB v4.3 + New Features

QGraphicsLayout not properly resizing to change of content

Scheduled Pinned Locked Moved Unsolved Qt for Python
5 Posts 3 Posters 943 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • N Offline
    N Offline
    Niagarer
    wrote on 24 Jun 2020, 08:27 last edited by Niagarer
    #1

    Hi,
    I am trying to create a QGraphicsWidget with a QGraphicsLinearLayout (for a QGraphicsItem) containing more layouts and widgets while adding and removing content as a result of user interaction. By left-clicking, I add content. By right-clicking, I remove content. The behavior strongly differs from what I would expect from QLayouts. There are two problems:

    • When adding content, the layout (self.inputs_layout see below) expands like expected. But it does not shrink when removing content. I would assume this has to be done using QSizePolicies but also no luck so far.
    • The stretches I add between two items to the layout somehow stay there also when the content around them is removed which creates an increasing blank area at the top when adding and removing content multiple times. And I cannot find a way to access and remove them. (is this a bug?)

    The following runnable code shows how I manage the layouts. Only the NodeInstance class should be of the essence for this issue:

    import sys
    
    from PySide2.QtWidgets import QMainWindow, QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem, \
        QGraphicsLinearLayout, QGraphicsLayoutItem, QGraphicsWidget
    from PySide2.QtGui import QPainter, QColor, QPen, QFont, QFontMetrics
    from PySide2.QtCore import QRectF, Qt, QPointF, QSizeF
    
    
    class NodeInstance(QGraphicsItem):
        def __init__(self):
            super(NodeInstance, self).__init__()
    
            # main widget
            self.body_widget = QGraphicsWidget(self)
            self.body_layout = QGraphicsLinearLayout(Qt.Horizontal)
            self.body_layout.setSpacing(30)
    
            #       inputs
            self.inputs_layout = self.get_ports_layout(num_ports=5)  # returns QGraphicsLinearLayout
            self.body_layout.addItem(self.inputs_layout)
            self.body_layout.setAlignment(self.inputs_layout, Qt.AlignLeft | Qt.AlignVCenter)
    
            self.body_layout.addStretch()  # add space in between
    
            #       outputs
            self.outputs_layout = self.get_ports_layout(num_ports=2)  # returns QGraphicsLinearLayout
            self.body_layout.addItem(self.outputs_layout)
            self.body_layout.setAlignment(self.outputs_layout, Qt.AlignRight | Qt.AlignVCenter)
    
            self.body_widget.setLayout(self.body_layout)
    
        def get_ports_layout(self, num_ports):
            """Creating vertical layout with PortInstances and stretches in between them."""
            layout = QGraphicsLinearLayout(Qt.Vertical)
            layout.setSpacing(3)
            for i in range(num_ports):
                inp = PortInstance()  # a horizontal QGraphicsLinearLayout containing two QGraphicsWidgets
                layout.addItem(inp)
                if i != num_ports-1:
                    layout.addStretch()
            return layout
    
        def boundingRect(self):
            return self.body_layout.geometry().toRect()
    
        def get_header_rect(self):
            header_height = self.get_header_height()
            header_width = self.get_width()
            height = self.get_height()
    
            return QRectF(-header_width/2, -height/2, header_width, header_height)
    
        def paint(self, painter, option, widget=None):
            painter.setBrush(QColor('yellow'))
            painter.setPen(QPen(QColor(0, 0, 0), 3))
            painter.drawRoundedRect(self.boundingRect(), 20, 20)
    
        def mousePressEvent(self, event):
            if event.button() == Qt.LeftButton:
                # add new input
                self.inputs_layout.addStretch()
                self.inputs_layout.addItem(PortInstance())
    
                self.body_layout.invalidate()
            elif event.button() == Qt.RightButton:
                inp: PortInstance = self.inputs_layout.itemAt(0)
    
                # remove input's contents from scene (for some reason, it doesn't happen automatically, even when deleting)
                self.scene().removeItem(inp.pin)
                self.scene().removeItem(inp.label)
    
                self.inputs_layout.removeAt(0)
    
                self.body_layout.invalidate()
    
    
    class PortInstance(QGraphicsLinearLayout):
        def __init__(self):
            super(PortInstance, self).__init__(Qt.Horizontal)
    
            self.pin = Pin()  # QGraphicsWidget
            self.label = Label()  # QGraphicsWidget
    
            self.addItem(self.pin)
            self.addItem(self.label)
    
            self.setAlignment(self.pin, Qt.AlignCenter)
            self.setAlignment(self.label, Qt.AlignCenter)
            self.setSpacing(10)
    
    
    class Pin(QGraphicsWidget):
        def __init__(self):
            super(Pin, self).__init__()
            self.setGraphicsItem(self)
    
            self.width = 30
            self.height = 30
    
        def boundingRect(self):
            return QRectF(QPointF(0, 0), self.geometry().size())
    
        def setGeometry(self, rect):
            self.prepareGeometryChange()
            QGraphicsLayoutItem.setGeometry(self, rect)
            self.setPos(rect.topLeft())
    
        def sizeHint(self, which, constraint=...):
            return QSizeF(self.width, self.height)
    
        def paint(self, painter, option, widget=None):
            painter.setBrush(QColor(0, 0, 255, 150))
            painter.setPen(Qt.NoPen)
            painter.drawEllipse(self.boundingRect())
    
    
    class Label(QGraphicsWidget):
        def __init__(self):
            super(Label, self).__init__()
            self.setGraphicsItem(self)
    
            self.text = 'label'
            self.font = QFont('Arial', 15)
            f_m = QFontMetrics(self.font)
            self.width = f_m.width(self.text)
            self.height = f_m.height()
    
        def boundingRect(self):
            return QRectF(QPointF(0, 0), self.geometry().size())
    
        def setGeometry(self, rect):
            self.prepareGeometryChange()
            QGraphicsLayoutItem.setGeometry(self, rect)
            self.setPos(rect.topLeft())
    
        def sizeHint(self, which, constraint=...):
            return QSizeF(self.width, self.height)
    
        def paint(self, painter, option, widget=None):
            painter.setFont(self.font)
            painter.drawText(self.boundingRect(), self.text)
    
    
    class MyView(QGraphicsView):
        def __init__(self):
            super(MyView, self).__init__()
            scene = QGraphicsScene(self)
            scene.setSceneRect(0, 0, self.width(), self.height())
            self.setScene(scene)
    
            ni = NodeInstance()
            self.scene().addItem(ni)
            ni.setPos(150, 80)
    
    
    class MainWindow(QMainWindow):
        def __init__(self):
            super(MainWindow, self).__init__()
            self.setCentralWidget(MyView())
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
    
        mw = MainWindow()
        mw.show()
    
        sys.exit(app.exec_())
    

    I am heavily stuck and hope someone with a little more experience with these QGraphicsWidgets can help. I could not find any hints regarding these issues in the doc.
    Thanks for answers!

    JonBJ 1 Reply Last reply 24 Jun 2020, 08:56
    0
    • N Niagarer
      24 Jun 2020, 08:27

      Hi,
      I am trying to create a QGraphicsWidget with a QGraphicsLinearLayout (for a QGraphicsItem) containing more layouts and widgets while adding and removing content as a result of user interaction. By left-clicking, I add content. By right-clicking, I remove content. The behavior strongly differs from what I would expect from QLayouts. There are two problems:

      • When adding content, the layout (self.inputs_layout see below) expands like expected. But it does not shrink when removing content. I would assume this has to be done using QSizePolicies but also no luck so far.
      • The stretches I add between two items to the layout somehow stay there also when the content around them is removed which creates an increasing blank area at the top when adding and removing content multiple times. And I cannot find a way to access and remove them. (is this a bug?)

      The following runnable code shows how I manage the layouts. Only the NodeInstance class should be of the essence for this issue:

      import sys
      
      from PySide2.QtWidgets import QMainWindow, QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem, \
          QGraphicsLinearLayout, QGraphicsLayoutItem, QGraphicsWidget
      from PySide2.QtGui import QPainter, QColor, QPen, QFont, QFontMetrics
      from PySide2.QtCore import QRectF, Qt, QPointF, QSizeF
      
      
      class NodeInstance(QGraphicsItem):
          def __init__(self):
              super(NodeInstance, self).__init__()
      
              # main widget
              self.body_widget = QGraphicsWidget(self)
              self.body_layout = QGraphicsLinearLayout(Qt.Horizontal)
              self.body_layout.setSpacing(30)
      
              #       inputs
              self.inputs_layout = self.get_ports_layout(num_ports=5)  # returns QGraphicsLinearLayout
              self.body_layout.addItem(self.inputs_layout)
              self.body_layout.setAlignment(self.inputs_layout, Qt.AlignLeft | Qt.AlignVCenter)
      
              self.body_layout.addStretch()  # add space in between
      
              #       outputs
              self.outputs_layout = self.get_ports_layout(num_ports=2)  # returns QGraphicsLinearLayout
              self.body_layout.addItem(self.outputs_layout)
              self.body_layout.setAlignment(self.outputs_layout, Qt.AlignRight | Qt.AlignVCenter)
      
              self.body_widget.setLayout(self.body_layout)
      
          def get_ports_layout(self, num_ports):
              """Creating vertical layout with PortInstances and stretches in between them."""
              layout = QGraphicsLinearLayout(Qt.Vertical)
              layout.setSpacing(3)
              for i in range(num_ports):
                  inp = PortInstance()  # a horizontal QGraphicsLinearLayout containing two QGraphicsWidgets
                  layout.addItem(inp)
                  if i != num_ports-1:
                      layout.addStretch()
              return layout
      
          def boundingRect(self):
              return self.body_layout.geometry().toRect()
      
          def get_header_rect(self):
              header_height = self.get_header_height()
              header_width = self.get_width()
              height = self.get_height()
      
              return QRectF(-header_width/2, -height/2, header_width, header_height)
      
          def paint(self, painter, option, widget=None):
              painter.setBrush(QColor('yellow'))
              painter.setPen(QPen(QColor(0, 0, 0), 3))
              painter.drawRoundedRect(self.boundingRect(), 20, 20)
      
          def mousePressEvent(self, event):
              if event.button() == Qt.LeftButton:
                  # add new input
                  self.inputs_layout.addStretch()
                  self.inputs_layout.addItem(PortInstance())
      
                  self.body_layout.invalidate()
              elif event.button() == Qt.RightButton:
                  inp: PortInstance = self.inputs_layout.itemAt(0)
      
                  # remove input's contents from scene (for some reason, it doesn't happen automatically, even when deleting)
                  self.scene().removeItem(inp.pin)
                  self.scene().removeItem(inp.label)
      
                  self.inputs_layout.removeAt(0)
      
                  self.body_layout.invalidate()
      
      
      class PortInstance(QGraphicsLinearLayout):
          def __init__(self):
              super(PortInstance, self).__init__(Qt.Horizontal)
      
              self.pin = Pin()  # QGraphicsWidget
              self.label = Label()  # QGraphicsWidget
      
              self.addItem(self.pin)
              self.addItem(self.label)
      
              self.setAlignment(self.pin, Qt.AlignCenter)
              self.setAlignment(self.label, Qt.AlignCenter)
              self.setSpacing(10)
      
      
      class Pin(QGraphicsWidget):
          def __init__(self):
              super(Pin, self).__init__()
              self.setGraphicsItem(self)
      
              self.width = 30
              self.height = 30
      
          def boundingRect(self):
              return QRectF(QPointF(0, 0), self.geometry().size())
      
          def setGeometry(self, rect):
              self.prepareGeometryChange()
              QGraphicsLayoutItem.setGeometry(self, rect)
              self.setPos(rect.topLeft())
      
          def sizeHint(self, which, constraint=...):
              return QSizeF(self.width, self.height)
      
          def paint(self, painter, option, widget=None):
              painter.setBrush(QColor(0, 0, 255, 150))
              painter.setPen(Qt.NoPen)
              painter.drawEllipse(self.boundingRect())
      
      
      class Label(QGraphicsWidget):
          def __init__(self):
              super(Label, self).__init__()
              self.setGraphicsItem(self)
      
              self.text = 'label'
              self.font = QFont('Arial', 15)
              f_m = QFontMetrics(self.font)
              self.width = f_m.width(self.text)
              self.height = f_m.height()
      
          def boundingRect(self):
              return QRectF(QPointF(0, 0), self.geometry().size())
      
          def setGeometry(self, rect):
              self.prepareGeometryChange()
              QGraphicsLayoutItem.setGeometry(self, rect)
              self.setPos(rect.topLeft())
      
          def sizeHint(self, which, constraint=...):
              return QSizeF(self.width, self.height)
      
          def paint(self, painter, option, widget=None):
              painter.setFont(self.font)
              painter.drawText(self.boundingRect(), self.text)
      
      
      class MyView(QGraphicsView):
          def __init__(self):
              super(MyView, self).__init__()
              scene = QGraphicsScene(self)
              scene.setSceneRect(0, 0, self.width(), self.height())
              self.setScene(scene)
      
              ni = NodeInstance()
              self.scene().addItem(ni)
              ni.setPos(150, 80)
      
      
      class MainWindow(QMainWindow):
          def __init__(self):
              super(MainWindow, self).__init__()
              self.setCentralWidget(MyView())
      
      
      if __name__ == '__main__':
          app = QApplication(sys.argv)
      
          mw = MainWindow()
          mw.show()
      
          sys.exit(app.exec_())
      

      I am heavily stuck and hope someone with a little more experience with these QGraphicsWidgets can help. I could not find any hints regarding these issues in the doc.
      Thanks for answers!

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on 24 Jun 2020, 08:56 last edited by
      #2

      @Niagarer
      I hope I'm right about this --- I stand to be corrected.

      I have done Qt QGraphicsItem stuff, though without using any layouts. However, I'm hoping the layouts is not the issue.

      When you add QGraphicItems on a QGraphicsScene/View, the size of the scene/view grows to accommodate wherever the items are placed. However, when you delete items the scene/view does not automatically shrink down to the minimum to accommodate whatever is left on it, it just stays at the larger size.

      Personally I did not bother, but if I wanted it to shrink back down I would have used the result from https://doc.qt.io/qt-5/qgraphicsscene.html#itemsBoundingRect

      Calculates and returns the bounding rect of all items on the scene. This function works by iterating over all items, and because of this, it can be slow for large scenes.

      to resize the scene/view back down to whatever minimum is now required.

      Does that address your issue?

      N 1 Reply Last reply 24 Jun 2020, 09:53
      0
      • JonBJ JonB
        24 Jun 2020, 08:56

        @Niagarer
        I hope I'm right about this --- I stand to be corrected.

        I have done Qt QGraphicsItem stuff, though without using any layouts. However, I'm hoping the layouts is not the issue.

        When you add QGraphicItems on a QGraphicsScene/View, the size of the scene/view grows to accommodate wherever the items are placed. However, when you delete items the scene/view does not automatically shrink down to the minimum to accommodate whatever is left on it, it just stays at the larger size.

        Personally I did not bother, but if I wanted it to shrink back down I would have used the result from https://doc.qt.io/qt-5/qgraphicsscene.html#itemsBoundingRect

        Calculates and returns the bounding rect of all items on the scene. This function works by iterating over all items, and because of this, it can be slow for large scenes.

        to resize the scene/view back down to whatever minimum is now required.

        Does that address your issue?

        N Offline
        N Offline
        Niagarer
        wrote on 24 Jun 2020, 09:53 last edited by Niagarer
        #3

        @JonB edit: sorry, I don't mean the layout, the view is placed in! I mean the layout of the QGraphicsWidget that's placed inside the scene.

        It is actually just a strange behavior of the geometry updates of the layouts/widgets used (for the NodeInstance.body_widget), the NodeInstance.body_widget does not shrink when removing content, although it does expand when adding stuff. And the second issue must be due to some very weird stretch behavior of QGraphicsLinearLayouts...

        1 Reply Last reply
        0
        • N Offline
          N Offline
          Niagarer
          wrote on 25 Jun 2020, 15:45 last edited by Niagarer
          #4

          The only workaround for the second issue with the stretches so far is rebuilding the affected layout on change. That's possible but ridiculous.
          And it still doesn't solve the first issue, the main layout does not shrink, even if it could and the SizePolicy is set to Minimum like that:

          ...
                  # main widget
                  self.body_widget = QGraphicsWidget(self)
                  self.body_layout = QGraphicsLinearLayout(Qt.Horizontal)
                  self.body_layout.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
                  self.body_layout.setSpacing(30)
          ...
                  self.body_layout.invalidate()
                  print('body min height:', self.body_layout.minimumHeight())
                  print('body actual height:', self.body_layout.geometry().height())
                  # prints for esample
                  # body min height: 84.0
                  # body actual height: 192.0
          

          how!?

          1 Reply Last reply
          0
          • 1 Offline
            1 Offline
            1312
            wrote 25 days ago last edited by
            #5

            14/5000
            So in the end, in what way was it solved?

            1 Reply Last reply
            0

            • Login

            • Login or register to search.
            • First post
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved