Is it possible to set a QItemDelegate on a particular QTreeWidgetItem? I need to color some of the QTreeWidgetItems with a particular color.
I assume it is possible as we have QAbstractItemView::setItemDelegateForRow but I can't figure out how. I can't use QAbstractItemView::setItemDelegateForRow because I need to set a custom delegate on a child row inside the QTreeWidget.
Does anyone know a solution for that?
You can't use QTreeWidgetItem in delegate directly (probably you can store list of this items inside delegates but I think that it is not efficient), because delegates works with QModelIndex and data inside different roles. You can set data to Qt::UserRole+1 and access it inside delegate. For example:
QTreeWidgetItem *cities = new QTreeWidgetItem(ui->treeWidget);
//...
cities->setData(0,Qt::UserRole+1,"chosen one");
QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
//...
QTreeWidgetItem *berlinItem = new QTreeWidgetItem(cities);
//...
berlinItem->setData(0,Qt::UserRole+1,"chosen one");
Inside delegate (just example):
void ItemDelegatePaint::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QString txt = index.model()->data( index, Qt::DisplayRole ).toString();
if( option.state & QStyle::State_Selected )
{
if(index.data(Qt::UserRole+1).toString() == "chosen one")
painter->fillRect( option.rect,Qt::green );
else
painter->fillRect( option.rect, option.palette.highlight() );
}else
if(option.state & QStyle::State_MouseOver)
{
if(index.data(Qt::UserRole+1).toString() == "chosen one")
painter->fillRect( option.rect,Qt::yellow );
else
painter->fillRect( option.rect, Qt::transparent );
}
else
{
QStyledItemDelegate::paint(painter,option,index);
}
}
You can access the QTreeWidget from you delegate's paint routine to check if a condition for painting the background is met
void custom_delegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
const QTreeWidget* tree_widget = qobject_cast<const QTreeWidget*>(qstyleoption_cast<const QStyleOptionViewItemV3*>(&option)->widget);
....
}
or you store something in the QModelIndex UserData as Chernobyl suggested. In that case I would however create an enum for flags (if this is applicable in your case):
enum custom_painter_flags{
paint_default = 0,
paint_background = 1
};
void somewhere_creating_the_items()
{
QTreeWidgetItem* newitem = new QTreeWidgetItem(...);
newitem->setData(0, Qt::UserRole, QVariant::fromValue<int>(custom_painter_flags::paint_background));
}
void custom_delegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
custom_painter_flags painter_flags = static_cast<painter>(index.data(Qt::UserRole).value<int>());
if(painter_flags & paint_background){
....
}
}
Unfortunately I have not much time right now so this is thrown together pretty quick. Feel free to edit if you find any errors.
You can use qss on the QTreeWidgetItem to change color or background color.
I have done it for a QTableWidget, You must check the value of all your QTreeWidgetItem and set a background color / color.
For example, for my QTableWidget i have done something like this in a loop :
if(good item):
MyQTableItem.setBackground(QtGui.QColor(255,255,255))
Related
We are using a QTableView with a custom QAbstractTableModel. Some data is too long to display in the cells directly or we would like to show additional information.
In the model we use the following code:
QVariant MyTableModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole) {
return "Short Content";
}
if (role == Qt::ToolTipRole) {
return "Super long content which contains line\nbreaks, tabs\t and more.";
}
return QVariant();
}
This works and when hovering the cells the tooltip is displayed. However, this takes a few seconds for the tooltip to appear and sometimes some mouse-wiggling.
Is there a built-in way in Qt 5 to disable the timeout and always display the tooltip?
As they point out in the Qt forum the delay depends on the style and is returned through SH_ToolTip_WakeUpDelay which is 700 ms by default.
Considering the above, a possible solution is to use the QProxyStyle method override:
class ProxyStyle : public QProxyStyle
{
public:
using QProxyStyle::QProxyStyle;
int styleHint(StyleHint hint, const QStyleOption* option = nullptr, const QWidget* widget = nullptr, QStyleHintReturn* returnData = nullptr) const override
{
if (hint == QStyle::SH_ToolTip_WakeUpDelay)
return 0;
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
};
tableview->setStyle(new ProxyStyle(tableview->style()));
I want to create same kind of QTreeView (not QTreeWidget) structure as shown in attached figure.. This is Property Editor of QT.
I am using QT-4.6
On 2nd column, depending on different condition, I can have either a spin box, or a drop down or a checkbox or text edit... and so on...
Please guide me on how to set different delegates in different cells of a particular column.
From docs, it is evident that there is no straight away API for setting delegate on a cell (rather is available for full widget or a row or a column).
All QAbstractItemDelegate methods, like createEditor or paint, have a model index as one of their parameters. You can access model data using that index and create an appropriate delegate widget. When you create your model you should set some value to every item that will be used to distinguish its type.
An example:
enum DelegateType
{
DT_Text,
DT_Checkbox,
DT_Combo
}
const int MyTypeRole = Qt::UserRole + 1;
QStandardItemModel* createModel()
{
QStandardItemModel *model = new QStandardItemModel;
QStandardItem *item = new QStandardItem;
item->setText("Hello!");
item->setData(DT_Checkbox, MyTypeRole);
model->appendRow(item);
return model;
}
QWidget* MyDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
int type = index.data(MyTypeRole).toInt();
// this is a simplified example
switch (type)
{
case DT_Text:
return new QLinedEdit;
case DT_Checkbox:
return new QCheckBox;
case DT_Combo:
return new QComboBox;
default:
return QItemDelegate::createEditor(parent, option, index);
}
}
#hank This is in response to your last comment... Do you see any flaw in it ?
MyItem* item2 = new MyItem(second);
item2->setData(delType, **MyTypeRole**);
if(delType == DT_Combo)
{
QString str1, str2, str3;
QStringList abc ;
abc << ("1" + str1.setNum(counter) ) << ("2" + str2.setNum(counter) )<< ( "3" + str3.setNum(counter) );
item2->setData(abc, MyTypeRole1);
}
QWidget* MyDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
int type = index.data(MyTypeRole).toInt();
// this is a simplified example
switch (type)
{
case DT_Text:
return new QLinedEdit;
case DT_Combo:
{
QComboBox* cb = new QComboBox(parent);
QStringList entries - index.data(MyTypeRole1).toStringList();
cb->addItems(entries)
return cb;
}
On different item2, I dynamically create entries with a counter variable that is different everytime it comes here...
Here, different combo boxes display different entries.
Does the approach looks fine to you ?
I'm using Qt 4.7.
I have a model that I display in a QTableView in two columns, and my goal is to provide inline editing of this model in my QTableView.
+-----------------+----------------+
| Axis position | Axis range |
+-----------------+----------------+
| Left | Fixed [0,1] |
| Left | Source: SRC1 |
| Right | Source: SRC2 |
| Left | Fixed [5,10] |
+-----------------+----------------+
The first column is editable by using a simple QComboxBox to switch between Right and Left, and it works quite well. The problem is with my second column, which is editable using a custom widget.
This widget is kind of simple, it describes a range. So there is a QComboBox to select the kind of range ("Fixed": the values are given by the user, "Source": the value are adjusted dynamically from the min/max of a source).
Here is the source code of my custom widget:
class RangeEditor : public QWidget
{
Q_OBJECT
public:
RangeEditor( ... );
~RangeEditor();
public:
CurveView::ConfigAxes::Range range () const;
QVariant minimum() const;
QVariant maximum() const;
DataModel* model () const;
void range ( CurveView::ConfigAxes::Range range );
void minimum( QVariant minimum );
void maximum( QVariant maximum );
void model ( DataModel* model );
public slots:
void rangeTypeChanged( int type );
private: // --- External editors
QComboBox* editRange_;
QSpinBox* editMinimum_;
QSpinBox* editMaximum_;
QComboBox* editModel_;
};
RangeEditor::RangeEditor( ... ) : QWidget(parent)
{
editRange_ = new QComboBox(this);
editMinimum_ = new QSpinBox (this);
editMaximum_ = new QSpinBox (this);
editModel_ = new QComboBox(this);
QHBoxLayout* layout = new QHBoxLayout();
setLayout(layout);
layout->addWidget( editRange_ );
layout->addWidget( editMinimum_ );
layout->addWidget( editMaximum_ );
layout->addWidget( editModel_ );
editRange_->addItem( "Fixed" );
editRange_->addItem( "Source" );
editModel_->setCurrentIndex(0);
editModel_->hide();
QObject::connect( editRange_, SIGNAL(currentIndexChanged(int)),
this, SLOT (rangeTypeChanged(int)) );
}
void RangeEditor::rangeTypeChanged( int type )
{
if ( type==CurveView::ConfigAxes::FIXED )
{
editMinimum_->show();
editMaximum_->show();
editModel_->hide();
}
else if ( type==CurveView::ConfigAxes::SOURCE )
{
editMinimum_->hide();
editMaximum_->hide();
editModel_->show();
}
}
Okay, so now, I created a QStyledItemDelegate to provide the view a custom editor for my columns. Here is how I did it:
class ConfigAxesDelegate : public QStyledItemDelegate
{
public:
ConfigAxesDelegate( ... );
~ConfigAxesDelegate();
public:
virtual QWidget* createEditor ( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
virtual void setEditorData ( QWidget* editor, const QModelIndex& index ) const;
virtual void setModelData ( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const;
virtual void updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
};
QWidget* ConfigAxesDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
if ( index.column()==0 ) // Position
{
PositionEditor* editor = new PositionEditor(parent);
return editor;
}
else if ( index.column()==1 ) // Range
{
RangeEditor* editor = new RangeEditor(parent);
return editor;
}
else
{
return QStyledItemDelegate::createEditor(parent,option,index);
}
}
void ConfigAxesDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
// WHAT TO DO HERE?
editor->setGeometry( option.rect );
}
Basically, what I get is a single pixel height editor.
Here is a screenshot of the result:
I tried to changed updateEditorGeometry to the following:
void ConfigAxesDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
QRect r = option.rect;
r.setSize( editor->sizeHint() );
editor->setGeometry( r );
}
Which seems to fix the size problem, but not the position:
I feel kind of lost since I don't know if the problem comes from my custom widget (not providing enough information for Qt to compute its position properly), or the view (maybe some margins that would crush the editor size), or the updateEditorGeometry() method.
Any help greatly appreciated, thanks for reading!
I would say setting editor's geometry by calling:
editor->setGeometry(rect);
should work correctly; What happens in your case is that your editor is built using QHBoxLayout which has default margins and spacing set. Default height of your tableview rows is less then editor's height and this makes your editor to resize; one pixel row on your screen shot would be: top margin + what's left from controls + bottom margin.
By enabling the vertical header for your tableview you would be able to resize row height to make your editor controls completely visible.
What you could possibly do:
1.Remove\decrease spacing and margins for the layout:
QHBoxLayout* layout = new QHBoxLayout();
layout->setSpacing(1);
layout->setMargin(1);
setLayout(layout);
in this case, updating editor's geometry this way:
QRect rect = option.rect;
QSize sizeHint = editor->sizeHint();
if (rect.width()<sizeHint.width()) rect.setWidth(sizeHint.width());
if (rect.height()<sizeHint.height()) rect.setHeight(sizeHint.height());
editor->setGeometry(rect);
or just
editor->setGeometry(rect);
should work fine for you
2.You can also consider using popup editors for your rows\cells values
3.Resize widget's row heights to fit cell editors.
hope this helps, regards
Is there any way to determine if a QTableView has an open editor in the current cell? I need to handle the following situation:
A user double-clicks a cell and edits the data, but leaves the cell in the "edit" state.
On another part of the UI, an action is taken that changes the selected row of the underlying model.
Back on my view, I want to determine if the newly selected row is the same as the open row. If not, I need to take an action. (Prompt the user? Commit automatically? Revert?)
I see how to get the current item, and can get the delegate on that item, but I don't see any isEditMode() property I was hoping to find.
Can someone point me in the right direction?
Just check whether the return value of
State QAbstractItemView::state () const
is
QTableView::EditingState
Connect to underlying model dataChanged signal
void QAbstractItemModel::dataChanged ( const QModelIndex & topLeft, const QModelIndex & bottomRight )
You can check if the cell where data has changed is the same than the currentIndex
QModelIndex QAbstractItemView::currentIndex () const
You cannot know if the current cell had an open editor straight, but can check if the view is in QAbstractItemView::EditingState
State QAbstractItemView::state () const
It should be enough to do what you want.
You can subclass QTableView in order to be able to access the state() function, which is unfortunately protected. However, I did not try that.
If you already have an QStyledItemDelegate subclass, you can use it to track whether an editor is currently open. However, you can't just use setEditorData/setModelData, because setModelData won't be called, when the user cancels editing. Instead, you can track the creation and destruction of the editor itself.
class MyItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
MyItemDelegate( QObject* parent = nullptr );
~MyItemDelegate();
QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
void setEditorData( QWidget* editor, const QModelIndex& index ) const;
void setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const;
bool isEditorOpen() const { return *m_editorCount > 0; }
protected:
int* m_editorCount;
protected slots:
void onEditorDestroyed( QObject* obj );
};
Implementation:
MyItemDelegate::MyItemDelegate( QObject* parent ) :
QStyledItemDelegate( parent )
{
m_editorCount = new int;
*m_editorCount = 0;
}
MyItemDelegate::~MyItemDelegate()
{
delete m_editorCount;
}
QWidget* MyItemDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
// create an editor, can be changed as needed
QWidget* editor = QStyledItemDelegate::createEditor( parent, option, index );
connect( editor, SIGNAL(destroyed(QObject*)), SLOT(onEditorDestroyed(QObject*)));
printf( "editor %p created\n", (void*) editor );
(*m_editorCount)++;
return editor;
}
void MyItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
...
}
void MyItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
...
}
void MyItemDelegate::onEditorDestroyed( QObject* obj )
{
printf( "editor %p destroyed\n", (void*) obj );
(*m_editorCount)--;
}
On some occasions, e.g. when moving to the next item in the tree using the cursor keys, Qt will create the new editor first and then destroy the old one. Hence, m_editorCount must be an integer instead of a bool.
Unfortunately, createEditor() is a const function. Therefore, you cannot create an int-member. Instead, create a pointer to an int and use that.
Subclass your delegate so that it includes an accessor that tells you when it's editing:
void MyDelegate::setEditorData ( QWidget * editor, const QModelIndex & index ) const {
// _isEditing will have to be mutable because this method is const
_isEditing = true;
QStyledItemDelegate::setEditorData(editor, index);
}
void MyDelegate::setModelData ( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) const {
QStyledItemDelegate::setModelData(editor, model, index);
_isEditing = false;
}
bool MyDelegate::isEditing() const { return _isEditing; }
Then you can just check the delegate to see what's going on. Alternatively and/or if you don't like the mutable, you can emit signals so you know what state the delegate is in.
If you know the index of the item being edited, you can call indexWidget() and attempt to cast it. If it's valid, you not only know you're editing, but you also have your editor widget handy.
EditWidget *editWidget = qobject_cast<EditWidget*>(tableView->indexWidget(tableView->currentIndex()));
if(editWidget)
{
//yep, ur editing bro
}
Here is an idea, its even helpful to get the edit/combo widget before the edit begins...
just emit a signal and consume it in the mainwindow... this is what I used one to get combo box in QTableWidget before editing...
first create a signal in ComoBoxItemDelegate...
signals:
void OnComboEdit(QComboBox* pCombo) const;
then emit the signal in the createEditor method...
QWidget* ComboBoxItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
// Create the combobox and populate it
QComboBox* cb = new QComboBox(parent);
emit OnComboEdit(cb);
return cb;
}
and in the MainWindow declare a function to receive the singal...
void MainWindow::OnComboEidt(QComboBox *pCB) const
{
qDebug() << "Combo Eidt Singal Received";
}
Then finally in the constructor of MainWindow connect it...
ComboBoxItemDelegate* cbid = new ComboBoxItemDelegate(ui->tableWidget);
connect(cbid, &ComboBoxItemDelegate::OnComboEdit, this, &MainWindow::OnComboEidt);
ui->tableWidget->setItemDelegateForColumn(0, cbid);
Suppose my model has items with the following string for Qt::DisplayRole
<span>blah-blah <b>some text</b> other blah</span>
I want QTreeView (actually, any item view) to render it like a rich text. Instead, item views render it like a pure text by default. How to achieve the desired rendering?
Actually, this is a search results model. User enters a text, some document is searched against that text and the user is presented with search results, where the words being searched should be bolder than surrounding text.
I guess you can use setItemDelegate method of the treeview to setup custom painter for your treeview items. In the delegate's paint method you can use QTextDocument to load item's text as html and render it. Please check if an example below would work for you:
treeview initialization:
...
// create simple model for a tree view
QStandardItemModel *model = new QStandardItemModel();
QModelIndex parentItem;
for (int i = 0; i < 4; ++i)
{
parentItem = model->index(0, 0, parentItem);
model->insertRows(0, 1, parentItem);
model->insertColumns(0, 1, parentItem);
QModelIndex index = model->index(0, 0, parentItem);
model->setData(index, "<span>blah-blah <b>some text</b> other blah</span>");
}
// create custom delegate
HTMLDelegate* delegate = new HTMLDelegate();
// set model and delegate to the treeview object
ui->treeView->setModel(model);
ui->treeView->setItemDelegate(delegate);
...
custom delegate implementation
class HTMLDelegate : public QStyledItemDelegate
{
protected:
void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
};
void HTMLDelegate::paint(QPainter* painter, const QStyleOptionViewItem & option, const QModelIndex &index) const
{
QStyleOptionViewItemV4 options = option;
initStyleOption(&options, index);
painter->save();
QTextDocument doc;
doc.setHtml(options.text);
options.text = "";
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter);
painter->translate(options.rect.left(), options.rect.top());
QRect clip(0, 0, options.rect.width(), options.rect.height());
doc.drawContents(painter, clip);
painter->restore();
}
QSize HTMLDelegate::sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
QStyleOptionViewItemV4 options = option;
initStyleOption(&options, index);
QTextDocument doc;
doc.setHtml(options.text);
doc.setTextWidth(options.rect.width());
return QSize(doc.idealWidth(), doc.size().height());
}
hope this helps, regards
update0: changes to HTMLDelegate to make icons visible and different pen color for selected items
void HTMLDelegate::paint(QPainter* painter, const QStyleOptionViewItem & option, const QModelIndex &index) const
{
QStyleOptionViewItemV4 options = option;
initStyleOption(&options, index);
painter->save();
QTextDocument doc;
doc.setHtml(options.text);
options.text = "";
options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter);
// shift text right to make icon visible
QSize iconSize = options.icon.actualSize(options.rect.size());
painter->translate(options.rect.left()+iconSize.width(), options.rect.top());
QRect clip(0, 0, options.rect.width()+iconSize.width(), options.rect.height());
//doc.drawContents(painter, clip);
painter->setClipRect(clip);
QAbstractTextDocumentLayout::PaintContext ctx;
// set text color to red for selected item
if (option.state & QStyle::State_Selected)
ctx.palette.setColor(QPalette::Text, QColor("red"));
ctx.clip = clip;
doc.documentLayout()->draw(painter, ctx);
painter->restore();
}
My answer is mostly inspired by #serge_gubenko's one. However, there were made several improvements so that the code is finally useful in my application.
class HtmlDelegate : public QStyledItemDelegate
{
protected:
void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
};
void HtmlDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItemV4 optionV4 = option;
initStyleOption(&optionV4, index);
QStyle *style = optionV4.widget? optionV4.widget->style() : QApplication::style();
QTextDocument doc;
doc.setHtml(optionV4.text);
/// Painting item without text
optionV4.text = QString();
style->drawControl(QStyle::CE_ItemViewItem, &optionV4, painter);
QAbstractTextDocumentLayout::PaintContext ctx;
// Highlighting text if item is selected
if (optionV4.state & QStyle::State_Selected)
ctx.palette.setColor(QPalette::Text, optionV4.palette.color(QPalette::Active, QPalette::HighlightedText));
QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &optionV4);
painter->save();
painter->translate(textRect.topLeft());
painter->setClipRect(textRect.translated(-textRect.topLeft()));
doc.documentLayout()->draw(painter, ctx);
painter->restore();
}
QSize HtmlDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItemV4 optionV4 = option;
initStyleOption(&optionV4, index);
QTextDocument doc;
doc.setHtml(optionV4.text);
doc.setTextWidth(optionV4.rect.width());
return QSize(doc.idealWidth(), doc.size().height());
}
Here's the PyQt conversion of the combination of the above answers that worked for me. I would expect this to work virtually identically for PySide as well.
from PyQt4 import QtCore, QtGui
class HTMLDelegate(QtGui.QStyledItemDelegate):
def paint(self, painter, option, index):
options = QtGui.QStyleOptionViewItemV4(option)
self.initStyleOption(options,index)
style = QtGui.QApplication.style() if options.widget is None else options.widget.style()
doc = QtGui.QTextDocument()
doc.setHtml(options.text)
options.text = ""
style.drawControl(QtGui.QStyle.CE_ItemViewItem, options, painter);
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
# Highlighting text if item is selected
#if (optionV4.state & QStyle::State_Selected)
#ctx.palette.setColor(QPalette::Text, optionV4.palette.color(QPalette::Active, QPalette::HighlightedText));
textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, options)
painter.save()
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
doc.documentLayout().draw(painter, ctx)
painter.restore()
def sizeHint(self, option, index):
options = QtGui.QStyleOptionViewItemV4(option)
self.initStyleOption(options,index)
doc = QtGui.QTextDocument()
doc.setHtml(options.text)
doc.setTextWidth(options.rect.width())
return QtCore.QSize(doc.idealWidth(), doc.size().height())
This one is in PySide. Rather than doing a lot of custom drawing, I pass the QPainter to the QLabel and make it draw itself. Highlighting code borrowed from other answers.
from PySide import QtGui
class TaskDelegate(QtGui.QItemDelegate):
#https://doc.qt.io/archives/qt-4.7/qitemdelegate.html#drawDisplay
#https://doc.qt.io/archives/qt-4.7/qwidget.html#render
def drawDisplay(self, painter, option, rect, text):
label = QtGui.QLabel(text)
if option.state & QtGui.QStyle.State_Selected:
p = option.palette
p.setColor(QtGui.QPalette.WindowText, p.color(QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
label.setPalette(p)
label.render(painter, rect.topLeft(), renderFlags=QtGui.QWidget.DrawChildren)
Writing up yet another answer for how this can be done in C++. The difference to the answers provided so far is that this is for Qt5 and not Qt4. Most importantly however the previous answers neglected that the item delegate should be able to align the text as specified (e.g. in a QTreeWidget). Additionally I also implemented a way to elide rich text in order to get a consistent feeling with plaintext delegates (in ItemViews).
So without further ado, here is my code for a RichTextDelegate:
void RichTextItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &inOption,
const QModelIndex &index) const {
QStyleOptionViewItem option = inOption;
initStyleOption(&option, index);
if (option.text.isEmpty()) {
// This is nothing this function is supposed to handle
QStyledItemDelegate::paint(painter, inOption, index);
return;
}
QStyle *style = option.widget ? option.widget->style() : QApplication::style();
QTextOption textOption;
textOption.setWrapMode(option.features & QStyleOptionViewItem::WrapText ? QTextOption::WordWrap
: QTextOption::ManualWrap);
textOption.setTextDirection(option.direction);
QTextDocument doc;
doc.setDefaultTextOption(textOption);
doc.setHtml(option.text);
doc.setDefaultFont(option.font);
doc.setDocumentMargin(0);
doc.setTextWidth(option.rect.width());
doc.adjustSize();
if (doc.size().width() > option.rect.width()) {
// Elide text
QTextCursor cursor(&doc);
cursor.movePosition(QTextCursor::End);
const QString elidedPostfix = "...";
QFontMetrics metric(option.font);
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
int postfixWidth = metric.horizontalAdvance(elidedPostfix);
#else
int postfixWidth = metric.width(elidedPostfix);
#endif
while (doc.size().width() > option.rect.width() - postfixWidth) {
cursor.deletePreviousChar();
doc.adjustSize();
}
cursor.insertText(elidedPostfix);
}
// Painting item without text (this takes care of painting e.g. the highlighted for selected
// or hovered over items in an ItemView)
option.text = QString();
style->drawControl(QStyle::CE_ItemViewItem, &option, painter, inOption.widget);
// Figure out where to render the text in order to follow the requested alignment
QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &option);
QSize documentSize(doc.size().width(), doc.size().height()); // Convert QSizeF to QSize
QRect layoutRect = QStyle::alignedRect(Qt::LayoutDirectionAuto, option.displayAlignment, documentSize, textRect);
painter->save();
// Translate the painter to the origin of the layout rectangle in order for the text to be
// rendered at the correct position
painter->translate(layoutRect.topLeft());
doc.drawContents(painter, textRect.translated(-textRect.topLeft()));
painter->restore();
}
QSize RichTextItemDelegate::sizeHint(const QStyleOptionViewItem &inOption, const QModelIndex &index) const {
QStyleOptionViewItem option = inOption;
initStyleOption(&option, index);
if (option.text.isEmpty()) {
// This is nothing this function is supposed to handle
return QStyledItemDelegate::sizeHint(inOption, index);
}
QTextDocument doc;
doc.setHtml(option.text);
doc.setTextWidth(option.rect.width());
doc.setDefaultFont(option.font);
doc.setDocumentMargin(0);
return QSize(doc.idealWidth(), doc.size().height());
}
Just a slight update from jbmohler's answer, for PyQt5: some classes have apparently been shifted to QtWidgets.
This is way beyond my paygrade (i.e. knowledge of the nuts and bolts behind PyQt5).
I echo the sentiment expressed in Cecil Curry's comment to the question. It is now 2021, and we appear still to have to struggle with this sort of hack. Ridiculous. I've been impressed by Qt5 to date, as compared to JavaFX for example. This deficiency is a let-down.
class HTMLDelegate( QtWidgets.QStyledItemDelegate ):
def __init__( self ):
super().__init__()
# probably better not to create new QTextDocuments every ms
self.doc = QtGui.QTextDocument()
def paint(self, painter, option, index):
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(options, index)
painter.save()
self.doc.setTextWidth(options.rect.width())
self.doc.setHtml(options.text)
self.doc.setDefaultFont(options.font)
options.text = ''
options.widget.style().drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)
painter.translate(options.rect.left(), options.rect.top())
clip = QtCore.QRectF(0, 0, options.rect.width(), options.rect.height())
painter.setClipRect(clip)
ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
ctx.clip = clip
self.doc.documentLayout().draw(painter, ctx)
painter.restore()
def sizeHint( self, option, index ):
options = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(option, index)
self.doc.setHtml(option.text)
self.doc.setTextWidth(option.rect.width())
return QtCore.QSize(self.doc.idealWidth(), self.doc.size().height())