Change default selection color for QTableView - qt

I am using a QTableView with QAbstractTableModel.
I have subclassed QAbstractTableModel and in my model class for Qt::BackgroundRole and Qt::ForegroundRole I have returned some color depending on some condition.
And I have subclassed QTableView class.
When I select any row/cell on the table, the row/cell is highlighted in the default color of the tabelVeiw, it does not show the color returned from my model class.
How can I change this behavior? How can I avoid/ignore this default coloring of QTableView and have only the colors returned by my model class?

You have to use a custom delegate.
Subclass the QStyledItemDelegate and implement it's paint() method like this:
void MyDelegate::paint(QPainter* painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
QStyleOptionViewItem itemOption(option);
initStyleOption(&itemOption, index);
if ((itemOption.state & QStyle::State_Selected) &&
(itemOption.state & QStyle::State_Active))
itemOption.palette.setColor(QPalette::Highlight, Qt::red); // set your color here
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &itemOption, painter, nullptr);
}
If you want to get yout selecion color from the model, I suggest to define a special custom role for that purpose:
enum MyRoles
{
HighlightColorRole = Qt::UserRole
};
Your model will use this role to return your custom highlight (selection) color through the QAbstractItemModel::data() method.
In your delegate you can obtain that color like this:
QColor color = index.data(HighlightColorRole).value<QColor>();

If you want to change the the color of the QTableview when a cell is selected you can do something like this :
QPalette palette = tableview->palette();
palette.setColor(QPalette::Highlight, QColor(255,255,255,100)); //set your own colors and transparency level in QColor
tableview->setPalette(palette);

Related

QTreeView cell selected highlight resize

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;
}
};

Overriding Qt Stylesheet in QStyledItemDelegate

I have a QTableView that is using a custom QStyledItemDelegate to render each row of the table. A stylesheet is setting the background color of the selected row on the TableView by doing the following:
QTableView::item::selected {
background-color: $highlight_color; //this parses to #FFFFFF
}
This works as it should, but under certain conditions I would like to adjust the alpha of the selected row's background by making it semi-transparent. I am doing this by overriding the paint() function of QStyledItemDelegate.
void CustomDelegate::paint(QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const {
QColor color(option.palette.color(QPalette::Highlight));
if(isOpaque)
color.setAlphaF(0.5);
painter->fillRect(options.rect, QBrush(background_color));
}
This does change the color, but the color of QPalette::Highlight is not correct. Furthermore, I have tried all of the different palette color roles, and none of them reflect the background color set in the stylesheet. If I do the following, however, it works just fine (minus the opacity).
void CustomDelegate::paint(QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const {
QStyledItemDelegate::paint(painter, options, index);
}
I have read that combining Qt Stylesheets and QPalette is not a good idea, but my only alternative is to have another variable in the stylesheet that reflects the opacity, and I would like to avoid that at all costs.

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.

How to set QStyleOptionButton like a checkbox with custom icons

I have a custom Delegate class which inherits from QStyledItemDelegate. In its paint() event, I would like to add QStyleOptionButton which should be checkable. Is it possible?
For example, it denotes visibility property with an icon of eye; and when the button is pressed, the eye icon turns into closed-eye icon.
Inside the paint() method, this is my current code to create the button:
QStyleOptionButton buttonVis;
buttonVis.rect = getButtonVisibilityRect();
buttonVis.iconSize = QSize(sizeX, sizeY);
buttonVis.icon = icon;
buttonVis.state = QStyle::State_Enabled;
buttonVis.features = QStyleOptionButton::None;
QApplication::style()->drawControl(QStyle::CE_PushButton, &buttonVis, painter);
The icon that I load to the buttonVis is created by:
QIcon icon;
icon.addPixmap(QPixmap(":/control-visibility.svg"), QIcon::Normal, QIcon::On);
icon.addPixmap(QPixmap(":/control-visibilityNo.svg"), QIcon::Normal, QIcon::Off);
For the moment when I run my program, the button has an icon with closed eye. Is there a command to control which icon is displayed? If my initial layout is not possible to implement, what is the right direction to go?
EDIT: I found out how to select which icon to use in order to simulate the checkbox look. Instead if line buttonVis.state = QStyle::State_Enabled; there should be:
if (/* current item checkbox checked? */)
buttonVis.state = QStyle::State_Enabled | QStyle::State_On;
else
buttonVis.state = QStyle::State_Enabled | QStyle::State_Off;
The problem now is to figure out what is that condition or how to set it up from the editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index). The problem is that I cannot really change option by myself since it is constant reference. Any ideas how to do it, or how to get around it?
You can try something like this:
QStyleOptionButton option;
option.initFrom(this);
...
QStylePainter p(this);
if (option.state.testFlag(QStyle::State_Sunken)) {
p.drawPixmap(this->rect(), QPixmap(":/control-visibility"));
} else {
p.drawPixmap(this->rect(), QPixmap(":/control-visibilityNo"));
}
Be careful with SVG file, try first with PNG.
QStyle::State_Sunken 0x00000004 Used to indicate if the widget is
sunken or pressed.
Finally, I figured out how to set up certain flag and then test for it and see if the button changed an icon.
So, for the icon change, as I mentioned on the edit of my question, I have to use something like:
buttonVis.state |= QStyle::State_Enabled;
buttonVis.state |= isChanged? QStyle::State_Off : QStyle::State_On;
So it comes down on how to set up isChanged flag. And it can be done within editorEvent() method of the delegate:
bool Delegate::editorEvent(QEvent *event, QAbstractItemModel *model, const StyleOptionViewItem &option, const QModelIndex &index)
{
if (/* event is release and it is over the button area*/)
{
bool value = index.data(Qt::UserRole).toBool();
// this is how we setup the condition flag
model->setData(index, !value, Qt::UserRole);
return true;
}
}
Now to use the setup flag, we do it in the paint() event right before we set up buttonVis.state:
bool isChanged = index.data(Qt::UserRole).toBool();
By adding these steps my QStyleOptionButton is now behaving like a checkbox, but it changes icons as states.

How to set width of QTableView columns by model?

I'm using QTableView with a subclass of QAbstractTableModel as its model. By implementing data() and headerdata() in the subclassed model, it is feasible to control many properties of the table like data, header values, font, and so on.
In my case, I want the model to set the width of each table column. How can this be done?
There are two ways:
In your model's data method you can return the role SizeHintRole.
A better way would be to subclass QItemDelegate and override the method.
See here (qitemdelegate.html#sizeHint)
Example -
QSize ItemDelegate::sizeHint( const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
QSize sz;
if(index.column()==2)
{
return QSize(128, option.rect().height());
}
return QSize();
}
Here I am setting the width of column 2 to 128 pixels and I am filling in the height from the item rectangle held in QStyleOptionViewItem.

Resources