จะเพิ่มส่วนหัวคงที่ให้กับ QScrollArea ได้อย่างไร

ขณะนี้ฉันมี QScrollArea ที่กำหนดโดย:

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)

ซึ่งวางซ้อนกันอย่างมีความสุขภายในเลย์เอาต์/วิดเจ็ตอื่น ๆ ปรับขนาดตามที่คาดไว้ ฯลฯ

เพื่อจัดเตรียมส่วนหัวสำหรับคอลัมน์กริด ฉันกำลังใช้ QGridLayout อื่นที่อยู่เหนือพื้นที่เลื่อนโดยตรง ซึ่งใช้งานได้... แต่ดูแปลก ๆ เล็กน้อย แม้ว่าจะจัดสไตล์อย่างเหมาะสม โดยเฉพาะอย่างยิ่งเมื่อแถบเลื่อนตามความต้องการ (แนวตั้ง) ปรากฏขึ้นหรือหายไป ตามความจำเป็นและส่วนหัวไม่สอดคล้องกับคอลัมน์กริดอีกต่อไป ฉันรู้ว่าเป็นสุนทรียศาสตร์...แต่ฉันก็ค่อนข้างจู้จี้จุกจิก ;)

วิดเจ็ตอื่น ๆ จะถูกเพิ่ม/ลบไปยัง self.results_grid_layout โดยทางโปรแกรมที่อื่น บรรทัดสุดท้ายด้านบนที่ฉันเพิ่งเพิ่มไปเมื่อเร็ว ๆ นี้เนื่องจากฉันคิดว่ามันจะง่ายต่อการใช้พื้นที่ระยะขอบที่สร้างขึ้น เอกสาร สำหรับสถานะ setViewportMargins:

ตั้งค่าระยะขอบรอบๆ พื้นที่การเลื่อน สิ่งนี้มีประโยชน์สำหรับแอปพลิเคชัน เช่น สเปรดชีตที่มีแถวและคอลัมน์ "ล็อก" พื้นที่ขอบเว้นว่างไว้ วางวิดเจ็ตในพื้นที่ที่ไม่ได้ใช้

แต่ฉันไม่สามารถหาวิธีการบรรลุเป้าหมายนี้ได้ตลอดชีวิต และ GoogleFu ของฉันก็ได้ละทิ้งฉันในวันนี้ หรือมีข้อมูล/ตัวอย่างเพียงเล็กน้อยเกี่ยวกับวิธีการบรรลุเป้าหมายนี้

หัวของฉันกำลังบอกฉันว่าฉันสามารถกำหนดวิดเจ็ตได้เพียงอันเดียวซึ่งควบคุมโดยเลย์เอาต์ (ที่มีวิดเจ็ตอื่น ๆ จำนวนเท่าใดก็ได้) ให้กับพื้นที่เลื่อน - อย่างที่ฉันเคยทำไปแล้ว ถ้าฉันเพิ่มพูด QHeaderview เช่นแถว 0 ของ gridlayout มันจะปรากฏใต้ระยะขอบของวิวพอร์ตและเลื่อนไปพร้อมกับเค้าโครงที่เหลือ หรือฉันขาดอะไรบางอย่างไปแต่มองไม่เห็นเนื้อไม้แทนต้นไม้?

ฉันแค่กำลังเรียนรู้ Python/Qt ดังนั้นความช่วยเหลือ พอยน์เตอร์ และ/หรือตัวอย่าง (ควรเป็น Python แต่ไม่จำเป็น) จะได้รับการชื่นชม!


แก้ไข: หลังจากทำตามคำแนะนำที่ให้ไว้ (ฉันคิดว่า) ฉันจึงได้โปรแกรมทดสอบเล็กๆ น้อยๆ ต่อไปนี้เพื่อทดลองใช้:

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

