Pregunta

Actualmente tengo un QSCrollarea definido por:

self.results_grid_scrollarea = QScrollArea()
self.results_grid_widget = QWidget()
self.results_grid_layout = QGridLayout()
self.results_grid_layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
self.results_grid_widget.setLayout(self.results_grid_layout)
self.results_grid_scrollarea.setWidgetResizable(True)
self.results_grid_scrollarea.setWidget(self.results_grid_widget)
self.results_grid_scrollarea.setViewportMargins(0,20,0,0)

que se sienta muy felizmente anidado en otros diseños/widgets, cambia de tamaño como se esperaba, etc.

Para proporcionar encabezados para las columnas de la cuadrícula, estoy usando otro QGridLayout colocado directamente sobre el área de desplazamiento: esto funciona ... pero parece un poco extraño, incluso cuando está diseñado adecuadamente, especialmente cuando la barra de desplazamiento (vertical) a pedido aparece o desaparece o desaparece según sea necesario y los encabezados ya no se alinean correctamente con las columnas de la cuadrícula. Es una cosa estética que sé ... pero soy un poco exigente;)

Otros widgets se agregan/eliminan al self.results_grid_layout programáticamente en otro lugar. La última línea anterior he agregado recientemente, ya que pensé que sería fácil usar el área de margen creada, el documentos Para setViewportMargins State:

Establece márgenes alrededor del área de desplazamiento. Esto es útil para aplicaciones como hojas de cálculo con filas y columnas "bloqueadas". El espacio marginal se deja en blanco; poner widgets en el área no utilizada.

Pero por mi vida no puedo resolver cómo lograr esto, y mi Googlefu me ha abandonado hoy, o hay poca información/ejemplos sobre cómo lograr esto.

Mi cabeza me dice que puedo asignar solo un widget, controlado por un diseño (que contiene cualquier número de otros widgets) al Scrollarea, como lo he hecho. Si agrego, digamos un Qheaderview, por ejemplo, para la fila 0 del GridLayout, ¿aparecerá debajo del margen de la ventana gráfica y se desplazará con el resto del diseño? ¿O me estoy perdiendo algo y simplemente no puedo ver la madera para los árboles?

Solo estoy aprendiendo Python/Qt, por lo que cualquier ayuda, consejos y/o ejemplos (preferiblemente con Python pero no esencial) ¡sería apreciado!


Editar: Habiendo seguido el consejo dado hasta ahora (creo), se me ocurrió el siguiente programa de prueba para probar las cosas:

