Issue
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()
Solution
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.
Answered By - ekhumoro
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.