PyQT5 Data/View model representation with a complex widget - python

I'm trying to wrap my head around some ModelView on PyQT5.
I have a QListView, which can display data stored in an QAbstractListModel.
But i'd like to have each line of my QListView displaying a complex Widget created in QDesigner.
I've created a widget with a QLabel, a spacer, and a QPushButton.
I'd like to have each elements of my QListView using this widget to display my model data
Here is the basic code with a simple ModelView QListView
import typing
from PyQt5 import QtCore
from PyQt5.QtCore import QModelIndex, Qt
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QListView
class MyListModel(QtCore.QAbstractListModel):
def __init__(self, parent=None):
super().__init__(parent)
self.data_list = []
def data(self, index: QModelIndex, role: int = ...) -> typing.Any:
if role == Qt.DisplayRole:
return self.data_list[index.row()]
def rowCount(self, parent: QModelIndex = ...) -> int:
return len(self.data_list)
data = ["emotion", "unliving", "brutally", "torch", "donut", "comet"]
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
layout = QWidget()
layout.setLayout(QVBoxLayout())
list_view = QListView()
model = MyListModel()
model.data_list = data
list_view.setModel(model)
layout.layout().addWidget(list_view)
layout.resize(640, 480)
layout.show()
sys.exit(app.exec_())
I've tried to create a QStyledItemDelegate, and set the delegate on the model, but i can't make it work.
class LineDelegate(QStyledItemDelegate):
def __init__(self, parent):
QStyledItemDelegate.__init__(self, parent)
self.line = Ui_Form()
self.line.setupUi(parent)
def paint(self, painter: QtGui.QPainter, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> None:
# Not sure what to put here
The doc seems to said that a Delegate should be used to edit data inside the view, not having a complex widget view.
https://doc.qt.io/qtforpython/overviews/model-view-programming.html#a-simple-delegate
Is there anything possible to have what i want ?
I'm really not sure how to approch this.

You must use a delegate so that you create an editor for each item, and then use the openPersistentEditor method to open the editors, it is not necessary to override the paint method.
import typing
from PyQt5.QtCore import QAbstractListModel, QModelIndex, Qt
from PyQt5.QtWidgets import (
QApplication,
QListView,
QStyledItemDelegate,
QVBoxLayout,
QWidget,
)
class Form(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_Form()
self.ui.setupUi(self)
class MyListModel(QAbstractListModel):
def __init__(self, parent=None):
super().__init__(parent)
self._data_list = []
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> typing.Any:
if role == Qt.DisplayRole:
return self._data_list[index.row()]
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
return len(self._data_list)
#property
def data_list(self):
return self._data_list
#data_list.setter
def data_list(self, data_list):
self.beginResetModel()
self._data_list = data_list.copy()
self.endResetModel()
class Delegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
return Form(parent)
"""def setEditorData(self, editor, index):
editor.label.setText(index.data())"""
data = ["emotion", "unliving", "brutally", "torch", "donut", "comet"]
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
list_view = QListView()
model = MyListModel()
model.data_list = data
list_view.setModel(model)
delegate = Delegate(list_view)
list_view.setItemDelegate(delegate)
for i in range(model.rowCount()):
index = model.index(i, 0)
list_view.openPersistentEditor(index)
container = QWidget()
layout = QVBoxLayout(container)
layout.addWidget(list_view)
container.resize(640, 480)
container.show()
sys.exit(app.exec_())

Related

PySide2 AutoComplete: Customize the Result

I am using QCompleter to implement auto-completion on a QLineEdit widget:
from PySide2 import QtGui
from PySide2.QtCore import Qt
from PySide2.QtGui import QStandardItem
from PySide2.QtWidgets import QCompleter, QWidget, QLineEdit, QFormLayout, QApplication
class SuggestionPlaceModel(QtGui.QStandardItemModel):
def __init__(self, parent=None):
super(SuggestionPlaceModel, self).__init__(parent)
def search(self, text):
self.clear()
data = [{'text': f"{text} {i}"} for i in range(10)]
for i, row in enumerate(data):
item = QStandardItem(row['text'])
self.appendRow(item)
class Completer(QCompleter):
def splitPath(self, path):
self.model().search(path)
return super(Completer, self).splitPath(path)
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self._model = SuggestionPlaceModel(self)
completer = Completer(self)
completer.setCaseSensitivity(Qt.CaseInsensitive)
completer.setModel(self._model)
lineedit = QLineEdit()
lineedit.setCompleter(completer)
lay = QFormLayout(self)
lay.addRow("Location: ", lineedit)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Here is a result:
QUESTION: How can I customize the SuggestionPlaceModel class so that the search result can include icons, horizontal separaters, different fonts, different font sizes, etc like this?
A possible solution is to use a custom delegate where the icon is set, in the case of html you can use a QLabel that supports rich text.
import random
from PySide2 import QtCore, QtGui, QtWidgets
class Delegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super(Delegate, self).initStyleOption(option, index)
option.text = ""
def paint(self, painter, option, index):
if isinstance(option.widget, QtWidgets.QAbstractItemView):
option.widget.openPersistentEditor(index)
super(Delegate, self).paint(painter, option, index)
def createEditor(self, parent, option, index):
editor = QtWidgets.QLabel(index.data(), parent)
editor.setContentsMargins(0, 0, 0, 0)
editor.setText(index.data(QtCore.Qt.UserRole))
return editor
class SuggestionPlaceModel(QtGui.QStandardItemModel):
def search(self, text):
self.clear()
for i in range(10):
html = f"{text}-{i} <b>Stack</b> <i>Overflow</i>"
plain = QtGui.QTextDocumentFragment.fromHtml(html).toPlainText()
pixmap = QtGui.QPixmap(128, 128)
pixmap.fill(QtGui.QColor(*random.sample(range(255), 4)))
item = QtGui.QStandardItem(plain)
item.setData(html, QtCore.Qt.UserRole)
item.setIcon(QtGui.QIcon(pixmap))
self.appendRow(item)
class Completer(QtWidgets.QCompleter):
def splitPath(self, path):
self.model().search(path)
return super(Completer, self).splitPath(path)
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self._model = SuggestionPlaceModel(self)
completer = Completer(self)
completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
completer.setModel(self._model)
delegate = Delegate(completer.popup())
completer.popup().setItemDelegate(delegate)
lineedit = QtWidgets.QLineEdit()
lineedit.setCompleter(completer)
lay = QtWidgets.QFormLayout(self)
lay.addRow("Location: ", lineedit)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())

Why QTreeview Item class changed after drag & drop Event? [duplicate]

This question already has answers here:
How to drop a custom QStandardItem into a QListView
(1 answer)
Preserve QStandardItem subclasses in drag and drop
(1 answer)
Closed 1 year ago.
Run code below, when drag & drop an item and then click on the moved one, I expect a console message but receive an error message about :AttributeError: 'QStandardItem' object has no attribute 'say'
Why qt change the class of the custom Item?
import sys
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import QMainWindow, QTreeView, QApplication
class DemoTreeView(QMainWindow):
class Item(QStandardItem):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.original_text = self.text()
def say(self):
print(self.original_text)
class View(QTreeView):
def __init__(self,parent):
super().__init__(parent)
self.setDragDropMode(self.InternalMove)
self.clicked.connect(self.on_clicked_handle)
def on_clicked_handle(self,index):
item= self.model().itemFromIndex(index)
item.say()
def __init__(self, parent=None):
super(DemoTreeView, self).__init__(parent)
self.initUi()
def initUi(self):
self.model = QStandardItemModel(self)
self.model.setHorizontalHeaderLabels(['test'])
self.model.appendRow([self.Item("A")])
self.model.appendRow([self.Item("B")])
self.treeView = self.View(self)
self.treeView.setModel(self.model)
self.treeView.expandAll()
self.setCentralWidget(self.treeView)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = DemoTreeView()
window.show()
sys.exit(app.exec())
If you are going to subclass QStandardItem then you have to implement the clone() method and pass a custom item to the model through the setItemPrototype() method.
On the other hand, when an item is copied by drag-and-drop, what is done is creating an empty item and then adding the roles using QDataStream, something like:
# ds: QDataStream with the information of the roles of the original item
item = FooItem()
ds >> item
So original_text will have an empty text so a possible solution is to say() return the text.
import sys
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import QMainWindow, QTreeView, QApplication
class Item(QStandardItem):
def say(self):
print(self.text())
def clone(self):
return Item()
class View(QTreeView):
def __init__(self, parent):
super().__init__(parent)
self.setDragDropMode(self.InternalMove)
self.clicked.connect(self.on_clicked_handle)
def on_clicked_handle(self, index):
item = self.model().itemFromIndex(index)
item.say()
class DemoTreeView(QMainWindow):
def __init__(self, parent=None):
super(DemoTreeView, self).__init__(parent)
self.initUi()
def initUi(self):
self.model = QStandardItemModel(self)
item = Item()
self.model.setItemPrototype(item)
self.model.setHorizontalHeaderLabels(["test"])
self.model.appendRow([Item("A")])
self.model.appendRow([Item("B")])
self.treeView = View(self)
self.treeView.setModel(self.model)
self.treeView.expandAll()
self.setCentralWidget(self.treeView)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = DemoTreeView()
window.show()
sys.exit(app.exec())

Move inherited item with Drag and Drop within QStandardItemModel

I'm trying to implement a function to move nodes by dragging in a single tree (QStandardItemModel, PyQt5, Python). My nodes are classes created by multiple inheritance like class Node(A, QStandardItem). When I drag and drop this node, only properties from QStandardItem parent class are moved, everything from the class A is lost.
Here is minimal working example:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import (Qt, QModelIndex, QMimeData, QByteArray)
from PyQt5.QtWidgets import (QApplication, QMainWindow, QAbstractItemView, QPushButton, QVBoxLayout, QWidget)
from PyQt5.QtGui import QStandardItemModel, QStandardItem
class A:
def __init__(self, *args, **kwargs):
self.symbol = None
super().__init__(*args, **kwargs) # forwards all unused arguments
class Node(A, QStandardItem):
def __init__(self, symbol, *args, **kwargs):
super().__init__(*args, **kwargs)
self.symbol = symbol
self.setText("Node " + str(self.symbol))
class DragDropTreeModel(QStandardItemModel):
def __init__(self, parent=None):
super(DragDropTreeModel, self).__init__(parent)
def supportedDropActions(self):
return Qt.MoveAction
def flags(self, index):
defaultFlags = QStandardItemModel.flags(self, index)
if index.isValid():
return Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | defaultFlags
else:
return Qt.ItemIsDropEnabled | defaultFlags```
class DemoDragDrop(QWidget):
def __init__(self, parent=None):
super(DemoDragDrop, self).__init__(parent)
self.setWindowTitle('drag&drop in PyQt5')
self.resize(480, 320)
self.initUi()
def initUi(self):
self.vLayout = QVBoxLayout(self)
self.TreeView = QtWidgets.QTreeView(self)
self.TreeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.TreeView.setDragEnabled(True)
self.TreeView.setAcceptDrops(True)
self.TreeView.setDropIndicatorShown(True)
self.ddm = DragDropTreeModel()
self.TreeView.setDragDropMode(QAbstractItemView.InternalMove)
self.TreeView.setDefaultDropAction(Qt.MoveAction)
self.TreeView.setDragDropOverwriteMode(False)
self.root_node = Node('root')
self.ddm.appendRow(self.root_node)
node_a = Node('a')
self.root_node.appendRow(node_a)
node_b = Node('b')
self.root_node.appendRow(node_b)
node_c = Node('c')
self.root_node.appendRow(node_c)
self.TreeView.setModel(self.ddm)
self.printButton = QPushButton("Print")
self.vLayout.addWidget(self.TreeView)
self.vLayout.addWidget(self.printButton)
self.printButton.clicked.connect(self.printModelProp)
def printModelProp(self):
cur_ind = self.TreeView.currentIndex()
obj = self.ddm.itemFromIndex(cur_ind)
obj: Node
print(obj.symbol)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle('fusion')
window = DemoDragDrop()
window.show()
sys.exit(app.exec_())
In this example, select the node from the tree and click "Print" button - it will print to the console, 'a' for "Node a", 'b' for "Node b" and so on. Then move one node, select it and push "Print" again. The application will crash with the error AttributeError: 'QStandardItem' object has no attribute 'symbol'.
Then I tried to move a node manually by overriding methods mimeData and dropMimeData. I saved row and column indexes in mimeData and tried to get this node from the index in dropMimeData to move it. But this doesn't work because the index has changed meanwhile.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import (Qt, QModelIndex, QMimeData, QByteArray)
from PyQt5.QtWidgets import (QApplication, QMainWindow, QAbstractItemView, QPushButton, QVBoxLayout, QWidget)
from PyQt5.QtGui import QStandardItemModel, QStandardItem
class A:
def __init__(self, *args, **kwargs):
self.symbol = None
super().__init__(*args, **kwargs) # forwards all unused arguments
class Node(A, QStandardItem):
def __init__(self, symbol, *args, **kwargs):
super().__init__(*args, **kwargs)
self.symbol = symbol
self.setText("Node " + str(self.symbol))
class DragDropTreeModel(QStandardItemModel):
def __init__(self, parent=None):
super(DragDropTreeModel, self).__init__(parent)
def supportedDropActions(self):
return Qt.MoveAction
def flags(self, index):
defaultFlags = QStandardItemModel.flags(self, index)
if index.isValid():
return Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | defaultFlags
else:
return Qt.ItemIsDropEnabled | defaultFlags
def mimeData(self, indexes) -> QtCore.QMimeData:
m_data = super().mimeData(indexes)
if (m_data):
r = indexes[0].row()
c = indexes[0].column()
obj = self.itemFromIndex(indexes[0])
print(f"row:{r}, column:{c}, type:{type(obj)}, ind:{indexes[0]}")
m_data.setData('row', QByteArray.number(indexes[0].row()))
m_data.setData('col', QByteArray.number(indexes[0].column()))
return m_data
def dropMimeData(self, data: QtCore.QMimeData, action: QtCore.Qt.DropAction, row: int, column: int,
parent: QtCore.QModelIndex) -> bool:
if data is None or action != QtCore.Qt.MoveAction:
return False
_row = data.data('row').toInt()[0]
_col = data.data('col').toInt()[0]
old_index = self.index(_row, _col)
current_index = parent
old_item = self.takeItem(old_index.row(), old_index.column())
parent_item = self.itemFromIndex(parent)
parent_item.appendRow(old_item)
return True
class DemoDragDrop(QWidget):
def __init__(self, parent=None):
super(DemoDragDrop, self).__init__(parent)
self.setWindowTitle('drag&drop in PyQt5')
self.resize(480, 320)
self.initUi()
def initUi(self):
self.vLayout = QVBoxLayout(self)
self.TreeView = QtWidgets.QTreeView(self)
self.TreeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.TreeView.setDragEnabled(True)
self.TreeView.setAcceptDrops(True)
self.TreeView.setDropIndicatorShown(True)
self.ddm = DragDropTreeModel()
self.TreeView.setDragDropMode(QAbstractItemView.InternalMove)
self.TreeView.setDefaultDropAction(Qt.MoveAction)
self.TreeView.setDragDropOverwriteMode(False)
self.root_node = Node('root')
self.ddm.appendRow(self.root_node)
node_a = Node('a')
self.root_node.appendRow(node_a)
node_b = Node('b')
self.root_node.appendRow(node_b)
node_c = Node('c')
self.root_node.appendRow(node_c)
self.TreeView.setModel(self.ddm)
self.printButton = QPushButton("Print")
self.vLayout.addWidget(self.TreeView)
self.vLayout.addWidget(self.printButton)
self.printButton.clicked.connect(self.printModelProp)
def printModelProp(self):
cur_ind = self.TreeView.currentIndex()
obj = self.ddm.itemFromIndex(cur_ind)
obj: Node
print(obj.symbol)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle('fusion')
window = DemoDragDrop()
window.show()
sys.exit(app.exec_())
In this example the tree will break.
I wonder is there a way to move the node without destroying it. It seems to me a wrong way to recreate the object (in mimeData() and dropMimeData()) when it's only needed to change the index.
So, the questions are: how to implement this move correctly, and is it possible without destroying the node (it can be a member of some list for example)?
Since nobody replied, I post an answer to my own question (maybe it's not perfect but it works).
First, I found that my design doesn't fit Qt's drag-n-drop design. One way is not to subclass an item using multiple inheritance (like class Node(A, QStandardItem)) but rather to use composition and keep class A within QStandardItem using roles. Then no need to override any methods.
In my case nodes are using multiple inheritance so I did override dropEvent method and move row (extract it from the model and insert in the new place). It's very simple example just for demonstration, so no checks what kind of object is dropping and so on.
If somebody has better idea - you're welcome to comment or write your own solution
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import (Qt, QModelIndex, QMimeData, QByteArray)
from PyQt5.QtWidgets import (QApplication, QMainWindow, QAbstractItemView, QPushButton, QVBoxLayout, QWidget)
from PyQt5.QtGui import QStandardItemModel, QStandardItem
class A:
def __init__(self, *args, **kwargs):
self.symbol = None
super().__init__(*args, **kwargs) # forwards all unused arguments
class Node(A, QStandardItem):
def __init__(self, symbol, *args, **kwargs):
super().__init__(*args, **kwargs)
self.symbol = symbol
self.setData(symbol, QtCore.Qt.UserRole)
self.setText("Node " + str(self.symbol))
def get_sym(self):
return self.data(QtCore.Qt.UserRole)
class DragDropTreeModel(QStandardItemModel):
def __init__(self, parent=None):
super(DragDropTreeModel, self).__init__(parent)
def supportedDropActions(self):
return Qt.MoveAction
def flags(self, index):
defaultFlags = QStandardItemModel.flags(self, index)
if index.isValid():
return Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | defaultFlags
else:
return Qt.ItemIsDropEnabled | defaultFlags
class MyTreeView(QtWidgets.QTreeView):
def __init__(self, parent=None):
super(MyTreeView, self).__init__(parent)
self.setAcceptDrops(True)
self.setDragEnabled(True)
def dropEvent(self, event):
index = self.indexAt(event.pos())
model = self.model()
dest_node = model.itemFromIndex(index)
if dest_node is None:
return
source_index = self.currentIndex()
source_node = model.itemFromIndex(source_index)
source_node: Node
sourse_parent = source_node.parent()
taken_row = sourse_parent.takeRow(source_index.row())
dest_parent = dest_node
if dest_node != sourse_parent:
dest_parent = dest_node.parent()
if dest_parent is None:
dest_parent = dest_node
dest_parent.insertRow(index.row(), taken_row)
class DemoDragDrop(QWidget):
def __init__(self, parent=None):
super(DemoDragDrop, self).__init__(parent)
self.setWindowTitle('drag&drop in PyQt5')
self.resize(480, 320)
self.initUi()
def initUi(self):
self.vLayout = QVBoxLayout(self)
self.TreeView = MyTreeView(self)# QtWidgets.QTreeView(self)#
self.TreeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.TreeView.setDragEnabled(True)
self.TreeView.setAcceptDrops(True)
self.TreeView.setDropIndicatorShown(True)
self.ddm = DragDropTreeModel()
self.TreeView.setDragDropMode(QAbstractItemView.InternalMove)
self.TreeView.setDefaultDropAction(Qt.MoveAction)
self.TreeView.setDragDropOverwriteMode(False)
self.root_node = Node('root')
self.ddm.appendRow(self.root_node)
node_1 = Node('1')
self.root_node.appendRow(node_1)
node_2 = Node('2')
self.root_node.appendRow(node_2)
node_d = Node('d')
node_2.appendRow(node_d)
node_a = Node('a')
font = QtGui.QFont()
font.setBold(True)
node_a.setFont(font)
node_1.appendRow(node_a)
node_b = Node('b')
node_1.appendRow(node_b)
node_c = Node('c')
node_1.appendRow(node_c)
self.TreeView.setModel(self.ddm)
self.printButton = QPushButton("Print")
self.vLayout.addWidget(self.TreeView)
self.vLayout.addWidget(self.printButton)
self.printButton.clicked.connect(self.printModelProp)
def drop(self):
print('drop')
def printModelProp(self):
cur_ind = self.TreeView.currentIndex()
obj = self.ddm.itemFromIndex(cur_ind)
obj: Node
print(obj.symbol)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle('fusion')
window = DemoDragDrop()
window.show()
sys.exit(app.exec_())

How to show images with original size ratio in QTableView cells?

i did create a qtableview in which column 1 has images and successfully made ImageDelegate that handles showing images in cells but the problem is with showing them with original ratio they appear to be stretched and maybe a bit smaller
image example:
the sample database used: https://gofile.io/d/LKNdsi
the code:
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import (QApplication, QComboBox, QDialog,
QDialogButtonBox, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout,
QLabel, QLineEdit, QMenu, QMenuBar, QPushButton, QSpinBox, QTextEdit,
QVBoxLayout, QCheckBox, QTableWidgetItem, QAbstractItemView, QTableWidget, QTableView, QStyledItemDelegate)
import sqlite3
class Main(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
conn = sqlite3.connect("math.db")
conn.text_factory = bytes
self.cur = conn.cursor()
data = self.cur.execute("select * from problems;").fetchall();conn.close()
self.dims = (lambda x: (len(x), len(x[0])))(data) #(rows number, col number)
self.model = TableModel([[j.decode("utf-8", "ignore") if u!=1 else j for u, j in enumerate(i)] for i in data])
self.table.setModel(self.model)
self.table.setItemDelegateForColumn(1, ImageDelegate(self))
#self.table.resizeColumnsToContents()
#self.table.resizeRowsToContents()
self.setCentralWidget(self.table)
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def data(self, index, role):
if role == Qt.DisplayRole:
return self._data[index.row()][index.column()]
def rowCount(self, index):
return len(self._data)
def columnCount(self, index):
return len(self._data[0])
class ImageDelegate(QStyledItemDelegate):
def __init__(self, parent):
QStyledItemDelegate.__init__(self, parent)
def paint(self, painter, option, index):
## [ Here is the part that needs something added or fixed ]
pixmap = QtGui.QPixmap()
pixmap.loadFromData(index.data(), "jpg")
painter.drawPixmap(option.rect, pixmap)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
main = Main()
main.resize(600,600)
main.show()
app.exec_()
and here is a snippet code i used to use with QtableWidget that fixed this issue for me in old days but now with QtableView things appears to be different somehow.
snippt class:
class ScaledPixmapLabel(QtWidgets.QLabel):
def __init__(self):
super().__init__()
self.setScaledContents(True)
def paintEvent(self, event):
if self.pixmap():
pm = self.pixmap()
originalRatio = pm.width() / pm.height()
currentRatio = self.width() / self.height()
if originalRatio != currentRatio:
qp = QtGui.QPainter(self)
pm = self.pixmap().scaled(self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
rect = QtCore.QRect(0, 0, pm.width(), pm.height())
rect.moveCenter(self.rect().center())
qp.drawPixmap(rect, pm)
return
super().paintEvent(event)
You have to make the size of column "1" and all its rows depend on the content, that is, on the image. On the other hand, instead of making a personalized painting, I established it as an icon. Also implement that the icon is placed in the center:
from PyQt5 import QtWidgets, QtGui, QtCore
import sqlite3
class ImageDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super(ImageDelegate, self).initStyleOption(option, index)
pixmap = QtGui.QPixmap()
pixmap.loadFromData(index.data(), "jpg")
if not pixmap.isNull():
option.features |= QtWidgets.QStyleOptionViewItem.HasDecoration
option.icon = QtGui.QIcon(pixmap)
option.decorationSize = pixmap.size() / pixmap.devicePixelRatio()
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
return self._data[index.row()][index.column()]
def rowCount(self, index):
return len(self._data)
def columnCount(self, index):
return len(self._data[0])
class ImageCenterProxyStyle(QtWidgets.QProxyStyle):
def subElementRect(self, subElement, option, widget=None):
r = super(ImageCenterProxyStyle, self).subElementRect(
subElement, option, widget
)
if subElement == QtWidgets.QStyle.SE_ItemViewItemDecoration:
r.moveCenter(option.rect.center())
return r
class Main(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
conn = sqlite3.connect("math.db")
conn.text_factory = bytes
self.cur = conn.cursor()
data = self.cur.execute("select * from problems;").fetchall()
conn.close()
self.dims = (lambda x: (len(x), len(x[0])))(data) # (rows number, col number)
self.model = TableModel(
[
[j.decode("utf-8", "ignore") if u != 1 else j for u, j in enumerate(i)]
for i in data
]
)
self.table.setModel(self.model)
self.table.setItemDelegateForColumn(1, ImageDelegate(self))
self.table.resizeRowsToContents()
self.table.horizontalHeader().setSectionResizeMode(
1, QtWidgets.QHeaderView.ResizeToContents
)
proxy = ImageCenterProxyStyle(self.table.style())
self.table.setStyle(proxy)
self.setCentralWidget(self.table)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
main = Main()
main.resize(600, 600)
main.show()
app.exec_()

using delegate for image in qtableview pyqt4 python?

This code will only show same image in the same row. How can i pass different path for image in ImageDelegate? Thanks
class testQT4(QtGui.QTableView):
def __init__(self, parent=None):
QtGui.QTableView.__init__(self, parent)
self.setItemDelegateForColumn(1, ImageDelegate(parent))
#table header
header = [ 'ID','image']
tabledata = [[1,2],[3,4]]
#create table model
self.model = MyTableModel(tabledata, header, self)
#set table model
self.setModel(self.model)
class ImageDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent):
print dir(self)
QtGui.QStyledItemDelegate.__init__(self, parent)
def paint(self, painter, option, index):
painter.fillRect(option.rect, QtGui.QColor(191,222,185))
# path = "path\to\my\image.jpg"
self.path = "image.bmp"
image = QtGui.QImage(str(self.path))
pixmap = QtGui.QPixmap.fromImage(image)
pixmap.scaled(50, 40, QtCore.Qt.KeepAspectRatio)
painter.drawPixmap(option.rect, pixmap)
In the paint method of the delegate you have access to the model via index.model(). You could then query your model for the data (image) you want to show. For example by using the Qt.UserRole for the data function of the model.
Another solution, which might even be easier, is that the data function of the model can return one of QIcon, QPixmap, QImage and QColor for the Qt.DecorationRole. In this case no Delegate is necessary.
As an example the following code will put in Icon in the (only) field of the table:
from PyQt4 import QtGui, QtCore
import PyQt4.uic
# using QtDesigner to just put a TableView in a Widget
Form, Base = PyQt4.uic.loadUiType(r'TableView.ui')
class TableModel( QtCore.QAbstractTableModel ):
def __init__(self, parent=None):
super(TableModel,self).__init__(parent)
def rowCount(self, parent=QtCore.QModelIndex()):
return 1
def columnCount(self, parent=QtCore.QModelIndex()):
return 1
def data(self, index, role):
if index.isValid():
if role==QtCore.Qt.DecorationRole:
return QtGui.QIcon("ChipScope.png")
return None
class TableViewUi(Form, Base ):
def __init__(self, parent=None):
Form.__init__(self)
Base.__init__(self,parent)
def setupUi(self, parent):
Form.setupUi(self,parent)
model = TableModel()
self.tableView.setModel(model)
if __name__=="__main__":
app = QtGui.QApplication(sys.argv)
MainWindow = QtGui.QMainWindow()
ui = TableViewUi()
ui.setupUi(ui)
MainWindow.setCentralWidget(ui)
MainWindow.show()
sys.exit(app.exec_())

Categories