มีบางอย่างตามบรรทัดเหล่านี้เป็นวิธีการที่คุณชี้ไปหรือไม่? ทุกอย่างทำงานตามที่คาดไว้เหมือนกับสิ่งที่ฉันทำ ยกเว้นพฤติกรรมเริ่มต้นแปลก ๆ ก่อนที่ผู้ใช้จะปรับขนาดหน้าต่าง เมื่อปรับขนาดทุกอย่างแล้ว ทุกอย่างก็เรียบร้อยดี ฉันอาจจะคิดมากอีกครั้งหรืออย่างน้อยก็มองข้ามบางสิ่งบางอย่าง... มีความคิดอะไรบ้าง?


person DazGreen    schedule 21.02.2012    source แหล่งที่มา
comment
ในโปรแกรมทดสอบของคุณ: การใส่ความคิดเห็นในบรรทัด grid_layout.setSizeConstraint ดูเหมือนว่าจะแก้ปัญหาได้   -  person ekhumoro    schedule 24.02.2012
comment
โอเคขอบคุณ. ฉันแน่ใจว่าฉันจะสามารถแก้ไขปัญหาเล็กๆ น้อยๆ ที่เหลือได้ด้วยตัวเอง (ความกว้างเริ่มต้นของส่วนหัวเป็นหลัก) ความช่วยเหลือของคุณได้รับการชื่นชมอย่างมากและยอมรับคำตอบเดิมของคุณเนื่องจากมันทำให้ฉันเดินไปถูกทาง ไชโย   -  person DazGreen    schedule 24.02.2012


คำตอบ (2)


คุณอาจจะคิดมากไปสักหน่อย

สิ่งที่คุณต้องทำคือใช้เรขาคณิตของวิวพอร์ตของพื้นที่เลื่อนและระยะขอบปัจจุบันเพื่อคำนวณเรขาคณิตของวิดเจ็ตใดๆ ที่คุณต้องการวางไว้ในระยะขอบ

เรขาคณิตของวิดเจ็ตเหล่านี้จะต้องได้รับการอัปเดตใน resizeEvent ของพื้นที่เลื่อนด้วย

หากคุณดูซอร์สโค้ดสำหรับ QTableView ฉันคิดว่าคุณจะพบว่ามันใช้วิธีนี้เพื่อจัดการมุมมองส่วนหัว (หรือบางอย่างที่คล้ายกันมาก)

แก้ไข

หากต้องการจัดการกับปัญหาการปรับขนาดเล็กน้อยในกรณีทดสอบของคุณ ฉันขอแนะนำให้คุณอ่าน พิกัด ในเอกสารสำหรับ QRect (โดยเฉพาะย่อหน้าที่สามเป็นต้นไป)

