QTreeView cell selected highlight resize - qt

Is there any way to customize focus rect size for QTreeView item? I have reviewed source code of paint() event of QStyledItemDelegate, and there is query for textRect inside them, but i not found the way to resize focus rect, it only paint a part of cell, containing text, i need to focus rect fill the entire cell rect. Any help?
cell focus rect example

The default selection highlight depends on the current app style. On Windows it's partial, which is how other Windows apps behave. With Fusion style (default on Linux) the selection highlight already covers the full item rectangle. Not sure on Mac.
Anyway, it's easily controlled with a style option which is set in the item delegate. All we need to do is set a flag, and luckily the style option init function is virtual. This is the same flag which is set by default for some styles. Try this item delegate:
class HighlightDelegate : public QStyledItemDelegate
{
public:
using QStyledItemDelegate::QStyledItemDelegate;
protected:
void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override
{
QStyledItemDelegate::initStyleOption(option, index);
option->showDecorationSelected = true;
}
};

Related

Set different images for different checkboxes in a QTreeView

I subclassed a QTreeView and I have two columns where there are checkboxes. I would like to set two different images: one for the first column, and another one for the second column. I know I can change the image in the stylesheet with:
QTreeView::indicator:checked{
image: url(:/checked);
}
QTreeView::indicator:unchecked{
image: url(:/unchecked);
}
but it will change all the checkboxes in the tree view. Is there a way to do it with the stylesheets, or do I need to use a delegate?
Short answer: Stylesheets can't do that (as far as I know). They are a pretty immature feature in Qt, and there seems to be no development on them either.
What you can do:
Stylesheets
You cannot assign properties to a column or an item and you cannot access the columns by index.
But you can use some of the pseudo-states selectors like :first, :middle and :last:
QTreeView::indicator:first:checked{
background: red;
}
QTreeView::indicator:middle:checked{
background: blue;
}
QTreeView::indicator:unchecked{
background: lightgray;
}
I used colors instead of images for the sake of simplicity.
Note however, that these pseudo-states are actual currently visible states, so if the user is allowed to reorder columns, the style of the column might change. For example if the user drags one of the :middlecolumns and drops it at the end, the box will not be blue anymore.
DecorationRole
You can fake it using Qt::DecorationRole.
To do so, you have to receive the mousePressEvent either by subclassing QTreeView or by installing an event filter. You can then change the icon (via Qt::DecorationRole + emit dataChanged()) when the user clicks in the area of the icon.
This does not work with the keyboard of course.
Custom ItemDelegate
Subclass QStyledItemDelegate and override paint(), just as you suggested.
Custom Style
If you are creating a heavily styled application, you probably have to create a custom Style sooner or later. Stylesheets just don't support some features.
To do so, subclass QProxyStyle, override drawPrimitive and handle the drawing if QStyle::PE_IndicatorViewItemCheck is passed. You will also receive a QStyleOptionViewItem of which has some useful properties like checkState, features (contains QStyleOptionViewItem::HasCheckIndicator if there is a checkbox), and of course index so you can determine what kind of checkbox you want to draw.
Edit: Appendix
Example Using a Custom QStyledItemDelegate
void MyItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
QStyledItemDelegate::paint(painter, option, index);
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
const QWidget *widget = option.widget;
QStyle *style = widget ? widget->style() : QApplication::style();
QRect checkRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &opt, widget);
drawCheckBox(painter, checkRect, opt.checkState, index);
}
void MyItemDelegate::drawCheckBox(QPainter * painter, const QRect & checkRect, Qt::CheckState checkState, const QModelIndex & index) const
{
if (checkState == Qt::Checked)
{
switch (index.column())
{
case 0:
painter->fillRect(checkRect, Qt::red);
break;
default:
painter->fillRect(checkRect, Qt::blue);
}
}
else
{
painter->fillRect(checkRect, Qt::lightGray);
}
}
This example is quick and easy. Simply paint over the checkbox drawn by QStyledItemDelegate. Requires you to fill the whole box however, otherwise the original will be visible.
You can try to use QStyledItemDelegate to draw anything but the checkbox, and draw the checkbox afterwards, but that is a little harder and will leave you with some minor drawing artifacts if you don't want to spend too much time on it.
Example Using a Custom QProxyStyle
void MyStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption * opt, QPainter * p, const QWidget * w) const
{
if (pe == QStyle::PE_IndicatorViewItemCheck)
{
const QStyleOptionViewItem * o = static_cast<const QStyleOptionViewItem *>(opt);
drawCheckBox(p, opt->rect, o->checkState, o->index);
return;
}
QProxyStyle::drawPrimitive(pe, opt, p, w);
}
The drawCheckBox() function is the same as in the first example.
As you can see, this way is much simpler, cleaner and has none of the drawbacks. You can apply the style globally, or only for a single widget.

