How to set width of QTableView columns by model? - qt

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.

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.

QTableView format individual row and columns

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

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.

Change default selection color for QTableView

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

Qt: QTableWidget/QTableView alternating row colors in full viewport

I have a QTableView containing data rows from a database. However, setting setAlternatingRowColors(true) only alternates row colors that has data - the rest of the table is just white, which is not the behaviour you'd expect (look in the bookmark list of any browser, for example - empty rows has alternating colors).
Does anyone know a workarund or an alternative to the table views supplied by Qt? I've fiddled with stylesheets and item delegates, same result.
Why don't you use Qt QSS for this? It working just fine. Have a look here : http://www.qtcentre.org/threads/42211-QTableWidget-alternating-background-color?p=263046#post263046
myTable->setAlternatingRowColors(true);
myTable->setStyleSheet("alternate-background-color: yellow;background-color: red;");
You could reimplement data() method of your model like this:
QVariant MyModel::data(const QModelIndex& index, int role) const
{
if(role == Qt::BackgroundColorRole)
return color;
...
}
It should be possible to do the same with a delegate using setModelData().

Resources