PDA

View Full Version : Repainting QTableWidget



Binary91
14th September 2015, 12:22
Hi,

I've got a QTableWidget with a custom item delegate and I'd like to repaint it when the user selects an item in a QComboBox.
I tried both, QTableWidget::update() and, directly, QTableWidget::repaint(), but it didn't work. I think, the problem is that QTableWidget doesn't have focus but QComboBox does after selecting an item. If I replace QTableWidget::update() with QTableWidget::setFocus(), it works great and it does the repainting automatically.

Hence, I could do it with QTableWidget::setFocus(), but I think it is not the usual way and I'd like to do it the smart way... Does anyone know how I can update the QTableWidget without setting the focus to it?

anda_skoa
14th September 2015, 12:43
Is that combobox a separate widget or do you use it as an editor in the delegate?

Cheers,
_

Binary91
14th September 2015, 13:04
It's a seperate widget...

anda_skoa
14th September 2015, 13:07
So you have a slot connected to one of the combobox's signals?
And in that slot you update data in the tableview but it does not show that new data?

Cheers,
_

Binary91
14th September 2015, 13:18
Exactly that's it :D
I mean, "updating the data" is what I'd like to do. I update the strings that were used in the reimplemented QItemDelegate::paint(..) function, but then I need a full update, hence, the repaint must follow

Added after 10 minutes:

That is my code (can have some errors, don't care):


classWindow::classWindow(QWidget *parent) : QWidget(parent)
{
// ...
QComboBox *combobox = new QComboBox;
connect(this->combobox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotIndexChanged(int)));
QTableWidget *table = new QTableWidget;
myItemDelegate *delegate = new myItemDelegate(this);
table->setItemDelegate(delegate);
// ...
}

void classWindow::slotIndexChanged(int index)
{
switch(index)
{
case 1:
this->stringForDelegate = "lalala";
case 2:
this->stringForDelegate = "lololo";
// ...
}
this->table->update(); // <--- doesn't work
this->table->repaint(); // <--- doesn't work
this->table->setFocus(); // <--- does work, but is not the "smart" way I guess
return;
}

void myItemDelegate::paint(QPainter *painter, QStyleOptionViewItem *option, QModelIndex *index)
{
painter->drawText(option.rect, this->stringForDelegate);
return;
}

anda_skoa
14th September 2015, 13:49
Forget the delegate, it is not involved other than in painting.

Consider the delegate an implementation detail of the QTableWidget.

In slotIndexChanged() update the data in the QTableWidget.
It will automatically update all cells involved in the data change.

Cheers,
_

Binary91
14th September 2015, 13:59
In slotIndexChanged() update the data in the QTableWidget.But how should I do that? I don't store the data into the table because (as you may remember from the last thread) QPainter is the only way to position the complex orientated strings into the table cells...

anda_skoa
14th September 2015, 14:35
But how should I do that?

I believe QTableWidget gets its data from QTableWidgetItems.



I don't store the data into the table because (as you may remember from the last thread) QPainter is the only way to position the complex orientated strings into the table cells...
The delegate always uses QPainter for drawing cells.
A custom delegate can overwrite the paint() method and draw the cell data in any way it likes.

The table widget is still the place the data comes from.

It holds a model that provides the data interface for its base class, QTableView, which then uses the set delegate to display the data.
The application's interface to the data is through QTableWidgetItems.

Cheers,
_

Binary91
14th September 2015, 14:46
Sorry, I still didn't get it I think...
I thought reimplementing QItemDelegate::paint(...) and using painter->drawText(...) is the only way to position complex data into cells, isn't it??

If yes, then what do you mean with using QTableWidgetItems for storing/getting the data? I know only one function to store data with items, that is "setText(..)". But I don't believe I can realize complex positioning with this function...

Can you give an example how you would store the data?

anda_skoa
14th September 2015, 16:46
I thought reimplementing QItemDelegate::paint(...) and using painter->drawText(...) is the only way to position complex data into cells, isn't it??

Yes. We covered that in the other thread, I thought that this problem was solved?



If yes, then what do you mean with using QTableWidgetItems for storing/getting the data?

Well, the table has to get data somehow.
An empty table will paint nothing but empty cells, no matter which delegate you use.

Like when you print an empty page, you will get an empty page no matter if you are using an ink printer or a laser printer.



I know only one function to store data with items, that is "setText(..)".

Yes, that is the most convenient way if the cell has a single text value.



But I don't believe I can realize complex positioning with this function...

Why would you need that?
Your delegate handles the positioning.



Can you give an example how you would store the data?



