How to proportionally adjust column widths in a QTableView?

廉价感情. 提交于 2021-02-04 19:38:08

问题


I want to proportionally change the column width of all columns in a QTableView widget, so that each column has the same width regardless of the data. For example, if a table has three columns, each column should always have a width of one third of the available horizontal space - and the width should be automatically updated whenever the dialog is resized by the user.

So far I've only managed to resize columns to their contents, which is not what I want. Here's the code I've got so far:

main.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Dialog</class>
 <widget class="QDialog" name="Dialog">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>624</width>
    <height>329</height>
   </rect>
  </property>
  <property name="sizePolicy">
   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
    <horstretch>0</horstretch>
    <verstretch>0</verstretch>
   </sizepolicy>
  </property>
  <property name="windowTitle">
   <string>Dialog</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout">
     <item>
      <widget class="QTableView" name="tableView">
       <property name="alternatingRowColors">
        <bool>true</bool>
       </property>
       <property name="selectionBehavior">
        <enum>QAbstractItemView::SelectRows</enum>
       </property>
      </widget>
     </item>
     <item>
      <layout class="QVBoxLayout" name="verticalLayout_2">
       <item>
        <widget class="QPushButton" name="btnPopulate">
         <property name="text">
          <string>Populate</string>
         </property>
        </widget>
       </item>
      </layout>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <tabstops>
  <tabstop>btnPopulate</tabstop>
 </tabstops>
 <resources/>
 <connections/>
</ui>

test.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys, os
from PyQt5 import uic
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtWidgets import QDialog, QApplication, QHeaderView

class GUI(QDialog):

    def __init__(self):
        super(GUI, self).__init__()
        dirname = os.path.dirname(os.path.abspath(__file__))
        uic.loadUi(os.path.join(dirname,'main.ui'), self)
        # button
        self.btnPopulate.clicked.connect(self.populate)
        # table model
        self.header = ['col1', 'col2', 'col3']
        self.QSModel = QStandardItemModel()
        self.QSModel.setColumnCount(3)
        self.QSModel.setHorizontalHeaderLabels(self.header)
        # table view
        self.tableView.setModel(self.QSModel)
        self.tableView.setWordWrap(True)
        self.tableView.horizontalHeader().setStretchLastSection(False)

    def populate(self):
        self.longtext = '''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ac tellus nunc. Phasellus imperdiet leo metus, et gravida lacus. Donec metus ligula, elementum at pellentesque pellentesque, suscipit ac nunc.'''
        row = self.QSModel.rowCount()
        for x in range(7):
            self.QSModel.insertRow(row)
            self.QSModel.setData(self.QSModel.index(row, 0), 'Lorem ipsum')
            self.QSModel.setData(self.QSModel.index(row, 1), self.longtext)
            self.QSModel.setData(self.QSModel.index(row, 2), 'Lorem ipsum')
        self.tableView.resizeColumnsToContents()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = GUI()
    window.show()
    sys.exit(app.exec_())

I've got the following questions:

  1. How do I change the code to proportionally adjust the column widths?
  2. Why doesn't setWordWrap(True) wrap the text?

回答1:


This can be achieved by setting the section resize mode. To get equal column widths:

self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

To wrap the contents vertically:

self.tableView.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)

There is no need to call resizeToContents, setWordWrap or setStretchLastSection. Calling setWordWrap(False) will switch to eliding the text on the right, rather than wrapping.

Note that since the row/column resizing is done automatically, the sizes can no longer be changed by the user or programmatically.




回答2:


Following example (PySide, using QT 4.8) will proportionally change the column widths to the width of the QTableView. When the user manually resizes the width of a column (double clicking or dragging the section header), from then on the width of that particular column will remain fixed while the other columns proportionally fill the remaining space.

from PySide.QtGui import *
from PySide.QtCore import QEvent

class CustomTableView(QTableView):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.verticalHeader().hide()
        self.horizontalHeader().sectionResized.connect(self.section_resized)
        self.dynamically_resized = False
        self.fixed_section_widths = dict()

    @disconnect_section_resized
    def dynamic_column_resize(self):
        flexible_width = self.width() - 2 - sum(self.fixed_section_widths.values())
        column_count = self.model().columnCount()
        flexible_column_count = column_count - len(self.fixed_section_widths)
        column_width = flexible_width // flexible_column_count if flexible_column_count else 1
        last_flexible_column_width = column_width + flexible_width % column_width
        for column_index in range(column_count):
            if column_index not in self.fixed_section_widths:
                width = column_width if flexible_column_count > 1 else last_flexible_column_width
                flexible_column_count = flexible_column_count - 1
            else:
                width = self.fixed_section_widths[column_index]
            self.setColumnWidth(column_index, width)
        self.dynamically_resized = True

    def section_resized(self, column_index, old_size, new_size):
        if not self.dynamically_resized:
            return
        self.fixed_section_widths[column_index] = self.columnWidth(column_index)
        self.dynamic_column_resize()

    def eventFilter(self, obj, event):
        if event.type() == QEvent.Resize:
            self.dynamic_column_resize()
            return True
        return super(QTableView, self).eventFilter(obj, event)

The section_resized method which is meant to apply a fixed width for a certain column, should only run in case the sectionResized signal has been emitted by (manual) user interaction. The dynamic_column_resize method (which will be executed each time the QTableWidget changes width) shouldn't trigger the section_resized method because then there'll be an infinite loop, because at the end of section_resized the dynamic_column_resize method is called. Following decorator is used to prevent this:

def disconnect_section_resized(func):
    def wrapper(self):
        self.horizontalHeader().sectionResized.disconnect(self.section_resized)
        func(self)
        self.horizontalHeader().sectionResized.connect(self.section_resized)
    return wrapper


来源:https://stackoverflow.com/questions/48317828/how-to-proportionally-adjust-column-widths-in-a-qtableview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!