Qt: QListWidget separator line after particuler items?

This is related to Qt: QListWidget separator line between items?
But this above answer adds separator line after each items, I would like to know a way to add the separator line after particular items.
Create a QListWidgetItem representing the separator. Such item would need to have defined the setSizeHint(), so its height is small, and also the setFlags() should define Qt::NoItemFlags, so the item is not selectable, etc. Then, after adding the item to the QListWidget, place a QFrame, with its shape set to QFrame::HLine, as the item's widget (using QListWidget::setItemWidget()).
As for your additional question from the comment, which is:
I want to add some gap on each sides of this separator line/frame. How can I achieve this?
The only solution that comes to my mind right now is to embed the QFrame inside of another QWidget and put the QWidget as item's widget (remember that you need to add a layout manager to the QWidget in order to embed anything in it). Then set proper margins on the widget: QWidget::setContentsMargins(int left, int top, int right, int bottom)
I found another possibility and tested it this time :p
You could create a new class inheriting QStyledItemDelegate that look like this :
void MyStyledItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyledItemDelegate::paint(painter, option, index);
// I have decided to use Qt::UserRole + 1 to store my boolean
// but it could be any other role while it's value is bigger than Qt::UserRole
QVariant isSeparator = index.data(Qt::UserRole + 1);
if (isSeparator.isValid() && isSeparator.toBool())
{
QRect rct = option.rect;
rct.setY(rct.bottom() - 1);
painter->fillRect(rct, QColor::fromRgb(qRgb(0, 0, 0)));
}
}
And the for each QListWidgetItem you can do the following :
// Qt::UserRole + 1 => Must match the role set in the delegate
item->setData(Qt::UserRole + 1, true);
Install the custom in your QListWidget like this
listWidget->setItemDelegate(new MyStyledItemDelegate());
It will draw a black line under the text of the item if the Qt::UserRole + 1 is set to true.
You can try using the same trick with dynamic properties.
myListWidget->setStyleSheet( "QListWidget::item[separator="true"] { border-bottom: 1px solid black; }" );
And on the widget you want the line to be drawn :
myWidget->setProperty("separator", true);
However be carefull the documentation says :
Warning: If the value of the Qt property changes after the style sheet has been set, it might be necessary to force a style sheet recomputation. One way to achieve this is to unset the style sheet and set it again.

Styling QTabWidget