QTableWidgetItem *item = new QTableWidgetItem();
item->setText("foo");
tableWidget->setItem(row, column, item);


Cheers,
_

Binary91
15th September 2015, 00:00
Yes. We covered that in the other thread, I thought that this problem was solved?
Yes, it was solved till now. I can paint text in complex position now, but I can do this only in the QITEMDELEGATE::PAINT function, because I need the painter and the rect of the corresponding cells.


Well, the table has to get data somehow.
An empty table will paint nothing but empty cells, no matter which delegate you use.
I don't think so, or I don't know it better, but I never store any data into the table. Why should I do? I only know one way for it with setText(QString), but that is not possible for me because it only allows storing of ONE text string. If you could tell me how to store the data I'd like to position (lots of text strings in different positions of each cell, pixmaps and icons) with a QTableWidgetItem, then everything would be fine...


Why would you need that?
Your delegate handles the positioning.But how?? From where does it get the data to handle with?? In my items, I can hold one text string, not more...

Please, I still don't know how to store complex data into a table like you suggested. I mean, I would need a function to store many strings and give them different identifiers, so the delegate can differ between them and so on... How is this realizable?

The way I realize it is creating a class "TableCell" that holds the texts and its rects. After changing the QComboBox index, the list of TableCell instances will be updated. Then, finally, the QTableWidget must be updated (that was the problem of the very beginning of this thread). While updating, delegate gets TableCell members and paints it on the table.
As you can see, I never "STORE" any data into the table, instead, I only let the data paint over the table...

So if there is any better solution, please show me. Tell me how you would "store" such complex TableCell instances into the table and how would you then tell the delegate to handle them?

anda_skoa
15th September 2015, 09:13
I can paint text in complex position now, but I can do this only in the QITEMDELEGATE::PAINT function, because I need the painter and the rect of the corresponding cells.

Yes, that is what delegates are there for.



I don't think so, or I don't know it better, but I never store any data into the table. Why should I do? I only know one way for it with setText(QString), but that is not possible for me because it only allows storing of ONE text string. If you could tell me how to store the data I'd like to position (lots of text strings in different positions of each cell, pixmaps and icons) with a QTableWidgetItem, then everything would be fine...

setData()
Methods like setText(), setBackground(), etc are just convenience wrappers around it.

You can even define your own roles and store complex objects in it as long as their types have been made known to the Qt type system with Q_DECLARE_METATYPE (see the QVariant documentation)



But how?? From where does it get the data to handle with?? In my items, I can hold one text string, not more...


Please, I still don't know how to store complex data into a table like you suggested. I mean, I would need a function to store many strings and give them different identifiers, so the delegate can differ between them and so on... How is this realizable?



The way I realize it is creating a class "TableCell" that holds the texts and its rects. After changing the QComboBox index, the list of TableCell instances will be updated. Then, finally, the QTableWidget must be updated (that was the problem of the very beginning of this thread). While updating, delegate gets TableCell members and paints it on the table.

Yes, that is also an option. In which case it doesn't make sense anymore to use the QTableWidget convenience wrapper, but using a QTableView with a custom model that operates on the cell data.

Cheers,
_

Binary91
15th September 2015, 09:58
Ok, If I'm right, I have two possible ways to realize my purposes:

1. I do it like I do it till now, without storing any data into the table but only drawing the data directly with paint(..) function.

2. I do it like you mentioned it with QTableWidgetItem::setData(role, data).

It works for me with option 1, so I need to know if your second option with "real storing" has any benefits? If no, I will not do much unneccessary work.
If yes, then I would have to define my own data role, right? My data consists of strings and pixmaps, so it is a mix of two existing data roles, what is not supported, right? Or is it possible to use setData() two times, one for alle the strings and one for all the pixmaps? Is that possible?

So what would you do? Any benefits with the second method? Is it profitable to change the way I do and create an own data role, create QTableWidgetItems, pass them my data and let it paint?

anda_skoa
15th September 2015, 10:21
You have two options

1) using QTableWidget and store the data using QTableWidgetItem
2) using QTableView and a QAbstractTableModel subclass to access data if you already have it in some tabular data structure.

When using (1), you make use of the QTableWidget's internal model.

In either case updates to the data let the model notify all views (that are currently using this model) that something has changed.
The views then update the respective cells.

The views do some of the drawing themselves, e.g. the cell grid, while they delegate the cell content drawing to delegates.

The delegates can retrieve any data from the model that the model has available, usually the data for the cell they are currently painting.
That data can be simple things like strings (e.g. Qt::DisplayRole) or pixmaps (e.g. Qt::DecorationRole) but also any complex type that can be put into a QVariant (using a custom role).
Or a combination of these.

