Making a clicked QListWidgetItem borderless - qt

I have a QListWidget subclass widget deal with mouse events, as the picture below:
Its items will show text when cursor hover on the icon, and the hover background is transparent. Selection will be automatically cleared after click, but I'm still getting a dotted line rect around clicked item when hovering:
I set the stylesheet as below:
self.setStyleSheet("ListLabelWidget {background: transparent;border: borderless;}\
ListLabelWidget::item:hover {background-color: transparent;}")
If I set the hover border to borderless or 0px with:
self.setStyleSheet("ListLabelWidget {background: transparent;border: borderless;}\
ListLabelWidget::item:hover {background-color: transparent;border: borderless;}")
The rect still show around the textfield:
What should I do to completely hide it?

I cann't find any solution with qss but it can be implemented with QStyledItemDelegate. Here is an example for C++.
First create your own Widget for items. In my example I created in designer simple widget with two QLabel: for icon and for text. And add simple method for data setting
void ItemWidget::SetData(const QIcon &icon, const QString &text)
{
ui->iconLabel->setPixmap(icon.pixmap(QSize(16,16)));
ui->textLabel->setText(text);
}
Then subclass QStyledItemDelegate and reimplement paint method
class ListWidgetDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit ListWidgetDelegate(QObject *parent = 0);
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
ItemWidget widget;
widget.SetData(index.data(Qt::DecorationRole).value<QIcon>(),
index.data(Qt::DisplayRole).toString());
widget.resize(option.rect.width(), option.rect.height());
painter->drawPixmap(option.rect, QPixmap::grabWidget(&widget));
}
};
Also you can reimplement sizeHint method if you need bigger items.
And finally set items and delegate for QListWidget.
QIcon icon(":/crown.ico");
QListWidgetItem *item1 = new QListWidgetItem(icon, "Text1", ui->listWidget);
QListWidgetItem *item2 = new QListWidgetItem(icon, "Text2", ui->listWidget);
QListWidgetItem *item3 = new QListWidgetItem(icon, "Text3", ui->listWidget);
QListWidgetItem *item4 = new QListWidgetItem("No Icon", ui->listWidget);
ListWidgetDelegate *delegate = new ListWidgetDelegate(ui->listWidget);
ui->listWidget->setItemDelegate(delegate);
In this implementation you don't need qss (StyleSheet) for QListWidget::item

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.

QToolButton with icon + text: How to center both?

I am using multiple QToolButtons in a custom QGridLayout widget. The buttons are set to display icon + text based on an assigned default QAction. The only issue is that the content (icon + text) is always left-aligned.
The content (icon + text, marked as a red box in the screenshot), should be center in the button (indicated by the blue box).
For most cases this is just fine, given that Qt automatically tries to render that button with the minimal size. However I am stretching the button to fit nicely into my QGridLayout.
QToolButton* pButton = new QToolButton(0);
pButton->addDefaultAction(pAction);
pButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
pButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QGridLayout *pActionAreaLayout = new QGridLayout;
pActionAreaLayout->addWidget(pSomeOtherWidget, 0, 0, 1, 2);
pActionAreaLayout->addWidget(pButton , 1, 0, 1, 1);
Is there a way to force the content to be centered in the button?
PS: I found the following comment in another forum, which however seems quite invasive and is not really clear to me yet:
You can try doing the horizontal alignment using a stylesheet, but you probably have to implement a QStyle proxy and reimplement drawControl() for QStyle::CE_ToolButtonLabel
Or derive from QToolButton, overwrite paintEvent() and call the style for everything other than the label.
As I suggest in answer to you another question. https://stackoverflow.com/a/28630318/1917249 Do not use QToolButton, just use QPushButton, and add popup menu if needed.
Then you wont have different sizes of QToolButton and QPushButton widgets. And you will have centered icon and text.
Popupmenu can be easily added to QPushButton ( only small arrow wont be shown )
QPushButton *pushButton = new QPushButton(toolAction->icon(), "PushButton", window);
// window - widget where button is placed ( to get correct QMenu position )
QObject::connect(pushButton, &QPushButton::released, [window, pushButton, action](){
QMenu menu;
menu.addAction(action);
QPoint pos = window->mapToGlobal(pushButton3->pos());
pos += QPoint(0, pushButton->height());
menu.exec(pos);
});
Or you can subclass QPushButton and add Popup menu handling there. Much better then try to center text with icon in QToolButton or have in same size of QPushButton and QToolButton
For complex example please see my answer: https://stackoverflow.com/a/28630318/1917249
The following class does the job for me:
class CenteredToolButtonStyle : public QProxyStyle
{
Q_OBJECT
public:
CenteredToolButtonStyle(QToolButton* b, const QSize& sIcon);
virtual void drawItemPixmap(QPainter *painter, const QRect &rect, int, const QPixmap &pixmap) const
override { m_pic = pixmap; m_ny = rect.y(); Draw(painter); }
virtual void drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal, bool enabled,
const QString &text, QPalette::ColorRole textRole = QPalette::NoRole) const override;
void Draw(QPainter *painter) const;
const QToolButton* B;
const QSize SICON;
mutable QString m_s;
mutable QPixmap m_pic;
mutable QRect m_r;
mutable int m_nf, m_ny;
mutable bool m_bEnabled;
mutable QPalette m_pal;
mutable QPalette::ColorRole m_textRole;
};
CenteredToolButtonStyle::CenteredToolButtonStyle(QToolButton* b, const QSize& sIcon)
: QProxyStyle(), B(b), SICON(sIcon), m_nf(0), m_bEnabled(true), m_ny(0)
{
b->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
setParent(b);
}
void CenteredToolButtonStyle::drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal,
bool enabled, const QString &text, QPalette::ColorRole textRole/* = QPalette::NoRole*/) const
{
m_s = text;
m_r = rect;
m_nf = flags | Qt::AlignCenter;
m_bEnabled = enabled;
m_pal = pal;
m_textRole = textRole;
Draw(painter);
}
void CenteredToolButtonStyle::Draw(QPainter *painter) const
{
if (m_ny) {
if (m_r.y() != m_ny) return;
auto r = m_r;
r.adjust(-SICON.width() - 8, m_ny = 0, -itemTextRect(B->fontMetrics(), m_r, m_nf, m_bEnabled, m_s).width(), 0);
QProxyStyle::drawItemPixmap(painter, r, Qt::AlignCenter, m_pic);
}
QProxyStyle::drawItemText(painter, m_r, m_nf, m_pal, m_bEnabled, m_s, m_textRole);
}
Sample use:
foreach(auto b, ui.mainToolBar->findChildren<QToolButton*>())
b->setStyle(new CenteredToolButtonStyle(b, ui.mainToolBar->iconSize()));

