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.
Related
I created a tablewidget like this:
I want to edit cell(0) value, (double click), but the edit box was too big and it covers cell(1):
How do I avoid the edit box covering the cell after it?
You should make your own children QStyledItemDelegate and redefine QStyledItemDelegate::createEditor method.
Something like that:
QWidget * MyStyledItemDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
QWidget * editor = QStyledItemDelegate::createEditor(parent, option, index);
editor->setWidth( 20 ); // Handle editor here.
return editor;
}
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.
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);
}
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.
Is it possible to use clipping in an widgets painEvent, if the widget is using stylesheets?
The background and reason for my question is that I want to make the widget animating when it appears and disappears. (Something like a resizing circle or square, that gets bigger starting as a small area from the center).
My first (and only) thought on how to solve this, was to use the clipping of a QPainter, so that only the required area is drawn.
If I make the Background of the widget transparent and use the primitive drawing functions from QPainter it works fine. But how can I solve this, if the widget has a stylesheet applied? Is it even possible?
The used Qt version is Qt 4.8.6
My questions are:
Is it possible to achieve what I want with the mentioned strategy?
Is it possible in any way to clip all the children, too?
Is my strategy appropriate or is it a bad Idea to solve it that way?
Are there any other ideas, best practices, Qt Classes, ... that can give me what I want?
Additional Information
I haven't much code to show, because I stuck with this clipping things. But here is something to get an idea of what I have tried:
This works.
/* Shows a small red circle inside the widget as expected */
void MyAnimatingWidget::paintEvent(QPaintEvent *ev) {
QPainter painter(this);
QRect rect = this->geometry()
QStyleOption opt;
painter.setClipRegion(QRegion(rect.width()/2,
rect.height()/2,
150, 150,
QRegion::Ellipse));
painter.setPen(QColor(255, 0, 0));
painter.setBrush(QColor(255, 0, 0));
painter.setOpacity(1);
painter.drawRect(rect);
}
But the following doesn't change anything:
/* This shows the widget as usual */
void MyAnimatingWidget::paintEvent(QPaintEvent *ev) {
QPainter painter(this);
QRect rect = this->geometry();
QStyleOption opt;
painter.setClipRegion(QRegion(rect.width()/2,
rect.height()/2,
150, 150,
QRegion::Ellipse));
painter.setRenderHint(QPainter::Antialiasing);
painter.setOpacity(1);
opt.init(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
}
Moreover I have noticed, that the stylesheet is also drawn, even if I remove the style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); line at all.
The stylesheet you apply to your widget overrides the OS-specific style(s) widgets are equipped with by default. This can even cause problems, if you want to have a, say, Windows look, but still want to use a stylesheet. Anyway, you can check what each style does in the Qt source directory: src/gui/styles. For style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);, the code reads:
case PE_Widget:
if (w && !rule.hasDrawable()) {
QWidget *container = containerWidget(w);
if (styleSheetCaches->autoFillDisabledWidgets.contains(container)
&& (container == w || !renderRule(container, opt).hasBackground())) {
//we do not have a background, but we disabled the autofillbackground anyway. so fill the background now.
// (this may happen if we have rules like :focus)
p->fillRect(opt->rect, opt->palette.brush(w->backgroundRole()));
}
break;
}
As you can see clipping is not meddled with in any way, so your idea of setting a clip region should work. Now for the painting mystery. The painting of the background happens in void QWidgetPrivate::paintBackground(QPainter *painter, const QRegion &rgn, int flags) const, which is called from void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QPoint &offset, int flags, QPainter *sharedPainter, QWidgetBackingStore *backingStore). You can find the code in: /src/gui/kernel/qwidget.cpp. The relevant code reads:
if (q->testAttribute(Qt::WA_StyledBackground)) {
painter->setClipRegion(rgn);
QStyleOption opt;
opt.initFrom(q);
q->style()->drawPrimitive(QStyle::PE_Widget, &opt, painter, q);
}
Maybe turning the attribute off would help? The basic lesson you should draw from my answer is to get accustomed to source diving. The idea behind Qt is nice (instantiating controls, without bothering about implementation details), but it rarely works in practice, i.e. you often need to source dive.
To clip widget's children to arbitrary clip regions, you can capture them into a pixmap, example:
QPixmap pixmap(widget->size());
widget->render(&pixmap);
And then draw the pixmap manually. You might also be able to prevent them repainting automatically (via setUpdatesEnabled() or by hiding them) and then calling their render in you paintEvent handler manually.