Skip to content

Instantly share code, notes, and snippets.

@csaez
Last active January 4, 2020 16:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save csaez/5cc13591688d0569eae51d16049a90be to your computer and use it in GitHub Desktop.
Save csaez/5cc13591688d0569eae51d16049a90be to your computer and use it in GitHub Desktop.
QtreeView: keep aspect ratio when resizing a column
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
def fetch_data():
return [
{
'name': 'horizontal',
'image': {
'colour': (255, 0, 0),
'width': 480,
'height': 240,
},
},
{
'name': 'vertical',
'image': {
'colour': (0, 255, 0),
'width': 240,
'height': 480,
},
},
{
'name': 'none',
'image': {},
},
{
'name': 'square',
'image': {
'colour': (0, 0, 255),
'width': 480,
'height': 480,
},
},
]
class MyModel(QtCore.QAbstractItemModel):
def __init__(self, raw_data, *args, **kwargs):
super(MyModel, self).__init__(*args, **kwargs)
self._raw_data = raw_data
def index(self, row, column, parent):
ptr = self._raw_data[row] if row < len(self._raw_data) else None
return self.createIndex(row, column, ptr)
def parent(self, index):
return QtCore.QModelIndex()
def hasChildren(self, index):
return index.internalPointer() is None
def rowCount(self, parent):
return len(self._raw_data)
def columnCount(self, parent):
return 2 # preview, name
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal:
return ('Preview', 'Name')[section]
def data(self, index, role):
d = index.internalPointer()
if role == QtCore.Qt.UserRole:
return d
column = index.column()
if column == 0:
if role == QtCore.Qt.DecorationRole:
if not d['image']:
return
p = QtGui.QPixmap(d['image']['width'], d['image']['height'])
p.fill(QtGui.QColor(*d['image']['colour']))
return p
elif column == 1:
if role == QtCore.Qt.DisplayRole:
return d['name']
class MyDelegate(QtWidgets.QStyledItemDelegate):
def sizeHint(self, option, index):
if not hasattr(self, '_static_hints'):
self._static_hints = {}
size = super(MyDelegate, self).sizeHint(option, index)
d = index.data(QtCore.Qt.UserRole)
if index.column() == 0 and d['image']:
ratio = d['image']['height']/float(d['image']['width'])
width = option.widget.columnWidth(index.column())
size = QtCore.QSize(width, width * ratio)
# sizeHint gets called when the model's data changes, therefore even
# tho we compute the image size correctly it doesn't get called when
# the user resize a column (notice this is an action on the view and
# doesn't change nothing in the viewport)
# we are going to store the last computed size hints in a dictionary
# so we have a way to check if an update is necesary while painting
if not hasattr(self, '_static_hints'):
self._static_hints = {}
self._static_hints[index] = size
return size
def paint(self, painter, option, index):
d = index.data(QtCore.Qt.UserRole)
if index.column() == 0 and d['image']:
ratio = d['image']['height']/float(d['image']['width'])
width = option.widget.columnWidth(index.column())
rect = QtCore.QRect(0, option.rect.y(),
width, (width * ratio))
painter.drawPixmap(rect, index.data(QtCore.Qt.DecorationRole))
if self._static_hints[index].height() != rect.height():
# update row height update by emitting dataChanged signal (in the model)
# which will force the view to recompute the sizeHint
model = option.widget.model()
model.dataChanged.emit(index, index, [QtCore.Qt.DecorationRole])
return
return super(MyDelegate, self).paint(painter, option, index)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mw = QtWidgets.QMainWindow()
model = MyModel(fetch_data())
delegate = MyDelegate()
tree = QtWidgets.QTreeView()
tree.setItemDelegate(delegate)
tree.setModel(model)
mw.setCentralWidget(tree)
mw.show()
sys.exit(app.exec_())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment