How does one paint the entire row's background in a QStyledItemDelegate? - c++

I have a QTableView which I am setting a custom QStyledItemDelegate on.
In addition to the custom item painting, I want to style the row's background color for the selection/hovered states. The look I am going for is something like this KGet screenshot:
KGet's Row Background http://www.binaryelysium.com/images/kget_background.jpeg
Here is my code:
void MyDelegate::paint( QPainter* painter, const QStyleOptionViewItem& opt, const QModelIndex& index ) const
{
QBrush backBrush;
QColor foreColor;
bool hover = false;
if ( opt.state & QStyle::State_MouseOver )
{
backBrush = opt.palette.color( QPalette::Highlight ).light( 115 );
foreColor = opt.palette.color( QPalette::HighlightedText );
hover = true;
}
QStyleOptionViewItemV4 option(opt);
initStyleOption(&option, index);
painter->save();
const QStyle *style = option.widget ? option.widget->style() : QApplication::style();
const QWidget* widget = option.widget;
if( hover )
{
option.backgroundBrush = backBrush;
}
painter->save();
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, widget);
painter->restore();
switch( index.column() )
{
case 0: // we want default behavior
style->drawControl(QStyle::CE_ItemViewItem, &option, painter, widget);
break;
case 1:
// some custom drawText
break;
case 2:
// draw a QStyleOptionProgressBar
break;
}
painter->restore();
}
The result is that each individual cell receives the mousedover background only when the mouse is over it, and not the entire row. It is hard to describe so here is a screenshot:
The result of the above code http://www.binaryelysium.com/images/loader_bg.jpeg
In that picture the mouse was over the left most cell, hence the highlighted background.. but I want the background to be drawn over the entire row.
How can I achieve this?
Edit: With some more thought I've realized that the QStyle::State_MouseOver state is only being passed for actual cell which the mouse is over, and when the paint method is called for the other cells in the row QStyle::State_MouseOver is not set.
So the question becomes is there a QStyle::State_MouseOver_Row state (answer: no), so how do I go about achieving that?

You need to be telling the view to update its cells when the mouse is over a row, so I would suggest tracking that in your model. Then in the paint event, you can ask for that data from the model index using a custom data role.

void TrackDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem viewOption(option);
if (viewOption.state & QStyle::State_HasFocus)
viewOption.state = viewOption.state ^ QStyle::State_HasFocus;
QImage image(m_RowBackGroundImagePath);
QPixmap pixmap(m_RowBackGroundImagePath);
qDebug()<<"forward"<<pixmap.width()<<pixmap.height();
pixmap.scaled(option.rect.width(),option.rect.height());
qDebug()<<"back"<<pixmap.width()<<pixmap.height();
qDebug()<<option.rect.width()<<option.rect.height();
QBrush brush(pixmap);
painter->save();
painter->fillRect(option.rect, brush/*QColor(238, 233, 233, 255)*/);
painter->restore();
viewOption.rect = QRect(option.rect.x(), option.rect.y(), option.rect.width(), option.rect.height());
//viewOption.palette.setColor(QPalette::Text, QColor(Qt::red));
//viewOption.palette.setBrush ( QPalette::ButtonText, brush1);
QItemDelegate::paint(painter, viewOption,index);
int progress = index.model()->data(index,Qt::DisplayRole).toInt();
QStyleOptionProgressBar progressBarOption;
progressBarOption.rect = QRect(option.rect.x(), option.rect.y()+(SETHEIGHT - PROGRESSBARHEIGHT)/2, option.rect.width(), /*option.rect.height()*/PROGRESSBARHEIGHT);
//qDebug()<<progressBarOption.rect.x()<<progressBarOption.rect.y()<<progressBarOption.rect.height()<<progressBarOption.rect.width();
//qDebug()<<option.rect.x()<<option.rect.y()<<option.rect.height()<<option.rect.width();
progressBarOption.state |= QStyle::State_Enabled;
progressBarOption.direction = QApplication::layoutDirection();
progressBarOption.fontMetrics = QApplication::fontMetrics();
progressBarOption.minimum = 0;
progressBarOption.maximum = 100;
progressBarOption.textAlignment = Qt::AlignCenter;
progressBarOption.textVisible = true;
progressBarOption.progress = progress < 0 ? 0 : progress;
progressBarOption.text = QString().sprintf("%d%%", progressBarOption.progress);
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);
break;
}

