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

PyQt5:如何可靠实现QTableView列尺寸的精细化控制?

Robust Implementation for QTableView with Mixed Column Sizes

Nice catch on the fragility of your current implementation! The dependency on setModel order and the brittle setup is definitely something we can fix while keeping your clean separation between model, view, and header. Let's walk through a more robust approach that eliminates the order sensitivity and encapsulates view-related logic where it belongs.

Core Issues with Your Current Setup

The main problem is that you're overriding setModel to configure column resize modes, which ties you to a specific call order. Additionally, trying to set resize modes in the constructor fails because the model isn't attached yet—so the header doesn't know how many columns exist. We can fix this using Qt's signal-slot system to react to model changes properly.

Improved Implementation

Step 1: Refactor the Header View

Instead of overriding setModel, we'll listen for the modelChanged signal that QHeaderView emits when its model is set. This ensures we only configure columns once the model is ready, regardless of setup order:

class MyHeaderView(QHeaderView):
    def __init__(self, orientation=Qt.Horizontal, parent=None):
        super().__init__(orientation, parent)
        # Define resize modes and fixed column width
        self._resize_modes = [QHeaderView.Stretch, QHeaderView.Stretch, QHeaderView.Fixed]
        self._fixed_col_width = 150  # Set your known fixed width here
        
        # Connect model change signal to our setup method
        self.modelChanged.connect(self._configure_columns)

    def _configure_columns(self, new_model, old_model):
        if not new_model:
            return
        # Ensure we have the correct number of columns before configuring
        if new_model.columnCount() != len(self._resize_modes):
            return
        
        # Apply resize modes to each column
        for idx, mode in enumerate(self._resize_modes):
            self.setSectionResizeMode(idx, mode)
        
        # Set fixed width for the last column
        self.resizeSection(len(self._resize_modes) - 1, self._fixed_col_width)

Step 2: Let the Table View Manage Its Own Header

To keep MyCentralWidget clean (no view configuration), we'll have MyTableView create and attach its own header internally. This encapsulates the view-header relationship:

class MyTableView(QTableView):
    def __init__(self, parent=None):
        super().__init__(parent)
        # Create and attach the custom header directly in the view
        self._custom_header = MyHeaderView(Qt.Horizontal, self)
        self.setHorizontalHeader(self._custom_header)

Step 3: Clean Up the Central Widget

Now MyCentralWidget only needs to initialize the model and view, and set the model on the view—no more manual header model setup:

class MyCentralWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.my_table_model = MyTableModel(parent=parent)
        self.my_table_view = MyTableView(parent=parent)
        
        # Only one line needed to connect model and view
        self.my_table_view.setModel(self.my_table_model)
        
        self.layout = QVBoxLayout(self)
        self.layout.addWidget(self.my_table_view)
        self.setLayout(self.layout)

Step 4: Optional Model Cleanup

We can also simplify the headerData method in MyTableModel for cleaner code:

class MyTableModel(QAbstractTableModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.entries = [
            ['row0, col0 (expanding)', 'row0, col1 (expanding)', 'row0, col2'],
            ['row1, col0 (expanding)', 'row1, col1 (expanding)', 'row1, col2'],
            ['row2, col0 (expanding)', 'row2, col1 (expanding)', 'row2, col2'],
        ]
        # Store headers in a list for easy access
        self._headers = ['Expanding 0', 'Expanding 1', 'Fixed 0']

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if orientation == Qt.Vertical:
            return QVariant()
        if role == Qt.DisplayRole and 0 <= section < len(self._headers):
            return self._headers[section]
        return QVariant()

    # Keep your existing data(), rowCount(), columnCount() methods the same

Full Working Code

Here's the complete refactored code:

#!/usr/bin/env python
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class MyTableModel(QAbstractTableModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.entries = [
            ['row0, col0 (expanding)', 'row0, col1 (expanding)', 'row0, col2'],
            ['row1, col0 (expanding)', 'row1, col1 (expanding)', 'row1, col2'],
            ['row2, col0 (expanding)', 'row2, col1 (expanding)', 'row2, col2'],
        ]
        self._headers = ['Expanding 0', 'Expanding 1', 'Fixed 0']

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if orientation == Qt.Vertical:
            return QVariant()
        if role == Qt.DisplayRole and 0 <= section < len(self._headers):
            return self._headers[section]
        return QVariant()

    def data(self, index=QModelIndex(), role=Qt.DisplayRole):
        if role != Qt.DisplayRole:
            return QVariant()
        return self.entries[index.row()][index.column()]

    def rowCount(self, parent=QModelIndex()):
        return len(self.entries)

    def columnCount(self, parent=QModelIndex()):
        return len(self.entries[0])

class MyHeaderView(QHeaderView):
    def __init__(self, orientation=Qt.Horizontal, parent=None):
        super().__init__(orientation, parent)
        self._resize_modes = [QHeaderView.Stretch, QHeaderView.Stretch, QHeaderView.Fixed]
        self._fixed_col_width = 150
        self.modelChanged.connect(self._configure_columns)

    def _configure_columns(self, new_model, old_model):
        if not new_model:
            return
        if new_model.columnCount() != len(self._resize_modes):
            return
        
        for idx, mode in enumerate(self._resize_modes):
            self.setSectionResizeMode(idx, mode)
        
        self.resizeSection(len(self._resize_modes) - 1, self._fixed_col_width)

class MyTableView(QTableView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._custom_header = MyHeaderView(Qt.Horizontal, self)
        self.setHorizontalHeader(self._custom_header)

class MyCentralWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.my_table_model = MyTableModel(parent=parent)
        self.my_table_view = MyTableView(parent=parent)
        self.my_table_view.setModel(self.my_table_model)
        
        self.layout = QVBoxLayout(self)
        self.layout.addWidget(self.my_table_view)
        self.setLayout(self.layout)

class MyMainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.central_widget = MyCentralWidget(self)
        self.setCentralWidget(self.central_widget)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MyMainWindow()
    main_window.show()
    sys.exit(app.exec())

Key Advantages of This Approach

  • No order sensitivity: The header configures itself automatically when the model is attached, thanks to the modelChanged signal.
  • Encapsulation: All view/header logic is contained within MyTableView and MyHeaderView—MyCentralWidget only handles assembly.
  • Robustness: We check for valid models and column counts before configuring, preventing crashes from mismatched setups.
  • Maintainability: Fixed width and resize modes are centralized in MyHeaderView, making them easy to adjust later.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:52:11