import sys
from PySide.QtCore import *
from PySide.QtGui import *

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setMinimumSize(640, 480)

        self.container_widget = QWidget()
        self.container_layout = QVBoxLayout()
        self.container_widget.setLayout(self.container_layout)
        self.setCentralWidget(self.container_widget)

        self.info_label = QLabel(
            "Here you can see the problem.... I hope!\n"
            "Once the window is resized everything behaves itself.")
        self.info_label.setWordWrap(True)

        self.headings_widget = QWidget()
        self.headings_layout = QGridLayout()
        self.headings_widget.setLayout(self.headings_layout)
        self.headings_layout.setContentsMargins(1,1,0,0)

        self.heading_label1 = QLabel("Column 1")
        self.heading_label1.setContentsMargins(16,0,0,0)
        self.heading_label2 = QLabel("Col 2")
        self.heading_label2.setAlignment(Qt.AlignCenter)
        self.heading_label2.setMaximumWidth(65)
        self.heading_label3 = QLabel("Column 3")
        self.heading_label3.setContentsMargins(8,0,0,0)
        self.headings_layout.addWidget(self.heading_label1,0,0)
        self.headings_layout.addWidget(self.heading_label2,0,1)
        self.headings_layout.addWidget(self.heading_label3,0,2)
        self.headings_widget.setStyleSheet(
            "background: green; border-bottom: 1px solid black;" )

        self.grid_scrollarea = QScrollArea()
        self.grid_widget = QWidget()
        self.grid_layout = QGridLayout()
        self.grid_layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
        self.grid_widget.setLayout(self.grid_layout)
        self.grid_scrollarea.setWidgetResizable(True)
        self.grid_scrollarea.setWidget(self.grid_widget)
        self.grid_scrollarea.setViewportMargins(0,30,0,0)
        self.headings_widget.setParent(self.grid_scrollarea)
        ### Add some linedits to the scrollarea just to test
        rows_to_add = 10
        ## Setting the above to a value greater than will fit in the initial
        ## window will cause the lineedits added below to display correctly,
        ## however - using the 10 above, the lineedits do not expand to fill
        ## the scrollarea's width until you resize the window horizontally.
        ## What's the best way to fix this odd initial behaviour?
        for i in range(rows_to_add):
            col1 = QLineEdit()
            col2 = QLineEdit()
            col2.setMaximumWidth(65)
            col3 = QLineEdit()
            row = self.grid_layout.rowCount()
            self.grid_layout.addWidget(col1,row,0)
            self.grid_layout.addWidget(col2,row,1)
            self.grid_layout.addWidget(col3,row,2)
        ### Define Results group to hold the above sections
        self.test_group = QGroupBox("Results")
        self.test_layout = QVBoxLayout()
        self.test_group.setLayout(self.test_layout)
        self.test_layout.addWidget(self.info_label)
        self.test_layout.addWidget(self.grid_scrollarea)
        ### Add everything to the main layout
        self.container_layout.addWidget(self.test_group)


    def resizeEvent(self, event):
        scrollarea_vpsize = self.grid_scrollarea.viewport().size()
        scrollarea_visible_size = self.grid_scrollarea.rect()
        desired_width = scrollarea_vpsize.width()
        desired_height = scrollarea_visible_size.height()
        desired_height =  desired_height - scrollarea_vpsize.height()
        new_geom = QRect(0,0,desired_width+1,desired_height-1)
        self.headings_widget.setGeometry(new_geom)


def main():
    app = QApplication(sys.argv)
    form = MainWindow()
    form.show()
    app.exec_()

if __name__ == '__main__':
   main()

¿Es algo en este sentido el método al que apuntas? Todo funciona tan esperado como es exactamente lo que buscaba, excepto por algún comportamiento inicial extraño antes de que el usuario cambie el tamaño de la ventana, una vez que se redimensiona todo se alinea y está bien. Probablemente estoy pensando demasiado o al menos pasar por alto algo ... ¿alguna idea?

¿Fue útil?

Solución

Puede estar pensando demasiado.

Todo lo que necesita hacer es utilizar la geometría de la ventana gráfica de Scrollarea y los márgenes actuales para calcular la geometría de cualquier widgets que desee colocar en los márgenes.

La geometría de estos widgets también necesitaría actualizarse en el resizeEvent del scrollarea.

Si observa el código fuente para QTableView, Creo que encontrará que utiliza este método para administrar sus vistas de encabezado (o algo muy similar).

EDITAR

Para lidiar con los problemas de cambio de tamaño menores en su caso de prueba, le aconsejaría que lea el Coordenadas sección en los documentos para QRect (en particular, el tercer párrafo en adelante).

Pude obtener un cambio de tamaño más preciso reescribiendo su caso de prueba como este:

import sys
from PySide.QtCore import *
from PySide.QtGui import *

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setMinimumSize(640, 480)
        self.container_widget = QWidget()
        self.container_layout = QVBoxLayout()
        self.container_widget.setLayout(self.container_layout)
        self.setCentralWidget(self.container_widget)
        self.grid_scrollarea = ScrollArea(self)
        self.test_group = QGroupBox("Results")
        self.test_layout = QVBoxLayout()
        self.test_group.setLayout(self.test_layout)
        self.test_layout.addWidget(self.grid_scrollarea)
        self.container_layout.addWidget(self.test_group)