Related

Adjusting the selection behaviour of QStandardItem with QStyledItemDelegate

I am using QStyledItemDelegate to style the items in my QTreeView.
The roots of my treeview are not decorated. It's just a simple tree with relation similar to the one below:
ColorBook1
Color1
Color2
ColorBook2
Color3
The parent and child are styled differently and selection on parent is disabled.
I want to customise the selection behaviour in the child nodes so that the selection rectangle around the child would cover the entire row and not the text child alone.
Current Behaviour:
Desired Behaviour:
Is there any way to extend the selection rectangle like this using QStyledItemDelegate? I tried adjusting the rect in QStyleOptionViewItem parameter of QStyledItemDelegate::paint. But that moved the child node text to the left. I want to keep the text node at the same place but only the selection rectangle has to be adjusted to the left. So just like drawing text and pixmaps in the paint method is there a way to draw the selection rectangle as well(, using the default selection rect color)?
The paint method of my StyledItemDelegate is as follows:
I am using the following code in the QStyledItemDelegate::paint method:
void paint( QPainter * inPainter, const QStyleOptionViewItem & inOption, const QModelIndex & inIndex ) const
{
if( inIndex.data( Qt::UserRole ) == ColorInfoType::kColorBook )
{
QFont font = inPainter->font();
font.setWeight( QFont::Bold );
font.setPointSize( 8 );
inPainter->setFont( font );
inPainter->drawText
(
inOption.rect.adjusted( 5,0,0,0 ),
inIndex.data( Qt::DisplayRole ).toString(),
QTextOption( Qt::AlignVCenter | Qt::AlignLeft )
);
}
else
{
//To Do: draw the selection rect after adjusting the size.
// Draw the Color Name text
QStyledItemDelegate::paint( inPainter, inOption, inIndex );
}
}
You can paint it yourself. Use option.palette.brush(QPalette::Highlight) to get the highlight color.
In this snippet I just paint the blank area manually. I also changed the color, but you don't have to do that.
void StyleDel::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if(option.state.testFlag(QStyle::State_Selected))
{
QStyleOptionViewItem newOption = option;
newOption.state = option.state & (~QStyle::State_HasFocus);
QBrush brush = option.palette.brush(QPalette::Highlight);
brush.setColor(QColor(150,0,0,100));
newOption.palette.setBrush(QPalette::Highlight, brush);
QRect s_rect = newOption.rect; //we use this rect to define the blank area
s_rect.setLeft(0); // starts from 0
s_rect.setRight(newOption.rect.left()); //ends where the default rect starts
painter->fillRect(s_rect, newOption.palette.brush(QPalette::Highlight));
QStyledItemDelegate::paint(painter, newOption, index);
return;
}
QStyledItemDelegate::paint(painter, option, index);
}

How can I set the background color of some special row in QTableView?

