如何在QScrollArea中使用户可调整大小的QWidget?

时间:2019-02-12 01:14:48

标签: qt resize qscrollarea qsplitter

要求:

  • 包含几个小部件的QScrollArea。
  • 每个窗口小部件应由用户分别调整大小(水平或垂直,但不能同时调整两个方向)。
  • 用户调整窗口小部件的大小不应更改其他窗口小部件的大小。它应该增加/减少QScrollArea中的可用区域。

使用QSplitter并没有帮助,因为QSplitter保持固定宽度,并且调整其任何 splits 的大小都会导致其他 splits 缩小。 [1] [2] [3]

当然可以通过创建自定义窗口小部件,添加用于指示可拖动区域的可视条以及侦听拖动事件以通过代码调整窗口小部件大小来完成此操作。有没有更简单的解决方案?

1 个答案:

答案 0 :(得分:0)

我遇到了同样的问题。想出了一个讨厌的黑客:

  • 在 QScrollArea 中放置一个 QSplitter
  • 存储所有 QSplitter 子部件的旧尺寸
  • 当 QSplitterHandle 移动时(即在 SIGNAL splitterMoved() 上)
    • 计算更改后的子小部件增长/缩小了多少
    • 将整个 QSplitter 的最小大小更改为该数量
    • 仅更新我为更改的子小部件存储的大小
    • 将 QSplitter 子部件的大小设置为我存储的大小。

它对我有用(目前)。但是它很笨拙,并且其中有一些令人讨厌的神奇数字可以使其发挥作用。 因此,如果有人提出更好的解决方案,那就太好了! 无论如何 - 如果有人觉得它有用,代码(在 Python3 和 PySide2 中)

    import sys
    
    from PySide2.QtCore import Qt
    from PySide2.QtWidgets import QWidget, QScrollArea, QSplitter
    from PySide2.QtWidgets import QApplication, QMainWindow, QLabel, QFrame
      
        
    class ScrollSplitter(QScrollArea):
        def __init__(self, orientation, parent=None):
            super().__init__(parent)
    
            # Orientation = Qt.Horizontal or Qt.Vertical
            self.orientation = orientation
            
            # Keep track of all the sizes of all the QSplitter's child widgets BEFORE the latest resizing,
            # so that we can reinstate all of them (except the widget that we wanted to resize)
            self.old_sizes = []
            self._splitter = QSplitter(orientation, self)
            
            # TODO - remove magic number. This is required to avoid zero size on first viewing.
            if orientation == Qt.Horizontal :
                self._splitter.setMinimumWidth(500)
            else :
                self._splitter.setMinimumHeight(500)
            
            # In a default QSplitter, the bottom widget doesn't have a drag handle below it.
            # So create an empty widget which will always sit at the bottom of the splitter,
            # so that all of the user widgets have a handle below them
            #
            # I tried playing with the max width/height of this bottom widget - but the results were crummy. So gave up.
            bottom_widget = QWidget(self)
            self._splitter.addWidget(bottom_widget)
            
            # Use the QSplitter.splitterMoved(pos, index) signal, emitted every time the splitter's handle is moved.
            # When this signal is emitted, the splitter has already resized all child widgets to keep its total size constant.
            self._splitter.splitterMoved.connect(self.resize_splitter)
    
            # Configure the scroll area.
            if orientation == Qt.Horizontal :
                self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
                self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
            else :
                self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
                self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            
            self.setWidgetResizable(True)
            self.setWidget(self._splitter)
                    
            
        # Called every time a splitter handle is moved
        #   We basically undo the QSplitter resizing of all the other children,
        #   and resize the QSplitter (using setMinimumHeight() or setMinimumWidth() ) instead.
        def resize_splitter(self, pos, index):
            # NOTE: index refs the child widget AFTER the moved splitter handle.
            #       pos is position relative to the top of the splitter, not top of the widget.
            
            # TODO - find a better way to initialise the old_sizes list.
            # Ideally whenever we add/remove a widget.
            if not self.old_sizes :
                self.old_sizes = self._splitter.sizes()
                
            # The 'index' arg references the QWidget below the moved splitter handle.
            # We want to change the QWidget above the moved splitter handle, so...
            index_above = index - 1
            
            # Careful with the current sizes - QSplitter has already mucked about with the sizes of all other child widgets
            current_sizes = self._splitter.sizes()
            
            # The only change in size we are interested in is the size of the widget above the splitter
            size_change = current_sizes[index_above] - self.old_sizes[index_above]
    
            # We want to keep the old sizes of all other widgets, and just resize the QWidget above the splitter.
            # Update our old_list to hold the sizes we want for all child widgets
            self.old_sizes[index_above] = current_sizes[index_above]
            
            # Increase/decrease the(minimum) size of the QSplitter object to accommodate the total new, desired size of all of its child widgets (without resizing most of them)
            if self.orientation == Qt.Horizontal :
                self._splitter.setMinimumWidth(max(self._splitter.minimumWidth() + size_change, 0))
            else :
                self._splitter.setMinimumHeight(max(self._splitter.minimumHeight() + size_change, 0)) 
    
            # and set the sizes of all the child widgets back to their old sizes, now that the QSplitter has grown/shrunk to accommodate them without resizing them
            self._splitter.setSizes(self.old_sizes)
            #print(self.old_sizes)
    
    
        # Add a widget at the bottom of the user widgets
        def addWidget(self, widget):
            self._splitter.insertWidget(self._splitter.count()-1, widget)
            
        # Insert a widget at 'index' in the splitter.
        # If the widget is already in the splitter, it will be moved.
        # If the index is invalid, widget will be appended to the bottom of the (user) widgets
        def insertWidget(self, index, widget):
            if index >= 0 and index < (self._splitter.count() - 1) :
                self._splitter.insertWidget(index, widget)
            
            self.addWidget(widget)
            
        # Replace a the user widget at 'index' with this widget. Returns the replaced widget
        def replaceWidget(self, index, widget):
            if index >= 0 and index < (self._splitter.count() - 1) :
                return self._splitter.replaceWidget(index, widget)
            
        # Return the number of (user) widgets
        def count(self):
            return self._splitter.count() - 1
            
        # Return the index of a user widget, or -1 if not found.
        def indexOf(self, widget):
            return self._splitter.indexOf(widget)
        
        # Return the (user) widget as a given index, or None if index out of range.
        def widget(self, index):
            if index >= 0 and index < (self._splitter.count() - 1) :
                return self._splitter.widget(index)
            return None
    
        # Save the splitter's state into a ByteArray.
        def saveState(self):
            return self._splitter.saveState()
        
        # Restore the splitter's state from a ByteArray
        def restoreState(self, s):
            return self._splitter.restoreState(s)
    
    
    
    class MainWindow(QMainWindow):
        def __init__(self):
            super().__init__()
            
            self.setWindowTitle("ScrollSplitter Test")
            self.resize(640, 400)
            
            self.splitter = ScrollSplitter(Qt.Vertical, self)
            self.setCentralWidget(self.splitter)    
            
            for color in ["Widget 0", "Widget 1", "Widget 2", "Some other Widget"]:
                widget = QLabel(color)
                widget.setFrameStyle(QFrame.Panel | QFrame.Raised)
                self.splitter.addWidget(widget)
                   
    app = QApplication(sys.argv)
    
    window = MainWindow()
    window.show()
    
    app.exec_()