Cheers,
_

Binary91
15th September 2015, 10:56
Ok, I see. Storing the data has the benefit that changing the data immediately results in updating the table. That is whalt I need and it makes handling easier for me.

Then, how would I declare the data role? The link you gave me (Q_DECLARE_METATYPE) only explains how to specify new data types, but not how to define new ItemDataRoles that I need to pass to the QTableWidgetItem::setData(role, data) function...

That is what I do so far:

class CalendarTableCell
{
private:
QString stringDay;
QStringList slEvents, slBirthdays;
QPixmap *pmEvent, *pmImportantEvent, *pmBirthday;
QColor colorBackground, colorDay, colorEvents;

public:
CalendarTableCell();
~CalendarTableCell();
QString getDay();
QStringList getEvents();
QStringList getBirthdays();
QList<QPixmap*> getPixmaps();
QColor getColorBackground();
QColor getColorDay();
QColor getColorEvents();
};
Q_DECLARE_METATYPE(CalendarTableCell)
Is that now automatically a new ItemDataRole? Can I do the following now:

QTableWidgetItem::setData(Qt::CalendarTableCell, data) ?? I don't think so.
What do I have to do now?

anda_skoa
15th September 2015, 11:24
The role is just an int.
Most people define their own roles using enums



enum CalendarRoles {
CellRole = Qt::UserRole + 1
}

If you only use one custom role, you can also just use Qt::UserRole with setData()

Btw, don't make those pixmap pointers, that just means you need to define a copy constructor and assignment operator for your data type so each copy gets new pointers and can delete the pointers safely.

Cheers,
_

Binary91
18th September 2015, 11:00
Ah, so that's what I was looking for, Qt::UserRole does the job for me.

Well, I solved it now with QTableWidget + subclass of QTableWidgetItem. In this subclass, I added a member that holds all data that has to be stored into the cell and also I reimplemented the setData method, so by calling setData() now, the member will be updated and set. And see, the table is immediatelly updated and repainted. Really great!

Now I recognized one more (little) problem. In my reimplemented paint function, I do text drawing with different text colors and different thickness. For this purpose, I create different QPens, like this:

void myItemDelegate::paint(QPainter *pPainter, const QStyleOptionViewItem &soviOption, const QModelIndex &miIndex) const
{
QStyledItemDelegate::paint(pPainter, soviOption, miIndex);
myTableCellData cellData = this->myTable->item(miIndex.row(), miIndex.column())->data(Qt::UserRole).value<myTableCellData>();

QPen pPen(pPainter->pen());
QRect rectStringDay = soviOption.rect, rectStringEvent = soviOption.rect;
rectStringEvent.setX(rectStringEvent.x() + 10);

pPainter->fillRect(soviOption.rect, cellData.colorBackground);

pPen.setColor(cellData.colorStringDay);
pPen.setWidth(10); // <--- doesn't work
pPainter->setPen(pPen);
pPainter->drawText(rectStringDay, cellData.stringDay);

pPen.setColor(cellData.colorStringEvents);
pPen.setWidth(2); // <--- doesn't work
pPainter->setPen(pPen);
for(int i = 0; i < 3; i++)
{
if(i >= cellData.slEvents.count())
break;
rectStringEvent.setY(soviOption.rect.y() + 3 + (i + 1) * 12);
pPainter->drawText(rectStringEvent, cellData.slEvents.at(i));
}
return;
}Setting the different text colors always works, but changing the pen's thickness with QPen::setWidth(int) doesn't change anything. Do you know why? I also tried to manually set the cosmetic mode to false, but it still doesn't work.

Any suggestions?

anda_skoa
18th September 2015, 13:03
Setting the different text colors always works, but changing the pen's thickness with QPen::setWidth(int) doesn't change anything. Do you know why? I also tried to manually set the cosmetic mode to false, but it still doesn't work.

I don't see any drawing operations that would draw lines, did you forget these in your posting?

Cheers,
_

Binary91
18th September 2015, 15:41
What do you mean with "draw lines" ? I'd like to draw text. Text with different sizes and different thickness..

anda_skoa
18th September 2015, 16:04
Ah, but text is not drawn of lines, it is rendered by a font engine.
You can increase the font size or draw the font scaled.

I.e. text doesn't have an outline, only filled area and that area is filled with the pen's brush.

Cheers,
_

Binary91
19th September 2015, 10:53
Ah, great! It works fine, thank you.

Well, my calendar looks pretty well now. Thank you for your support and your patience :D

Greetings