ฉันสามารถปรับขนาดได้แม่นยำยิ่งขึ้นโดยเขียนกรณีทดสอบของคุณใหม่ดังนี้:

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
อ่า น่าสนใจ ขอบคุณครับ ตกลง ฉันสามารถหาวิธีรับเรขาคณิต ระยะขอบ และทำการคำนวณที่จำเป็นเพื่อให้ได้ขนาดและตำแหน่งที่ต้องการ พร้อมทั้งอัปเดตสิ่งเหล่านี้ ดังนั้นฉันจึงไม่จำเป็นต้องใช้เมธอด addWidget ของเลย์เอาต์เพื่อรวมวิดเจ็ตส่วนหัว และสามารถสร้างมันขึ้นมา ตั้งค่าเรขาคณิต และใช้ setParent เพื่อทำให้มันเป็นลูกของเลย์เอาต์ด้วยเพื่อให้แน่ใจว่ามันจะถูกทำลายไปพร้อมกับกริดเมื่อ ไม่ต้องการอีกต่อไป? ฉันได้ดูแหล่งที่มาของ QTableView แล้วบรรทัด 1282 - ฉันรู้ C++ น้อยมาก - person DazGreen; 22.02.2012
comment
@ดาซกรีน. ส่วนหัวไม่ควรเป็นส่วนหนึ่งของเค้าโครงใดๆ เลย ดังนั้นทำให้เป็นลูกของ grid_widget แหล่งที่มา QTableView ที่ฉันอ้างถึงอยู่ใน updateGeometries วิธีการ - ซึ่งหวังว่าคุณจะพบว่า มาก ง่ายต่อการเข้าใจ - person ekhumoro; 22.02.2012
comment
ยอดเยี่ยม. ทันทีที่ฉันสามารถและกลับมาที่เครื่องพัฒนาของฉัน ฉันจะหมุนวนและโพสต์กลับ (เช่น โหวตเห็นด้วยและยอมรับคำตอบนี้ ถ้ามันเหมาะกับฉัน และฉันก็ได้รับตัวแทนแล้วในตอนนั้น) ขอบคุณที่สละเวลา. - person DazGreen; 22.02.2012
comment
ได้เพิ่มโค้ดตัวอย่างที่รันได้บางส่วนเพื่อทดสอบสิ่งที่คุณพูด - person DazGreen; 23.02.2012
comment
@ดาซกรีน. ขออภัยที่ล่าช้าในการตอบกลับอย่างถูกต้อง ดูคำตอบที่อัปเดตของฉันสำหรับการปรับแต่งกรณีทดสอบของคุณ - person ekhumoro; 24.02.2012
comment
ไม่มีปัญหา แค่ดีใจที่ได้รับความช่วยเหลือ โดยเฉพาะอย่างยิ่งเนื่องจากทั้งหมดนี้ยังค่อนข้างใหม่สำหรับฉัน ฉันเห็นสิ่งที่ฉันทำตอนนี้ - ฉันใช้งาน resizeEvent ของ MainWindow อีกครั้งแทนที่จะใช้คลาสย่อย QScrollArea และทำที่นั่น! และแน่นอนว่าการย้ายการตั้งค่าส่วนหัวไปยังคลาสย่อยก็สมเหตุสมผลเช่นกัน ขออภัยที่บางทีอาจไม่ได้ค้นคว้ามากพอ และ/หรือความหนาแน่นเกินกว่าที่จะทำได้ตั้งแต่เริ่มแรก ความช่วยเหลือของคุณมีค่าอย่างยิ่ง - person DazGreen; 26.02.2012
comment
ฉันไม่สามารถเลื่อนแนวนอนเพื่อทำงานในโซลูชันนี้ได้ การตั้งค่าเรขาคณิตไม่คำนึงถึงมุมมองของพื้นที่เลื่อนในเนื้อหาที่เลื่อน มันทำงานได้ดีเมื่อไม่มีการเลื่อน - person Jonas Hultén; 01.04.2018

ฉันมีปัญหาที่คล้ายกันและแก้ไขแตกต่างออกไปเล็กน้อย แทนที่จะใช้ QScrollArea หนึ่งรายการ ฉันใช้สองรายการและส่งต่อการเคลื่อนไหวของการเลื่อนด้านล่าง พื้นที่ไปด้านบน สิ่งที่รหัสด้านล่างนี้ทำคือ

  1. มันสร้างวิดเจ็ต QScrollArea สองอันใน QVBoxLayout
  2. โดยจะปิดการมองเห็นแถบเลื่อนของ QScrollArea ด้านบน และกำหนดความสูงคงที่
  3. การใช้สัญญาณ valueChanged ของแถบเลื่อนแนวนอนด้านล่าง QScrollArea เป็นไปได้ที่จะ "ส่งต่อ" ค่าแถบเลื่อนแนวนอนจาก QScrollArea ที่ต่ำกว่าไปยังด้านบนสุดส่งผลให้ส่วนหัวคงที่ที่ด้านบนของหน้าต่าง

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)

หน้าต่างที่เกิดจากโค้ดด้านบน

person Woltan    schedule 29.09.2017
comment
ฉันเลือกคำตอบนี้เพราะคำตอบที่มีเคล็ดลับระยะขอบไม่ทำงานเมื่อจำเป็นต้องเลื่อนเนื้อหาพื้นที่เลื่อนในแนวนอน มันทำงานได้อย่างสมบูรณ์แบบ - person Jonas Hultén; 01.04.2018