You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于PySide6的Qt模型/视图编程:实现数据列垂直成对显示的方案咨询

Best Approaches for Vertical Pair Display in PySide6 Model-View

Hey there! Let's figure out the best way to achieve this vertical pair display (A above B, repeated for each data row) in PySide6's model-view framework. Your current QScrollArea + QDataWidgetMapper approach works, but it's not the most scalable or idiomatic—Qt's model-view system has cleaner, more maintainable solutions tailored for this kind of layout. Here are the top three options, ordered by ease of implementation and alignment with Qt's design principles:

1. Proxy Model + QTableView (Simplest, Reuses Existing Views)

This approach leverages a proxy model to transform your original 2-column data into a single-column view where each original row becomes two rows (A followed by B). You can use a standard QTableView with minimal changes, which means you get all its built-in features (selection, editing, sorting) for free.

Example Code:

from PySide6.QtCore import QAbstractProxyModel, QModelIndex, Qt
from PySide6.QtWidgets import QApplication, QTableView, QWidget, QVBoxLayout
from PySide6.QtGui import QStandardItemModel, QStandardItem

class PairProxyModel(QAbstractProxyModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        
    def rowCount(self, parent=QModelIndex()):
        if parent.isValid():
            return 0
        # Double the row count: each original row becomes two proxy rows
        return self.sourceModel().rowCount() * 2
    
    def columnCount(self, parent=QModelIndex()):
        return 1  # Only show one column
    
    def mapFromSource(self, sourceIndex):
        if not sourceIndex.isValid():
            return QModelIndex()
        # Map source column 0 (A) to even proxy rows, column 1 (B) to odd
        if sourceIndex.column() == 0:
            return self.createIndex(sourceIndex.row() * 2, 0, sourceIndex.internalPointer())
        elif sourceIndex.column() == 1:
            return self.createIndex(sourceIndex.row() * 2 + 1, 0, sourceIndex.internalPointer())
        return QModelIndex()
    
    def mapToSource(self, proxyIndex):
        if not proxyIndex.isValid():
            return QModelIndex()
        # Reverse the mapping: even proxy rows go to source column 0, odd to 1
        source_row = proxyIndex.row() // 2
        source_col = 0 if proxyIndex.row() % 2 == 0 else 1
        return self.sourceModel().index(source_row, source_col)
    
    def index(self, row, column, parent=QModelIndex()):
        if parent.isValid() or column != 0:
            return QModelIndex()
        source_row = row // 2
        source_col = 0 if row % 2 == 0 else 1
        source_index = self.sourceModel().index(source_row, source_col)
        return self.createIndex(row, column, source_index.internalPointer())
    
    def parent(self, index):
        return QModelIndex()  # No hierarchy, so return invalid index
    
    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None
        # Delegate data requests to the source model
        source_index = self.mapToSource(index)
        return self.sourceModel().data(source_index, role)

if __name__ == "__main__":
    app = QApplication([])
    window = QWidget()
    layout = QVBoxLayout(window)
    
    # Create your original 2-column model
    source_model = QStandardItemModel()
    source_model.setHorizontalHeaderLabels(["A", "B"])
    for i in range(5):
        source_model.appendRow([QStandardItem(f"A{i+1}"), QStandardItem(f"B{i+1}")])
    
    # Attach the proxy model
    proxy_model = PairProxyModel()
    proxy_model.setSourceModel(source_model)
    
    # Use a standard QTableView
    view = QTableView()
    view.setModel(proxy_model)
    view.horizontalHeader().hide()  # Hide the single-column header
    layout.addWidget(view)
    
    window.show()
    app.exec()

2. QListView + Custom Delegate (Most Flexible for Styling)

If you want full control over how each A/B pair looks (custom fonts, colors, spacing), a custom item delegate for QListView is perfect. This approach keeps your model intact and lets you draw each pair as a single list item with vertical layout.

Example Code:

from PySide6.QtCore import Qt, QSize
from PySide6.QtWidgets import QApplication, QListView, QWidget, QVBoxLayout, QStyle
from PySide6.QtGui import QStandardItemModel, QStandardItem, QStyledItemDelegate, QPainter, QTextOption

class PairDelegate(QStyledItemDelegate):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.item_height = 40  # Height for each individual A/B line
    
    def sizeHint(self, option, index):
        # Each list item takes twice the height of a single line
        return QSize(option.rect.width(), self.item_height * 2)
    
    def paint(self, painter, option, index):
        painter.save()
        
        # Draw background for selected items
        if option.state & QStyle.State_Selected:
            painter.fillRect(option.rect, option.palette.highlight())
        
        # Fetch A and B values from the model
        a_index = index.sibling(index.row(), 0)
        b_index = index.sibling(index.row(), 1)
        a_text = index.model().data(a_index, Qt.DisplayRole) or ""
        b_text = index.model().data(b_index, Qt.DisplayRole) or ""
        
        # Define rectangles for A and B
        a_rect = option.rect.adjusted(10, 0, -10, -self.item_height)
        b_rect = option.rect.adjusted(10, self.item_height, -10, 0)
        
        # Set text alignment (left-aligned, vertically centered)
        text_option = QTextOption(Qt.AlignLeft | Qt.AlignVCenter)
        
        # Set text color based on selection state
        text_color = option.palette.highlightedText().color() if option.state & QStyle.State_Selected else option.palette.text().color()
        painter.setPen(text_color)
        
        # Draw the text
        painter.drawText(a_rect, a_text, text_option)
        painter.drawText(b_rect, b_text, text_option)
        
        painter.restore()

if __name__ == "__main__":
    app = QApplication([])
    window = QWidget()
    layout = QVBoxLayout(window)
    
    # Original 2-column model
    source_model = QStandardItemModel()
    source_model.setHorizontalHeaderLabels(["A", "B"])
    for i in range(5):
        source_model.appendRow([QStandardItem(f"A{i+1}"), QStandardItem(f"B{i+1}")])
    
    # QListView with custom delegate
    view = QListView()
    view.setItemDelegate(PairDelegate())
    view.setModel(source_model)
    view.setViewMode(QListView.ListMode)
    view.setUniformItemSizes(True)  # Ensures consistent item heights
    
    layout.addWidget(view)
    window.show()
    app.exec()

3. Custom QAbstractItemView (Full Layout Control)

If you need a completely custom layout (e.g., arranging pairs in a grid instead of a single column), you can subclass QAbstractItemView and override methods to control how items are positioned and rendered. This is the most powerful option but requires more code, so only use it if the above two don't fit your needs.

Key Methods to Override:

  • visualRect(): Returns the on-screen rectangle for a given model index
  • indexAt(): Returns the model index at a given screen position
  • scrollTo(): Handles scrolling to bring an index into view
  • paintEvent(): Draws items (or manage child widgets if you prefer widget-based rendering)

Which to Choose?

  • Proxy Model + QTableView: Best if you want to reuse QTableView's built-in features (editing, sorting) with minimal code.
  • QListView + Custom Delegate: Ideal if you need custom styling or want to keep the view as a single list of pairs.
  • Custom QAbstractItemView: Only for complex, fully custom layouts that can't be achieved with the other two approaches.

Both the proxy model and delegate approaches are far more scalable than your current QScrollArea setup—they automatically sync with model data changes, support selection and editing out of the box, and follow Qt's model-view separation principles.

内容的提问来源于stack exchange,提问作者Andre Jonas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 17:07:42