class ScrollArea(QScrollArea):
    def __init__(self, parent=None):
        QScrollArea.__init__(self, parent)
        self.grid_widget = QWidget()
        self.grid_layout = QGridLayout()
        self.grid_widget.setLayout(self.grid_layout)
        self.setWidgetResizable(True)
        self.setWidget(self.grid_widget)
        # save the margin values
        self.margins = QMargins(0, 30, 0, 0)
        self.setViewportMargins(self.margins)
        self.headings_widget = QWidget(self)
        self.headings_layout = QGridLayout()
        self.headings_widget.setLayout(self.headings_layout)
        self.headings_layout.setContentsMargins(1,1,0,0)
        self.heading_label1 = QLabel("Column 1")
        self.heading_label1.setContentsMargins(16,0,0,0)
        self.heading_label2 = QLabel("Col 2")
        self.heading_label2.setAlignment(Qt.AlignCenter)
        self.heading_label2.setMaximumWidth(65)
        self.heading_label3 = QLabel("Column 3")
        self.heading_label3.setContentsMargins(8,0,0,0)
        self.headings_layout.addWidget(self.heading_label1,0,0)
        self.headings_layout.addWidget(self.heading_label2,0,1)
        self.headings_layout.addWidget(self.heading_label3,0,2)
        self.headings_widget.setStyleSheet(
            "background: green; border-bottom: 1px solid black;" )
        rows_to_add = 10
        for i in range(rows_to_add):
            col1 = QLineEdit()
            col2 = QLineEdit()
            col2.setMaximumWidth(65)
            col3 = QLineEdit()
            row = self.grid_layout.rowCount()
            self.grid_layout.addWidget(col1,row,0)
            self.grid_layout.addWidget(col2,row,1)
            self.grid_layout.addWidget(col3,row,2)

    def resizeEvent(self, event):
        rect = self.viewport().geometry()
        self.headings_widget.setGeometry(
            rect.x(), rect.y() - self.margins.top(),
            rect.width() - 1, self.margins.top())
        QScrollArea.resizeEvent(self, event)

if __name__ == '__main__':

    app = QApplication(sys.argv)
    form = MainWindow()
    form.show()
    sys.exit(app.exec_())

Otros consejos

Tuve un problema similar y lo resolví un poco diferente. En lugar de usar uno Qscrollarea Utilizo dos y hacia adelante un movimiento del área de desplazamiento inferior a la parte superior. Lo que hace el código a continuación es

  1. Crea dos widgets QSCrollarea en un QVboxLayout.
  2. Desactiva la visibilidad de las barras de desplazamiento de la Top Qscrollarea y le asigna una altura fija.
  3. Utilizando el valuechanged Señal de la barra de desplazamiento horizontal de los Qscrollarea inferiores Es posible "avanzar" el valor de la barra de desplazamiento horizontal desde el Qscrolllarea inferior a la parte superior, lo que da como resultado un encabezado fijo en la parte superior de la ventana.

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        widget = QWidget()
        self.setCentralWidget(widget)

        vLayout = QVBoxLayout()
        widget.setLayout(vLayout)

        # TOP
        scrollAreaTop = QScrollArea()
        scrollAreaTop.setWidgetResizable(True)
        scrollAreaTop.setFixedHeight(30)
        scrollAreaTop.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scrollAreaTop.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scrollAreaTop.setWidget(QLabel(" ".join([str(i) for i in range(100)])))

        # BOTTOM
        scrollAreaBottom = QScrollArea()
        scrollAreaBottom.setWidgetResizable(True)
        scrollAreaBottom.setWidget(QLabel("\n".join([" ".join([str(i) for i in range(100)]) for _ in range(10)])))
        scrollAreaBottom.horizontalScrollBar().valueChanged.connect(lambda value: scrollAreaTop.horizontalScrollBar().setValue(value))

        vLayout.addWidget(scrollAreaTop)
        vLayout.addWidget(scrollAreaBottom)

Window resulting from above code.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top