I read an older post however that is not work for me.
I would like to set the background color of every row whose 6th argument is true.
I tried to overwrite the Paint method in my subclass of QSqlRelationalDelegate but apparently it does not do anything.
MoviesDelegate::MoviesDelegate(QObject *parent)
: QSqlRelationalDelegate(parent)
{ }
void MoviesDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if( index.sibling( index.row(), 6 ).data().toBool() )
{
QStyleOptionViewItemV4 optionViewItem = option;
optionViewItem.backgroundBrush = QBrush( Qt::yellow );
drawDisplay( painter, optionViewItem,
optionViewItem.rect,index.data().toString() );
drawFocus( painter, optionViewItem, optionViewItem.rect);
}
else
QSqlRelationalDelegate::paint(painter, option, index);
}
How can I fix it?
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
// Grab cell value and cast it to boolean
bool boolValue = index.model()->data(index).toBool();
// Paint cell background depending on the bool value
if(boolValue)
painter->fillRect(option.rect, QColor(179, 229, 255));
else
painter->fillRect(option.rect, Qt::red);
// Paint text
QStyledItemDelegate::paint(painter, option, index);
}

Correct highlighting with qt custom delegates

I am making a table control that displays some additional text data apart from those in DisplayRole of its model. In all other respects text and cell display should be identical. What i am having trouble with is correct display of highlighted cell.
I am currently using the following code:
void MatchDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, option.palette.highlight());
painter->save();
QString str = qvariant_cast<QString>(index.data())+ "\n";
str += QString::number(qvariant_cast<float>(index.data(Qt::UserRole)));
if (option.state & QStyle::State_Selected)
painter->setBrush(option.palette.highlightedText());
else
painter->setBrush(qvariant_cast<QBrush>(index.data(Qt::ForegroundRole)));
painter->drawText(option.rect, qvariant_cast<int>(index.data(Qt::TextAlignmentRole)), str);
painter->restore();
}
However, the result looks like this:
Text color is wrong, there is no dashing line around the cell, and when the control loses focus, the cell remains blue instead of becoming light gray like drawn by default cells do.
How should painting code be changed to fix those issues?
Please try below code, It will work.
Set drawControl with take care to draw dashed line( Let Qt take care it internally ) when selected.
Fixed( Dashed line, Text color and multiline ) while selecting cell.
void MatchDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItemV4 opt = option;
initStyleOption(&opt, index);
const QWidget *widget = option.widget;
QString str = qvariant_cast<QString>(index.data())+ "\n";
str += QString::number(qvariant_cast<float>(index.data(Qt::UserRole)));
opt.text = "";
//option
QStyle *style = widget ? widget->style() : QApplication::style();
if (option.state & QStyle::State_Selected)
{
// Whitee pen while selection
painter->setPen(Qt::white);
painter->setBrush(option.palette.highlightedText());
// This call will take care to draw, dashed line while selecting
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);
}
else
{
painter->setPen(QPen(option.palette.foreground(), 0));
painter->setBrush(qvariant_cast<QBrush>(index.data(Qt::ForegroundRole)));
}
painter->drawText(option.rect, qvariant_cast<int>(index.data(Qt::TextAlignmentRole)), str);
}

Cannot draw checkbox in QStyledItemDelegate

I have a QStyledItemDelegate derived object for a QTableView derived view. I further delegate the painting and editor creation depending on the model index data type. For bools I wanted to represent the state via a checkbox - but the check box never appears.
Here is the base delegate paint function:
void Sy_QtPropertyDelegate::paint( QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index ) const
{
painter->save();
if ( index.column() == 0 ) {
...
} else {
QVariant var = index.data();
bool modified = index.data( Sy_QtPropertyModel::ModifiedRole ).toBool();
// If the data type is one of our delegates, then push the work onto
// that.
auto it = delegateMap_.find( var.type() );
if ( it != delegateMap_.end() ) {
( *it )->paint( painter, option, index );
} else if ( var.type() != QVariant::UserType ) {
...
} else {
...
}
}
painter->restore();
}
And the bool sub delegate paint function:
void Sy_boolPD::paint( QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index ) const
{
painter->save();
bool checked = index.data().toBool();
bool modified = index.data( Sy_QtPropertyModel::ModifiedRole ).toBool();
QStyle* style = Sy_application::style();
if ( modified ) {
QStyleOptionViewItemV4 bgOpt( option );
bgOpt.backgroundBrush = QBrush( Sy_QtPropertyDelegate::ModifiedColour );
style->drawControl( QStyle::CE_ItemViewItem, &bgOpt, painter );
}
QStyleOption butOpt( option );
butOpt.state = QStyle::State_Enabled;
butOpt.state |= checked ? QStyle::State_On : QStyle::State_Off;
style->drawControl( QStyle::CE_CheckBox, &butOpt, painter );
painter->restore();
}
If I force modified to be true, the background is colour of the table is appropriately changed, and couting butOpt's rect and state members show that they are correct - but no check box is shown! Setting QStyle::CE_CheckBox to any other type also causes nothing to render.
I've worked with Qt's MVC framework a lot, but I cannot see where I have gone wrong here.
All I had to do was look in the source code...
case CE_CheckBox:
if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
...
}
The QStyleOption I pass to the method is cast to the type specific for drawing the control, CE_CheckBox requires a QStyleOptionButton, and if the cast fails the drawing operation silently skips.

