Bagaimana cara menambahkan header tetap ke QScrollArea?

Saat ini saya memiliki QScrollArea yang ditentukan oleh:

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)

yang dengan senang hati bersarang di dalam tata letak/widget lain, mengubah ukuran seperti yang diharapkan, dll.

Untuk memberikan judul kolom kisi, saya menggunakan QGridLayout lain yang diposisikan tepat di atas area gulir - ini berfungsi... tetapi terlihat sedikit aneh, bahkan ketika ditata dengan tepat, terutama ketika bilah gulir sesuai permintaan (vertikal) muncul atau menghilang sesuai kebutuhan dan header tidak lagi sejajar dengan kolom grid. Itu hal estetis yang saya tahu... tapi saya agak pemilih ;)

Widget lain ditambahkan/dihapus ke self.results_grid_layout secara terprogram di tempat lain. Baris terakhir di atas baru saja saya tambahkan karena menurut saya akan mudah menggunakan area margin yang dibuat, dokumen untuk status setViewportMargins:

Menetapkan margin di sekitar area gulir. Ini berguna untuk aplikasi seperti spreadsheet dengan baris dan kolom "terkunci". Ruang marginal dibiarkan kosong; letakkan widget di area yang tidak digunakan.

Namun seumur hidup saya tidak dapat menemukan cara untuk mencapai hal ini, dan GoogleFu saya telah meninggalkan saya hari ini, atau hanya ada sedikit informasi/contoh di luar sana tentang cara untuk mencapai hal ini.

Kepala saya memberi tahu saya bahwa saya hanya dapat menetapkan satu widget, dikontrol oleh tata letak (berisi sejumlah widget lain) ke area gulir - seperti yang telah saya lakukan. Jika saya menambahkan QHeaderview misalnya ke baris 0 dari gridlayout, itu hanya akan muncul di bawah margin viewport dan gulir dengan tata letak lainnya? Atau apakah saya melewatkan sesuatu dan tidak dapat melihat kayu di pepohonan?

Saya baru belajar Python/Qt, jadi bantuan, petunjuk, dan/atau contoh apa pun (lebih disukai dengan Python tetapi tidak penting) akan dihargai!


Sunting: Setelah mengikuti saran yang diberikan sejauh ini (menurut saya), saya membuat program pengujian kecil berikut untuk mencobanya:

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

Apakah sesuatu seperti ini adalah metode yang Anda maksudkan? Semuanya berfungsi seperti yang diharapkan persis seperti yang saya cari, kecuali beberapa perilaku awal yang aneh sebelum jendela diubah ukurannya oleh pengguna, setelah ukurannya diubah, semuanya berbaris dan baik-baik saja. Saya mungkin terlalu banyak berpikir lagi atau setidaknya mengabaikan sesuatu... ada pemikiran?


person DazGreen    schedule 21.02.2012    source sumber
comment
Pada program pengujian Anda: mengomentari baris grid_layout.setSizeConstraint, sepertinya menyelesaikan masalah.   -  person ekhumoro    schedule 24.02.2012
comment
Ok terima kasih. Saya yakin saya akan dapat menyelesaikan sendiri masalah kecil yang tersisa (terutama lebar awal header). Bantuan Anda sangat dihargai dan telah menerima jawaban awal Anda karena itu membawa saya ke jalur yang benar. Bersulang.   -  person DazGreen    schedule 24.02.2012


Jawaban (2)


Anda mungkin terlalu memikirkan banyak hal.

Yang perlu Anda lakukan hanyalah menggunakan geometri area pandang area gulir dan margin saat ini untuk menghitung geometri widget apa pun yang ingin Anda tempatkan di margin.

Geometri widget ini juga perlu diperbarui di resizeEvent area gulir.

Jika Anda melihat kode sumber untuk QTableView, saya rasa Anda akan menemukannya menggunakan metode ini untuk mengelola tampilan headernya (atau sesuatu yang sangat mirip).

EDIT

Untuk mengatasi masalah kecil pengubahan ukuran dalam kasus pengujian Anda, saya sarankan Anda membaca Koordinat di dokumen untuk QRect (khususnya, paragraf ketiga dan seterusnya).

