QTableView format individual row and columns - qt

Maybe what I'm about to ask is so basic that i missed it somewhere, but i google all kind of things and i have not been able to find the answer. I have the following table view, feed from a SQLite table:
body = new QSqlTableModel(parent,data->m_db);
body->setTable("C"+QString::number(markTime.toSecsSinceEpoch()));
body->select();
ui->bodyView->setModel(body);
ui->bodyView->sortByColumn(0,Qt::AscendingOrder);
ui->bodyView->setColumnWidth(0,30);
ui->bodyView->setColumnWidth(1,80);
for(int x=2;x<ui->columns->maximum()+2;x++) ui->bodyView->setColumnWidth(x,40);
ui->bodyView->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->bodyView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui->bodyView->setAlternatingRowColors(true);
ui->bodyView->show();
Further down in the program i add data to the table to be displayed. That works with no problems. My question is:
How i can access individual lines to format the row, i would like to change the size of the font, format the display to show number aligned and make it boldface but only for a specific row.
Thanks for the help.

Qt's proposed solution to customize the details of individual view items rendering (and editing) is via delegates. The level of customization depends on your use-case: you can do some minor adjustment like changing a font or you can draw something completely special, like what's shown in Star Delegate example.
There are basically two options to proceed with a custom delegate: either you subclass QStyledItemDelegate (or its base class QItemDelegate if you need to draw the items of Qt's datatypes somewhat specially) and change some particular details of interest to you leaving the rest to the base class or you subclass QAbstractItemDelegate to have full control over the view item's appearance and behavior.
A delegate can be set up for the view in either of three different ways:
for the whole view (i.e. for all model items) via setItemDelegate method
for a particular column via setItemDelegateForColumn method
for a particular row via setItemDelegateForRow method
For the sake of example, here's how you can specify a slightly larger font size for the item rendering:
class CustomDelegate: public QStyledItemDelegate
{
public:
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const Q_DECL_OVERRIDE;
};
void CustomDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (!index.isValid()) {
return;
}
QFont font = option.font;
font.setPointSize(font.pointSize() + 1);
QStyleOptionViewItem localOption(option);
localOption.font = font;
QStyledItemDelegate::paint(painter, localOption, index);
}

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

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.

QTreeView: resizeRowsToContents equivalent or how to word-wrap text in rows

I have a QTreeView with more than one column (like a table). Columns in the tree have a fixed size. I need to resize row heights and use multi-lined text like QTableView::resizeRowsToContents.
How can I do this?
I've tried using a custom QStyledItemDelegate with reimplemented sizeHint, but I don't know how to calculate multiline text block height with known width.
Using QStyledItemDelegate is the right approach. In your sizeHint function, you can use the style options text with the QFontMetrics class:
QSize MyItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override {
QSize baseSize = this->QStyledItemDelegate::sizeHint(option, index);
baseSize.setHeight(10000);//something very high, or the maximum height of your text block
QFontMetrics metrics(option.font);
QRect outRect = metrics.boundingRect(QRect(QPoint(0, 0), baseSize), Qt::AlignLeft, option.text);
baseSize.setHeight(outRect.height());
return baseSize;
}
Note: Right now I cannot test this, but it should work. You may have to tweak the call to metrics.boundingRect if the output does not fit your needs
EDIT:
It seems the sizeHint will be only called once to create the initial layout, but not after resizing the columns.
A final Idea could be to override the QAbstractItemModel::data function to return the desired size using Qt::SizeHintRole. You could either add it to your existing model or provide a proxy-model to do this:
QSize MyModel::data(const QModelIndex &index, int role) const override {
switch(role) {
//...
case Qt::SizeHintRole:
{
QSize baseSize(getFixedWidth(index.column()), baseSize.setHeight(10000));//something very high, or the maximum height of your text block
QFontMetrics metrics(this->data(index, Qt::FontRole).value<QFont>());
QRect outRect = metrics.boundingRect(QRect(QPoint(0, 0), baseSize), Qt::AlignLeft, this->data(index, Qt::DisplayRole)));
baseSize.setHeight(outRect.height());
return baseSize;
}
//...
}
}
Important: Everytime your view gets resized, you will have to emit the dataChanged signal for all those items. getFixedWidth is something you will have to implement to return the current width of the given column.

QListItem adds an offset to each item

I am trying to draw QWwidget in a QListView with a delegate. I can get the widget to render but it seems like there is a clip window that is moving over the widget in the left direction as I draw them and I can't figure why. It's almost like the QListView is adding a margin that grows with each item that is added, even if I am still drawing the widget at the right location.
The render is like this:
|-----|
->|-----|
--> |-----|
And here is the code I am using :
void CustomItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
MyWidget w("test", "test");
//At some point I want to load the widget with data from here
if(index.data().canConvert<CustomModelItem>())
{
w.resize(option.rect.size());
painter.save();
//some kind of bug with passing topLeft in. I found this solution somewhere
w.render(painter, painter->deviceTransform().map(option.rect.topLeft()));
painter.restore();
}
else
{
QStyledItemDelegate::paint(painter, option, index);
}
}
Can anyone explain this behavior?
UPDATE: I put some debug code in the render function for QWidgets. What I am observing is that the x coordinate of the systemclip is moving + 3 in every iteration of the loop in QListView's paint function. I have no idea where this coming from.

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