Adding button to QTableview

I have created one table by using QTableview and QAbstractTableModel .
In one of the cells, I want to add one help button in the right corner of that cell.
Is there any way to achieve this?
You will have to implement your own delegate for that.
In Qt, aside from the Data, the Model and the View, you have your Delegates. They provide input capabilities, and they are also responsible for rendering "special" items in the View, which is what you need.
Qt doc has a good coverage on those (keywords: Model/View programming), and you can also find some examples here and here.
Also (a little off-topic, but I think I should point this out), if you use an ordinary QTableWidget, you can insert anything into any cell with it's setCellWidget() function.
UPD
here is a slightly modified example from Qt docs (I suck with model/view stuff in Qt so don't beat me hard for this code). It will draw a button in each cell on the right, and catch the click events in cells to check if the click was on the "button", and react accordingly.
Probably this is not the best way to do it, but as I mentioned, I'm not too good with Qt's models and views.
To do things right and allow proper editing, you will need to also implement createEditor(), setEditorData() and setModelData() functions.
To draw your stuff in a specific cell instead of all cells, just add a condition into the paint() function (note that it gets the model index as an argument, so you can always know in what cell you are painting, and paint accordingly).
delegate.h:
class MyDelegate : public QItemDelegate
{
Q_OBJECT
public:
MyDelegate(QObject *parent = 0);
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index);
};
delegate.cpp:
#include <QtGui>
#include "delegate.h"
MyDelegate::MyDelegate(QObject *parent)
: QItemDelegate(parent)
{
}
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionButton button;
QRect r = option.rect;//getting the rect of the cell
int x,y,w,h;
x = r.left() + r.width() - 30;//the X coordinate
y = r.top();//the Y coordinate
w = 30;//button width
h = 30;//button height
button.rect = QRect(x,y,w,h);
button.text = "=^.^=";
button.state = QStyle::State_Enabled;
QApplication::style()->drawControl( QStyle::CE_PushButton, &button, painter);
}
bool MyDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
if( event->type() == QEvent::MouseButtonRelease )
{
QMouseEvent * e = (QMouseEvent *)event;
int clickX = e->x();
int clickY = e->y();
QRect r = option.rect;//getting the rect of the cell
int x,y,w,h;
x = r.left() + r.width() - 30;//the X coordinate
y = r.top();//the Y coordinate
w = 30;//button width
h = 30;//button height
if( clickX > x && clickX < x + w )
if( clickY > y && clickY < y + h )
{
QDialog * d = new QDialog();
d->setGeometry(0,0,100,100);
d->show();
}
}
return true;
}
main.cpp
#include "delegate.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QStandardItemModel model(4, 2);
QTableView tableView;
tableView.setModel(&model);
MyDelegate delegate;
tableView.setItemDelegate(&delegate);
tableView.horizontalHeader()->setStretchLastSection(true);
tableView.show();
return app.exec();
}
The result will look like this:
I've got a solution WITHOUT any complex re-invention of the whole paint-processes.
I have a TableView in which I have a button in every row.
Note, that, in my case the for loop runs through each row.
QSignalMapper *signalMapper = new QSignalMapper(this);
for( int i=0; i<rows.length(); i++ ) { //replace rows.length with your list or vector which consists of the data for your rows.
//do something with your data for normal cells...
auto item = model->index(i, COLUMN_FOR_WHATEVER_YOU_WANT);
model->setData(item, QVariant::fromValue(yourObject.getSpecificInformation()));
//make new button for this row
item = model->index(i, COLUMN_BUTTON);
QPushButton *cartButton = new QPushButton("Add");
ui->table_view->setIndexWidget(item, cartButton);
signalMapper->setMapping(cartButton, i);
connect(cartButton, SIGNAL(clicked(bool)), signalMapper, SLOT(map()));
}
connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(doSomething(int)));
Then you automatically get the index of the row in which the user clicked the button.
You just need to make your own slot:
private SLOTS:
void doSomething(int row);
If you have specific cells, it would work similar.
Note, that I did not care about memory leaks in this example, and I don't exactly know what would happen if you update your TableView... (It's working fine, but it might not delete the old button-pointers when new ones are created)
setIndexWidget worked for me.
Example:
QPushButton* helpButton = new QPushButton("Help");
tableView->setIndexWidget(model->index(position,COLUMN_NUMBER), helpButton);
If you just want to add a button and do something on click of it, adding a button using setIndexWidget() works fine. I believe we don't need the cumbersome paint method or implementing delegates.
// use only standard style
QApplication::style()->drawControl(QStyle::CE_PushButtonLabel, &button, painter);
For use user styles, need changed:
//use user style
QPushButton* real_button = ....; // button inherited user styles
real_button->style()->drawControl( QStyle::CE_PushButtonLabel, &button, painter, real_button);
I got the solution ..
Old paint method :
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionButton button;
QRect r = option.rect;//getting the rect of the cell
int x,y,w,h;
x = r.left() + r.width() - 30;//the X coordinate
y = r.top();//the Y coordinate
w = 30;//button width
h = 30;//button height
button.rect = QRect(x,y,w,h);
button.text = "=^.^=";
button.state = QStyle::State_Enabled;
QApplication::style()->drawControl( QStyle::CE_PushButton, &button, painter);
}
here is the updated paint() method .
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QItemDelegate::paint(painter, option, index);
if(index.row()==8)//since i have to make it display only at (8,0) position .
{
if(index.column()==0)
{
QStyleOptionButton button;
QRect r = option.rect;//getting the rect of the cell
int x,y,w,h;
x = r.left() + r.width() - 20;//the X coordinate
y = r.top();//the Y coordinate
w = 15;//button width(based on the requirement)
h = 15;//button height(based on the requirement)
button.icon= QIcon(QString::fromUtf8("Resources/HelpIcon.png"));
button.iconSize = QSize(20,20);
button.rect = QRect(x,y,w,h);
button.text = "";//no text . since if text will be given then it will push the icon to left side based on the coordinates .
button.state = QStyle::State_Enabled;
//QApplication::style()->drawControl( QStyle::CE_PushButton, &button, painter);
QApplication::style()->drawControl( QStyle::CE_PushButtonLabel, &button, painter);//To make the Button transparent .
}
}
}
When the view wants to draw a cell it calls the delegate’s paint() function with some information about how, what, and where to draw the contents of the cell. The default delegate just draws the Qt::DisplayRole text and selectionState.
If you replace the delegate then you completely replace the default behaviour: you can draw whatever you like. If you want the text then you need to arrange to draw it. You can do it yourself or, using standard C++ mechanisms, you can call the default drawing code first then draw over the top.
It works after adding QItemDelegate::paint(painter, option, index); at the beginning of my paint() method .