I have a QTabWidget with a background gradient and two problems.
How dow I remove the anoying outline around the active tab (see image)? I tried "outline: none" like with push buttons but it does not seem to have an effect.
How do I style disabled tabs? I tried :disabled and :!enabled but both do not work. // Edit: This works with :disabled but not with all properties. Seems like I tried the only not supported.
The qt documentation was no help. Google either. :-(
It seems that the focus rectangle is handled by the QStyle (not to be confused with style sheets) that is in use. You can write a QStyle subclass and apply that to your to your QTabWidget. The subclass should override the drawControl() method and do nothing if it is currently drawing the focus rectangle.
The subclass would look something like this:
NoFocusRectStyle.h
#ifndef NOFOCUSRECTSTYLE_H
#define NOFOCUSRECTSTYLE_H
#include <QWindowsVistaStyle> // or the QStyle subclass of your choice
class NoFocusRectStyle : public QWindowsVistaStyle
{
public:
NoFocusRectStyle();
protected:
void drawControl(ControlElement element, const QStyleOption *option,
QPainter *painter, const QWidget *widget = 0) const;
};
#endif // NOFOCUSRECTSTYLE_H
NoFocusRectStyle.cpp
#include "NoFocusStyle.h"
NoFocusRectStyle::NoFocusRectStyle()
{
}
void NoFocusRectStyle::drawControl(ControlElement element,
const QStyleOption *option, QPainter *painter,
const QWidget *widget) const
{
if(element == CE_FocusFrame)
return;
QWindowsVistaStyle::drawControl(element, option, painter, widget);
}
Somewhere in your form's intializer/constructor you would apply the custom style subclass to the tab widget:
ui->tabWidget->setStyle(new NoFocusRectStyle());
This should allow your style sheets to continue to work.
It would be nice if there was an easier way to do this but I couldn't find one :)
This thread is old but maybe this would help people.
If you don't need to use the focus, then you can just set it through your tab widget:
ui->tabWidget->setFocusPolicy(Qt::NoFocus);
Focus rectangle could be removed by adding snippet below to your style:
QWidget {
outline: 0;
}
It is not related directly to style of QTabWidget but works as you expect.

Elegant way of getting if element is focused in function QGraphicsItem::shape()

In a graphical qt application,
i can learn if my object that inherits from QGraphicsItem is focused in paint method:
Qt Code:
void MyQGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *)
{
if (option->state & QStyle::State_HasFocus) {
//if focus some shape
} else {
//if no focus another shape
}
}
but i must click it and the shape must change whether it is focused or not.
how can i get if focused information in
Qt Code:
QPainterPath QGraphicsItem::shape() const
method in an appropriate way?
I think to declare a global variable but i do not like this idea.
thanks
Use QGraphicsItem::hasFocus() :
Returns true if this item is active, and it or its focus proxy has
keyboard input focus; otherwise, returns false.
Incidentally, if you want the shape to change when you focus the item, you will need to override focusInEvent() and focusOutEvent() and remember to call prepareGeometryChange() before the shape changes.

Setting QStyleOptionComboBox.currentText does not have any effect on the drawn widget

I want to draw a QComboBox inside a delegate, which works fine except that I can't figure out how to draw the inital text that's visible inside the combo box.
The documentation says that QStyleOptionComboBox.currentText holds: "the text for the current item of the combo box." but setting the variable does not have any effect.
This is my code:
void MyDelegate::paint(QPainter *painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
QStyleOptionComboBox comboBoxOption;
comboBoxOption.rect = option.rect;
comboBoxOption.state = option.state;
comboBoxOption.state |= QStyle::State_Enabled;
comboBoxOption.editable = false;
comboBoxOption.currentText = "CCC"; // This doesn't show up.
QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &comboBoxOption, painter);
}
Looking at qwindowsxpstyle.cpp I don't see where the text of a "real" combo box is drawn since currentText is not used inside the drawComplexControl method. The only place where it seems to be used for Windows XP style is in qcommonstyle.cpp (Line 2107, Qt 4.7.2), but I can't figure out how those two classes play together.
It seems you also need to force Qt to draw the combo box label, in addition to the complex control. Try this:
QApplication::style()->drawControl(QStyle::CE_ComboBoxLabel, &comboBoxOption, painter)
If I read the documentation, and source, correctly that might force QStyle to draw a combo box label. It seems odd that you'd have to specify both...but I don't know a whole lot about how Qt styles draw themselves, to be honest.

Resources