In QDialog, resize window to contain all columns o

2019-07-27 01:59发布

问题:

I am writing a simple program to display the contents of a SQL database table in a QDialog (PySide). The goal is to have a method that expands the window to show all of the columns, so the user doesn't have to resize to see everything. This problem was addressed in a slightly different context:

Fit width of TableView to width of content

Based on that, I wrote the following method:

def resizeWindowToColumns(self):
    frameWidth = self.view.frameWidth() * 2
    vertHeaderWidth = self.view.verticalHeader().width()
    horizHeaderWidth =self.view.horizontalHeader().length()
    vertScrollWidth = self.view.style().pixelMetric(QtGui.QStyle.PM_ScrollBarExtent) 
    fudgeFactor = 6 #not sure why this is needed 
    newWidth = frameWidth + vertHeaderWidth + horizHeaderWidth + vertScrollWidth + fudgeFactor

It works great. But notice that I have had to add a fudgeFactor. With fudge, it works perfectly. But it suggests I have lost track of six pixels, and am very curious where they are coming from. It doesn't seem to matter how many columns are displayed, or their individual widths: the fudgeFactor 6 always seems to work.

System details

Python 2.7 (Spyder/Anaconda). PySide version 1.2.2, Qt version 4.8.5. Windows 7 laptop with a touch screen (touch screens sometimes screw things up in PySide).

Full working example

# -*- coding: utf-8 -*-
import os
import sys
from PySide import QtGui, QtCore, QtSql

class DatabaseInspector(QtGui.QDialog):

    def __init__(self, tableName, parent = None):
        QtGui.QDialog.__init__(self, parent) 

        #define model
        self.model = QtSql.QSqlTableModel(self)
        self.model.setTable(tableName)
        self.model.select()

        #View of model
        self.view = QtGui.QTableView()
        self.view.setModel(self.model)

        #Sizing
        self.view.resizeColumnsToContents()  #Resize columns to fit content
        self.resizeWindowToColumns()  #resize window to fit columns

        #Quit button
        self.quitButton = QtGui.QPushButton("Quit");
        self.quitButton.clicked.connect(self.reject)

        #Layout
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.view)  #table view
        layout.addWidget(self.quitButton)  #pushbutton
        self.setLayout(layout)
        self.show()  

    def resizeEvent(self, event):  
        #This is just to see what's going on                         
        print "Size set to ({0}, {1})".format(event.size().width(), event.size().height())

    def resizeWindowToColumns(self):
        #Based on: https://stackoverflow.com/a/20807145/1886357
        frameWidth = self.view.frameWidth() * 2
        vertHeaderWidth = self.view.verticalHeader().width()
        horizHeaderWidth =self.view.horizontalHeader().length()
        vertScrollWidth = self.view.style().pixelMetric(QtGui.QStyle.PM_ScrollBarExtent) 
        fudgeFactor = 6 #not sure why this is needed 
        newWidth = frameWidth + vertHeaderWidth + horizHeaderWidth + vertScrollWidth + fudgeFactor
        if newWidth <= 500:
            self.resize(newWidth, self.height())
        else:
            self.resize(500, self.height())


def populateDatabase():
    print "Populating table in database..."
    query = QtSql.QSqlQuery()
    if not query.exec_("""CREATE TABLE favorites (
                id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
                category VARCHAR(40) NOT NULL,
                number INTEGER NOT NULL,
                shortdesc VARCHAR(20) NOT NULL,
                longdesc VARCHAR(80))"""):
                print "Failed to create table"
                return False
    categories = ("Apples", "Chocolate chip cookies", "Favra beans")
    numbers = (1, 2, 3)
    shortDescs = ("Crispy", "Yummy", "Clarice?")
    longDescs = ("Healthy and tasty", "Never not good...", "Awkward beans for you!")
    query.prepare("""INSERT INTO favorites (category, number, shortdesc, longdesc)
                     VALUES (:category, :number, :shortdesc, :longdesc)""") 
    for category, number, shortDesc, longDesc in zip(categories, numbers, shortDescs, longDescs):
        query.bindValue(":category",  category)
        query.bindValue(":number", number)
        query.bindValue(":shortdesc", shortDesc)
        query.bindValue(":longdesc",  longDesc)        
        if not query.exec_():
            print "Failed to populate table"
            return False 
    return True

def main():
    import site
    app = QtGui.QApplication(sys.argv)

    #Connect to/initialize database
    dbName = "food.db"
    tableName = "favorites"
    site_pack_path = site.getsitepackages()[1]
    QtGui.QApplication.addLibraryPath('{0}\\PySide\\plugins'.format(site_pack_path))
    db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
    fullFilePath = os.path.join(os.path.dirname(__file__), dbName) #;print fullFilePath
    dbExists = QtCore.QFile.exists(fullFilePath) #does it already exist in directory?
    db.setDatabaseName(fullFilePath) 
    db.open()    
    if not dbExists:
        populateDatabase()

    #Display database      
    dataTable = DatabaseInspector(tableName)
    sys.exit(app.exec_())

    #Close and delete database (not sure this is needed)
    db.close()
    del db


if __name__ == "__main__":
    main()

回答1:

The difference between the linked question and your example, is that the former is resizing a widget within a layout, whereas the latter is resizing a top-level window.

A top-level window is usually decorated with a frame. On your system, the width of this frame seems to be three pixels on each side, making six pixels in all.

You can calculate this value programmatically with:

    self.frameSize().width() - self.width()

where self is the top-level window.

However, there may be an extra issue to deal with, and that is in choosing when to calculate this value. On my Linux system, the frame doesn't get drawn until the window is fully shown - so calculating during __init__ doesn't work.

I worked around that problem like this:

dataTable = DatabaseInspector(tableName)
dataTable.show()
QtCore.QTimer.singleShot(10, dataTable.resizeWindowToColumns)

but I'm not sure whether that's portable (or even necessarily the best way to do it).

PS:

It seems that the latter issue may be specific to X11 - see the Window Geometry section in the Qt docs.

UPDATE:

The above explanation and calculation is not correct!

The window decoration is only relevant when postioning windows. The resize() and setGeometry() functions always exclude the window frame, so it doesn't need to be factored in when calculating the total width.

The difference between resizing a widget within a layout versus resizing a top-level window, is that the latter needs to take account of the layout margin.

So the correct calculation is this:

    margins = self.layout().contentsMargins()
    self.resize((
        margins.left() + margins.right() +
        self.view.frameWidth() * 2 +
        self.view.verticalHeader().width() +
        self.view.horizontalHeader().length() +
        self.view.style().pixelMetric(QtGui.QStyle.PM_ScrollBarExtent)
        ), self.height())

But note that this always allows room for a vertical scrollbar.

The example script doesn't add enough rows to show the vertical scrollbar, so it is misleading in that respect - if more rows are added, the total width is exactly right.