Set border to an image in QListView in 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);

Drawing an overlay on top of an application's window

I want to be able to paint on top of my application's window so that I can annotate all the widgets with some extra diagnostic information, similar to the CSS developer tools in Firefox (eg add widget classes, styles, highlight borders etc).
I can walk the widget tree and extract the relevant information, but the question is how can I overlay all the application windows with this information?
One way would be to override my QMainWindow's paint event, but this has to be done for all top level windows. Is there an alternative method where you can paint on the QDesktopWidget for instance? Or any hooks into each QWidget's paint method? Anything that involves subclassing QWidget itself won't work with the standard widgets.
This follows on from my previous question:
Are there any useful tools for diagnosing Qt layout and spacing problems?
cheers
Mandrill
EDIT:
Thanks to Dmitry I've now got a really simple method that is easily extensible:
class DiagnosticStyle : public QWindowsVistaStyle
{
Q_OBJECT
public:
typedef QWindowsVistaStyle BaseStyle;
void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const;
};
void DiagnosticStyle::drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const
{
BaseStyle::drawControl(element, option, painter, widget);
if (widget && painter) {
// draw a border around the widget
painter->setPen(QColor("red"));
painter->drawRect(widget->rect());
// show the classname of the widget
QBrush translucentBrush(QColor(255,246,240, 100));
painter->fillRect(widget->rect(), translucentBrush);
painter->setPen(QColor("darkblue"));
painter->drawText(widget->rect(), Qt::AlignLeft | Qt::AlignVCenter, widget->metaObject()->className());
}
}
qApp->setStyle(new DiagnosticStyle());
You can create own style class based on QMotifStyle or other ... and paint on any widget/control related to him information.
void MyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option,QPainter *painter, const QWidget *widget) const
{
QStyle::State flags = option->state;
QRect rect = option->rect;
QPalette pal = option->palette;
QBrush brush;
switch (element)
{
case PE_FrameTabWidget:
{
painter->save();
// for example: draw anything on TabWidget
painter->drawPixmap(rect,centerPm,centerPm.rect());
painter->restore();
}
break;
default:
QMotifStyle::drawPrimitive(element, option, painter, widget);
break;
}
}
Somewhere in Qt5 the styles (GTK, Windows, etc) were made internal. Now you need to use QCommonStyle.
If anyone's wondering how to do this with Qt5+. Here's a self-contained version of #the_mandrill's code above.
class DiagnosticStyle : public QCommonStyle
{
Q_OBJECT
public:
typedef QStyle BaseStyle;
void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const
{
QCommonStyle::drawControl(element, option, painter, widget);
if (widget && painter) {
// draw a border around the widget
painter->setPen(QColor("red"));
painter->drawRect(widget->rect());
// show the classname of the widget
QBrush translucentBrush(QColor(255,246,240, 100));
painter->fillRect(widget->rect(), translucentBrush);
painter->setPen(QColor("darkblue"));
painter->drawText(widget->rect(), Qt::AlignLeft | Qt::AlignVCenter, widget->metaObject()->className());
}
};
};
Then, in your main window constructor call
qApp->setStyle(new DiagnosticStyle());

Resources