Set border to an image in QListView in Qt - qt

I am setting a QPixmap to a QStandardItem:
QStandardItem* item = new QStandardItem();
item->setData( pixmap, Qt::DecorationRole );
Then I do appendRow() and add item to the Model.
I display all the pixmaps in the Model in a QListView.
How do I set a thin border to only the 1st item (image) in the ListView ?

Subclass QStyledItemDelegate and override it´s paint function. Use this to paint a border to your item. Then set that delegate to your QListView.
Example:
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if(index.row() == 0)
{
painter->setPen(QPen(Qt::red, 2));
painter->drawRect(option.rect.x()+1, option.rect.y(), option.rect.width()-1, option.rect.height());
}
QStyledItemDelegate::paint(painter, option, index);
}
And to set the delegate for your QListView:
listView->setItemDelegate(new MyDelegate);
You don't necessarely have to check the row in the paint function. You can just set the delegate for a specific row:
listView->setItemDelegateForRow(0, new MyDelegate);

Related

QStyledItemDelegate paint event on cell (not row) hover

I have a custom QStyledItemDelegate that paints a QPixmap in a particular column. When that cell is hovered over with the mouse, I would like to paint it differently.
Below is my paint event, which does paint the cell correctly when not State_MouseOver. However, it changes the color when I hover anywhere on the row. How can I make it change only when the mouse is hovering over the cell with the pixmap in it?
void myDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_ASSERT(index.isValid());
switch(index.column()) {
case DAY_COLUMN:
{
QSize btnSize = QSize(option.rect.height() * .9, option.rect.height() * .9);
QRect r = option.rect;
int x = r.right() - btnSize.width() - 10;
int y = r.top();
QRect btnRect = QRect(x, y, btnSize.width(), btnSize.height());
QPixmap pixmap(":/icons/edit.png");
// If hovered over, change color.
if(option.state & QStyle::State_MouseOver) {
auto mask = pixmap.createMaskFromColor(QColor("Black"), Qt::MaskOutColor);
pixmap.fill(QColor("Red"));
pixmap.setMask(mask);
}
painter->drawPixmap(btnRect, pixmap, pixmap.rect());
return;
}
/*.... draw other column(s) as appropriate ...*/
}
}
I'm using this delegate on all rows withing a QTreeView.
Qt 5.12
It can be because the selection behavior of the QTreeView is QAbstractItemView::SelectRows by default.
You can change it using:
m_tree_view.setSelectionBehavior(QAbstractItemView::SelectItems);
See more:
QAbstractItemView::SelectionBehavior
QTreeView source code

QCombobox not using custom delegate to render current item

I am using a custom delegate to render rich text in a QComboBox. It is used to allow users to select a color to plot a variable on a graph. It works for items in the drop down menu, but not the selected item. Any help would be appreciated.
Here is the code for the delegate I am using:
class CustomDelegate : public QStyledItemDelegate
{
public:
CustomDelegate();
protected:
void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const;
};
void CustomDelegate::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 CustomDelegate::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());
}
I set the options and delegate for the combo box using code similar, I removed some of the options to reduce the size of the code:
ui->SelectColor->clear();
ui->SelectColor->addItem("Select Color");
ui->SelectColor->addItem("<font color='blue'>Blue</font>");
ui->SelectColor->addItem("<font color='darkBlue'>Dark Blue</font>");
ui->SelectColor->addItem("<font color='red'>Red</font>");
ui->SelectColor->addItem("Dark Yellow");
ui->SelectColor->addItem("<font color='magenta'>Dark Magenta</font>");
ui->SelectColor->addItem("White");
ui->SelectColor->setItemDelegate(new CustomDelegate);
Some of the options are just the names because they don't look good when rendered.
Your custom styled item delegate is applied to items that are part of QComboBox (i.e. ones in QComboBox popup), not for the current item which is represented. The easiest way to achieve what you want would be to introduce your custom class inheriting QComboBox, and then override void QWidget::paintEvent(QPaintEvent *event) method by applying changes you would like to introduce, e.g. set some colored text. Another way (in case you can not introduce another class inheriting QComboBox for some reason) would be to introduce an event filter that does something just after the QPaintEvent. However, usage of event filters might be tricky, and I would advise you to just introduce some another class, and then override paint event.
Now, in case you want to show current item in the same way as an option in the combo box list popup, you could do the following (code is not complete, and you should apply it to your needs yourself):
virtual void paintEvent(QPaintEvent* e) override
{
// QComboBox::paintEvent(e); - this will leave just a rectangle in which you can perform your custom drawings.
// Will make your option colored at least.
QPainter p(this);
QTextDocument doc;
doc.setHtml(this->currentText());
doc.drawContents(&p, rect());
}
Finally, it is worth to mention that QComboBox painting is pretty complex, and you might still need to rewrite pretty everything that Qt already has done for you, so it would be possible to apply other styles you might want not to lose. In order to do that, you should take a look into the source code of this class. You can do this in the Code Browser by Woboq for C & C++ (QComboBox). Hopefully, this clarifies the problem for you more, and now you know what to do in order to achieve your goal.

