Hello @Max_Fritzler,
There is indeed no way to draw a QLineEdit using the QPainter API. I think that’s because, from Qt point of view, there is no reason to draw a line edit, as you can simply draw some text for displaying it. If you really want it to look like a line edit, you can add a rectangle around your text. The line edit would be useful only when you want to edit the data, and that’s where you can use dedicated overridden methods for that. By this way, you show only one line edit when you want to edit a specific data item, and you paint all the other items. Showing a line edit for each item would use more resource and slower to respond, that’s why you see “warnings” about this.
I have modified the code from the FAQ for adding text drawing and methods for editing the data, using a line edit as editor.
p.s. Sorry for modifying the imports, I prefer this way so I don’t need to constantly update my Qt imports.
import glob
import math
import sys
import typing
from collections import namedtuple
from PyQt5 import QtCore as qc
from PyQt5 import QtGui as qg
from PyQt5 import QtWidgets as qw
# Create a custom namedtuple class to hold our data.
preview = namedtuple("preview", "id title image")
NUMBER_OF_COLUMNS = 4
CELL_PADDING = 20 # all sides
TEXT_HEIGHT = 20
class PreviewDelegate(qw.QStyledItemDelegate):
def createEditor(self, parent:typing.Optional[qw.QWidget], option:qw.QStyleOptionViewItem, index:qc.QModelIndex):
editor = qw.QLineEdit(parent)
return editor
def setEditorData(self, editor:typing.Optional[qw.QWidget], index:qc.QModelIndex):
data:preview = index.model().data(index, qc.Qt.ItemDataRole.DisplayRole)
if data is None:
super().setEditorData(editor, index)
editor.setText(data.title)
def setModelData(self, editor:typing.Optional[qw.QWidget], model:qc.QAbstractItemModel, index:qc.QModelIndex):
text = editor.text()
model.setData(index, text, qc.Qt.ItemDataRole.DisplayRole)
# super().setModelData(editor, model, index)
def updateEditorGeometry(self, editor:typing.Optional[qw.QWidget], option:qw.QStyleOptionViewItem, index:qc.QModelIndex):
data = index.model().data(index, qc.Qt.ItemDataRole.DisplayRole)
if data is None:
return super().updateEditorGeometry(editor, option, index)
width = option.rect.width() - CELL_PADDING * 2
height = option.rect.height() - CELL_PADDING * 2
txt_rect = qc.QRect(
option.rect.x() + CELL_PADDING,
option.rect.y() + CELL_PADDING + height,
width,
TEXT_HEIGHT
)
editor.setGeometry(txt_rect)
# super().updateEditorGeometry(editor, option, index)
def paint(self, painter:typing.Optional[qg.QPainter], option:qw.QStyleOptionViewItem, index:qc.QModelIndex):
# data is our preview object
data = index.model().data(index, qc.Qt.ItemDataRole.DisplayRole)
if data is None:
return
width = option.rect.width() - CELL_PADDING * 2
height = option.rect.height() - CELL_PADDING * 2
# option.rect holds the area we are painting on the widget (our table cell)
# scale our pixmap to fit
scaled = data.image.scaled(
width,
height,
aspectRatioMode=qc.Qt.AspectRatioMode.KeepAspectRatio,
)
# Position in the middle of the area.
x = CELL_PADDING + (width - scaled.width()) / 2
y = CELL_PADDING + (height - scaled.height() - TEXT_HEIGHT) / 2
painter.drawImage(int(option.rect.x() + x), int(option.rect.y() + y), scaled)
# Draw the title below the image, looking like a line edit
txt_rect = qc.QRect(
option.rect.x() + CELL_PADDING,
option.rect.y() + CELL_PADDING + height,
width,
TEXT_HEIGHT
)
painter.drawRect(
txt_rect,
)
painter.drawText(
txt_rect,
qc.Qt.AlignmentFlag.AlignCenter,
data.title,
)
def sizeHint(self, option:qw.QStyleOptionViewItem, index:qc.QModelIndex):
# All items the same size.
return qc.QSize(300, 200)
class PreviewModel(qc.QAbstractTableModel):
def __init__(self, todos=None):
super().__init__()
# .data holds our data for display, as a list of Preview objects.
self.previews = []
def flags(self, index):
return qc.Qt.ItemFlag.ItemIsEditable | qc.Qt.ItemFlag.ItemIsEnabled | qc.Qt.ItemFlag.ItemIsSelectable
def setData(self, index:qc.QModelIndex, value:typing.Any, role:qc.Qt.ItemDataRole):
if role == qc.Qt.ItemDataRole.DisplayRole:
p:preview = self.previews[index.row() * 4 + index.column()]
self.previews[index.row() * 4 + index.column()] = preview(p.id, value, p.image)
return True
return False
def data(self, index:qc.QModelIndex, role:qc.Qt.ItemDataRole):
try:
data:preview = self.previews[index.row() * 4 + index.column()]
except IndexError:
# Incomplete last row.
return
if role == qc.Qt.ItemDataRole.DisplayRole:
return data # Pass the data to our delegate to draw.
if role == qc.Qt.ItemDataRole.ToolTipRole:
return data.title
def columnCount(self, index:qc.QModelIndex):
return NUMBER_OF_COLUMNS
def rowCount(self, index:qc.QModelIndex):
n_items = len(self.previews)
return math.ceil(n_items / NUMBER_OF_COLUMNS)
class MainWindow(qw.QMainWindow):
def __init__(self):
super().__init__()
self.view = qw.QTableView()
self.view.horizontalHeader().hide()
self.view.verticalHeader().hide()
self.view.setGridStyle(qc.Qt.PenStyle.NoPen)
delegate = PreviewDelegate()
self.view.setItemDelegate(delegate)
self.model = PreviewModel()
self.view.setModel(self.model)
self.setCentralWidget(self.view)
# Add a bunch of images.
for n, fn in enumerate(glob.glob("*.jpg")):
image = qg.QImage(fn)
item = preview(n, fn, image)
self.model.previews.append(item)
self.model.layoutChanged.emit()
self.view.resizeRowsToContents()
self.view.resizeColumnsToContents()
app = qw.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()