Saya bisa mendapatkan pengubahan ukuran yang lebih akurat dengan menulis ulang test case Anda seperti ini:

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_())
person ekhumoro    schedule 21.02.2012
comment
Ahh, menarik, terima kasih. Oke, saya bisa mengetahui cara mendapatkan geometri, margin, dan melakukan perhitungan yang diperlukan untuk mendapatkan ukuran dan posisi yang diperlukan, serta memperbaruinya. Jadi saya tidak perlu menggunakan metode addWidget tata letak untuk menyertakan widget header, dan cukup membuatnya, mengatur geometri, dan menggunakan setParent untuk menjadikannya juga anak tata letak untuk memastikannya dihancurkan bersama dengan grid saat itu tidak diperlukan lagi? Saya memang melihat sumber QTableView, baris 1282 - Saya hanya tahu sedikit C++ - person DazGreen; 22.02.2012
comment
@DazGreen. Header tidak boleh menjadi bagian dari tata letak apa pun. Jadi jadikan saja itu anak dari grid_widget. Sumber QTableView yang saya maksud ada di updateGeometries metode - yang diharapkan akan banyak lebih mudah dipahami. - person ekhumoro; 22.02.2012
comment
Bagus sekali. Segera setelah saya bisa, dan kembali ke mesin pengembangan saya, saya memutarnya dan mempostingnya kembali (juga memberi suara positif dan menerima jawaban ini jika cocok untuk saya dan saya sudah mendapatkan perwakilannya saat itu). Terima kasih atas waktunya. - person DazGreen; 22.02.2012
comment
Telah menambahkan beberapa kode contoh yang dapat dijalankan untuk menguji apa yang Anda katakan. - person DazGreen; 23.02.2012
comment
@DazGreen. Mohon maaf atas keterlambatan dalam merespon dengan baik. Lihat jawaban saya yang diperbarui untuk beberapa perbaikan pada kasus uji Anda. - person ekhumoro; 24.02.2012
comment
Tidak masalah, hanya senang bisa mendapatkan bantuan apa pun - terutama karena ini semua masih merupakan hal baru bagi saya. Saya mengerti apa yang saya lakukan sekarang - saya mengimplementasikan kembali resizeEvent MainWindow daripada membuat subkelas QScrollArea dan melakukannya di sana! Dan tentu saja masuk akal juga untuk memindahkan pengaturan header ke subkelas. Mohon maaf karena mungkin tidak melakukan penelitian yang cukup, dan/atau terlalu padat untuk memahaminya sejak awal, bantuan Anda sangat berharga. - person DazGreen; 26.02.2012
comment
Saya tidak dapat mengaktifkan pengguliran horizontal dalam solusi ini. Menyetel geometri tidak memperhitungkan tampilan area gulir ke dalam konten yang digulir. Ini berfungsi dengan baik ketika tidak ada pengguliran. - person Jonas Hultén; 01.04.2018

Saya memiliki masalah serupa dan menyelesaikannya dengan sedikit berbeda. Daripada menggunakan satu QScrollArea saya menggunakan dua dan meneruskan gerakan gulir bawah daerah ke yang paling atas. Apa yang dilakukan kode di bawah ini adalah

  1. Ini menciptakan dua widget QScrollArea di QVBoxLayout.
  2. Ini menonaktifkan visibilitas bilah gulir di QScrollArea atas dan menetapkan ketinggian tetap.
  3. Menggunakan sinyal valueChanged dari bilah gulir horizontal di bagian bawah QScrollArea dimungkinkan untuk "meneruskan" nilai bilah gulir horizontal dari QScrollArea bawah ke atas sehingga menghasilkan header tetap di bagian atas jendela.

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)

Jendela yang dihasilkan dari kode di atas.

person Woltan    schedule 29.09.2017
comment
Saya memilih jawaban ini karena yang memiliki trik margin tidak berfungsi ketika konten scrollarea perlu digulir secara horizontal. Ini bekerja dengan sempurna. - person Jonas Hultén; 01.04.2018