QTreeView color of painted row

I extented a QStyledItemDelegate to draw some pixmaps on the right side of a QTreeView. This works well, however, I realized that the pixmaps can be in front of the text if it gets too long.
So I tried to draw a rectangle of the same color as the background before drawing my pixmap.
void MyItemDelegate::paint(
QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
...
QStyledItemDelegate::paint(painter, option, index);
...
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
...
painter->fillRect(rect, opt.backgroundBrush);
painter->drawPixmap(topLeft, pixmap);
}
I know that all variables are correct except opt.backgroundBrush. It's always invisible black, with or without the initStyleOption call.
Then I started to check the value of opt.state and opt.features to pick the color myself. I had some success with the alternating color (QStyleOptionViewItem::Alternate) but it's starting to get long and tedious because there are other states (hover and selected) and other OS that may have other states. There must be a simpler way.
So, my question is: How can I get the actual color that will be used to paint the row when I'm in the paint method? Or, do you have any other clean way to avoid this situation?
As suggested by #G.M. I used the QItemDelegate class instead of QStyledItemDelegate and I do have more control on the drawing process. Notably because of the added protected functions like drawDisplay. Overriding this method, I can adjust the "display rectangle" to a smaller size than what was calculated by Qt.
void ActionsItemDelegate::drawDisplay(
QPainter *painter,
const QStyleOptionViewItem &option,
const QRect &rect,
const QString &text) const
{
const int minX = ...; // x position of the left-most icon
const int iconsWidth = rect.width() - (minX - rect.x());
QItemDelegate::drawDisplay(painter, option, rect.adjusted(0, 0, -iconsWidth, 0), text);
}
The drawback is that the UI changes when using QStyledItemDelegate. I don't know how to get the benefits of both: normal style and more controls.
I may process differently. I won't start by calling:
QStyledItemDelegate::paint(painter, option, index);
in the first place.
Then, the important part: if you know how much icons there are on your items, you could calculate the bounding rectangle.
How do you know how many icons you have ? Maybe by calling something like:
bool hasIcon = index->data(YourAPP::HasIcons).toBool();
You could have something like this in your delegate:
void MyItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
...
QStyleOptionViewItem o = option;
initStyleOption(&o, index);
bool hasIcon = index->data(YourModel::HasIcons).toBool();
// or
bool hasCustomRole1 = index->data(YourModel::Role1).toBool();
bool hasCustomRole2 = index->data(YourModel::Role2).toBool();
if (hasIcon) {
// width of your icons, can be like o.rect.height() * numbers of icons
int width = 0;
// shrink your first rectangle
o.rect.adjust(0, 0, -width, 0);
// create a second rectangle
QStyleOptionViewItem o2 = option;
initStyleOption(&o2, index);
o2.rect.adjust(width, 0, 0, 0);
// paint your icons
...
painter->drawPixmap(o2, pixmap);
} else {
QStyledItemDelegate::paint(painter, option, index);
}

Painting QPixmap in the center of QTableView cell

