Qt : Fit width of TableView to width of content

Ilyes Ferchiou picture Ilyes Ferchiou · Dec 27, 2013 · Viewed 11k times · Source

I have a window that contains a QTableView which columns are adjusted to content and are fixed in width. The QTableView is nested within a QWidget that in turn is nested within a QScrollArea that in turn is nested within a tabbed QMdiArea which is the centralWidget of a QMainWindow.

When the QScrollArea is shown, the QTableView has extra space to the right of the last column that I want to be removed :

Screenshot of TableView

I want the QTableView to fit the exact width of the columns (i.e. no extra space after the rightmost column).

I tried to use tableView.setFixedWidth(desired_width) but then the only way it could work was to iterate over all the columns and get their widths and sum them up together and add the verticalHeader width and the scroll-bars width and pass it as the desired_width.

It does work this way but it seems to be very complex for such an obvious requirement : I think a lot of program developers want their tables' width to fit the columns' width and this is what makes me wonder that maybe there is a method that does that automatically without the extra calculations and that I'm unaware of.

So my question is : is there a simpler way to achieve the same result ?

Here is my code for reference :

The code for the t_main module responsible for creating the QMainWindow :

import sys
import t_wdw
from PySide import QtGui

class Main_Window(QtGui.QMainWindow):
    def __init__(self):
        super(Main_Window,self).__init__()
        self.initUI()


    def initUI(self):
        self.statusBar()
        # Defines QActions for menu
        self.new_window=QtGui.QAction("&Window alpha",self)
        self.new_window.triggered.connect(self.open_window)
        # Creates the menu
        self.menu_bar=self.menuBar()
        self.menu1=self.menu_bar.addMenu('&Menu 1')
        self.menu1.addAction(self.new_window)
        # Creates a QMdiArea to manage all windows.
        self.wmanager=QtGui.QMdiArea()
        self.wmanager.setViewMode(QtGui.QMdiArea.TabbedView)
        self.setCentralWidget(self.wmanager)
        self.showMaximized()

    # Opens the new window that holds the QTableView
    def open_window(self):
        t_wdw.launch_window()
        t_wdw.window_alpha=self.wmanager.addSubWindow(t_wdw.window)
        t_wdw.window_alpha.show()

def main():
    app=QtGui.QApplication(sys.argv)
    main_wdw=Main_Window()
    sys.exit(app.exec_())

if __name__=="__main__":
    main()

The code for t_wdw module that is responsible for creating the SubWindow with the QTableView.

from PySide import QtGui
from PySide import QtCore

def launch_window():
    global window
    # Creates a new window
    window=QtGui.QScrollArea()
    window1=QtGui.QWidget()
    row=0
    data=[["VH"+str(row+i),1,2,3] for i in range(20)]
    headers=["HH1","HH2","HH3"]
    # Creates a table view with columns resized to fit the content.
    model=my_table(data,headers)
    tableView=QtGui.QTableView()
    tableView.setModel(model)
    tableView.resizeColumnsToContents()
    # Fixes the width of columns and the height of rows.
    tableView.horizontalHeader().setResizeMode(QtGui.QHeaderView.Fixed)
    tableView.verticalHeader().setResizeMode(QtGui.QHeaderView.Fixed)

    """
    Here is the solution I resorted to to fix the tableView width 
    equal to its content and that I want to make simpler
    """
    vhwidth=tableView.verticalHeader().width()
    desired_width=QtGui.QStyle.PM_ScrollBarExtent*2+vhwidth+1
    for i in range(len(headers)):
        desired_width+=tableView.columnWidth(i)
    tableView.setFixedWidth(desired_width)

    """
    Sets the layouts and widgets using a QHBoxLayout because
    I want the QTableView to be centered on the window
    and more widgets and layouts are to be added later.
    """

    window1.main_layout=QtGui.QHBoxLayout()
    window1.main_layout.addStretch(0)
    window1.main_layout.addWidget(tableView)
    window1.main_layout.addStretch(0)
    window.setLayout(window1.main_layout)
    window.setWidget(window1)

class my_table(QtCore.QAbstractTableModel):

    def __init__(self,data,headers,parent=None):
        QtCore.QAbstractTableModel.__init__(self,parent)
        self.__data=data
        self.__headers=headers

    def rowCount(self,parent):
        return len(self.__data)

    def columnCount(self,parent):
        return len(self.__headers)

    def data(self, index, role):
        if role == QtCore.Qt.DisplayRole:
            row = index.row()
            column = index.column()
            return self.__data[row][column+1]

    def headerData(self,section,orientation,role):
        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                return self.__headers[section]
            if orientation == QtCore.Qt.Vertical:
                return self.__data[section][0]

An extra question for those who read the code : Why do I need to multiply PM_ScrollBarExtent by 2 to get the correct result ?

P.S : I'm using PySide 1.2.1 but answers in PyQt or C++ are fine.

Answer

ekhumoro picture ekhumoro · Dec 27, 2013

There are a couple of things about your width calcualtion that are wrong.

Firstly, you attempt to use QtGui.QStyle.PM_ScrollBarExtent to get the width of the vertical scrollbar - but that is a constant, not a property. Instead, you need to use QStyle.pixelMetric:

tableView.style().pixelMetric(QtGui.QStyle.PM_ScrollBarExtent)

Secondly, you do not take into account the width of the tableview frame, which can be calculated like this:

tableView.frameWidth() * 2

Putting these values together with the dimensions of the headers, the final calculation should be:

vwidth = tableView.verticalHeader().width()
hwidth = tableView.horizontalHeader().length()
swidth = tableView.style().pixelMetric(QtGui.QStyle.PM_ScrollBarExtent)
fwidth = tableView.frameWidth() * 2

tableView.setFixedWidth(vwidth + hwidth + swidth + fwidth)

This should leave exactly the space needed for the vertical scrollbar.

PS:

Since you're setting a fixed width for the tableview, you could also get rid of the horizontal scrollbar, which is redundant:

tableView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)