I have a QTableView that works very well, the first column holds some thumbnails, in each cell of this column the thumbnails are vertically centered, but not horizontally centered.
Do I really need to use a delegate?
If yes, How to center them horizontally using QStyledItemDelegate?
Construct your own delegate and inherit QStyledItemDelegate. Override the paint method.
Then do something like this:
void
MyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
QPixmap pixmap;
pixmap.load("Your pixmap file path");
pixmap = pixmap.scaled(option.rect.width(), option.rect.height(), Qt::KeepAspectRatio);
// Position our pixmap
const int x = option.rect.center().x() - pixmap.rect().width() / 2;
const int y = option.rect.center().y() - pixmap.rect().height() / 2;
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
}
painter->drawPixmap(QRect(x, y, pixmap.rect().width(), pixmap.rect().height()), pixmap);
}
Drawing by yourself is not necessary, but a custom delegate - is. The styled item delegate uses the style's control element drawing code to draw a CE_ItemViewItem - see the source code for Qt 5.5.0. The drawing code takes the style option's decorationAlignment member into account. Unfortunately, there's no data role that would pass that alignment to the styles's implementation. Instead, you have to override the alignment in your delegate:
class DecorationAligningDelegate : public QStyledItemDelegate {
Q_OBJECT
Qt::Alignment const m_alignment;
public:
explicit DecorationAligningDelegate(Qt::Alignment alignment, QObject * parent = 0) :
QStyledItemDelegate(parent), m_alignment(alignment) {}
Qt::Alignment alignment() const { return m_alignment; }
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const {
auto opt = option;
opt.decorationAlignment = m_alignment;
QStyledItemDelegate::paint(painter, opt, index);
}
};
Then, to center the thumbnails:
view.setItemDelegateForColumn(0,
new DecorationAligningDelegate(Qt::AlignHCenter, &view));
//or
view->setItemDelegateForColumn(0,
new DecorationAligningDelegate(Qt::AlignHCenter, view));
If you really wished to paint it all yourself, even though it's unnecessary, the rectangle of the item to be painted is given in the style option (option.rect). To draw the pixmap centered in the item's rectangle, you could do as follows:
QStyleOption option;
QPixmap pix;
QPainter painter;
...
painter.save();
auto loc = option.rect.center() - pix.rect().center()
painter.drawPixmap(loc, pix);
painter.restore();
I will just leave my version that literary is a combination of the two answers.
class DecorationAligningDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit DecorationAligningDelegate(Qt::Alignment alignment, QObject *parent = nullptr)
: QStyledItemDelegate(parent), m_alignment(alignment) {}
Qt::Alignment alignment() const { return m_alignment; }
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QIcon icon = QIcon(qvariant_cast<QIcon>(index.data(Qt::DecorationRole)));
if (option.state & QStyle::State_Selected)
{
painter->fillRect(option.rect, option.palette.highlight());
}
icon.paint(painter, option.rect, m_alignment);
}
private:
Q_DISABLE_COPY(VDecorationAligningDelegate)
Qt::Alignment const m_alignment;
};
I assume you define your item like this:
auto *item = new QTableWidgetItem();
item->setIcon(QIcon("Your pixmap file path"));
Don't forget about setting a delegate.

QListView and delegate display unintended item

I've a problem with my QListView, it paint an unintended item on the top left of the QListView :
http://s4.postimage.org/64orbk5kd/Screen_Shot_2013_02_14_at_20_23_14.png
I use a QStyledItemDelegate in my QListView :
m_stringList.push_back("FIRST");
m_stringList.push_back("SECOND");
m_stringList.push_back("THIRD");
m_model.setStringList(m_stringList);
ui->processesListView->setFlow(QListView::LeftToRight);
ui->processesListView->setModel(&m_model);
ui->processesListView->setItemDelegate(new ProcessItemDelegate(this, ui->processesListView));
The delegate (ProcessItemDelegate) paint method use a custom QWidget to display the information :
void ProcessItemDelegate::paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex &inIndex ) const
{
_listItem->setContent(_listView->model()->data(inIndex).toString());
painter->save();
painter->translate(option.rect.center());
_listItem->render(painter);
painter->restore();
}
The setContent method of the QWidget is very simple :
void ProcessItem::setContent(const QString &s)
{
ui->processId->setText(s);
}
I have another way to add a widget to some list using a QListWidget.
For example knowing that ui->historyView is a QListWidget element and HistoryElementView a subclass of QWidget.
void View::onHistoryChanged(const QList<HistoryElement> &history)
{
clearHistory();
foreach(HistoryElement elt, history)
{
HistoryElementView *historyViewElement = new HistoryElementView(elt.getDateTime("dd/MM/yyyy - hh:mm"), elt.getFilename());
QListWidgetItem *item = new QListWidgetItem();
ui->historyView->addItem(item);
ui->historyView->setItemWidget(item, historyViewElement);
}
}
void View::clearHistory()
{
QListWidgetItem *item;
while (ui->historyView->count() != 0)
{
item = ui->historyView->takeItem(0);
delete item;
}
}
You do not need to delete the widgets inside your QListWidgetItem, it will be handle by Qt.
Once your widgets are inside the list, you can retrieve them using :
// Using index
QListWidgetItem *item = ui->historyView->item(0);
HistoryElementView *elt = qobject_cast<HistoryElementView *>(ui->historyView->itemWidget(item));
// Using position
QListWidgetItem *item = ui->historyView->itemAt(pos);
HistoryElementView *historyElement = qobject_cast<HistoryElementView *>(ui->historyView->itemWidget(item));
Hope it helps.

Resources