PDA

View Full Version : Calendar with QTableWidget and QItemDelegate



Binary91
8th September 2015, 16:18
Hi,

I'm not familiar with QItemDelegate/QStyledItemDelegate and I don't have any clue, how to use it to realize my purposes.

I'd like to display a calendar in a QTableWidget. Each QTableWidget cell represents a calendar day. If the user inserts some to-do points to a qlistwidget, these strings should be stored into the corresponding table cell, vertically aligned on the left of the cell.
Also, the user can mark some to-do points as more or less important. Depending on the importance, some different pictograms should also be stored into the table cell, positioned on the top right of the cell.

So all in all, I need to render my table cells with multiple strings in a block that should be aligned left centered in the cell and also some pictograms (e.g. .gif, .png, .jpg) in the top left corner of the cell.

I think, I can realize that with a QItemDelegate/QStyledItemDelegate, but I don't have any clue how to do that.
I was googleing for a long time now and I don't understand it. I know, I have to subclass Q(Styled)ItemDelegate and reimplement some basic functions like e.g. paint(...).

What I don't know is:
1. When and how are these functions called?
2. Which functions do I need for my purposes?
3. What do I have to do in these reimplemented functions?

When I try to google for it, I only get examples how to store QSpinBoxes or stuff like that into a table cell, but I need to know how to store multiple data and how to align each data differently in the corresponding table cell...

I would be really glad if someone could help me with that..

Thank you in anticipation!
Binary

Binary91
8th September 2015, 21:40
Is there really nobody who knows how to place multiple data into a QTableWidget cell like the following draft ("-" means empty space):

#######################
#---------------------pictogram#
# string_1---------------------#
# string_2---------------------#
#------------------------------#
#######################

I mean, layouts like the one above are often used in calendars, so there has to be a way to do it... If there is a better way than using QItemDelegate, feel free to tell me :)

anda_skoa
8th September 2015, 23:24
Is there really nobody who knows how to place multiple data into a QTableWidget cell like the following draft ("-" means empty space):

Oh, I am sure there are plenty of people who know that.
In fact I remember several threads with similar discussions.

How did you arrive at the assumption that nobody knows how to do that?

Cheers,
_

Binary91
9th September 2015, 13:05
Hi anda_skoa,

well, I just thought it because I didn't get any answers. I don't like a full compilable solution, a little hint would be enough. Nevertheless, I got a hint from somewhere else. The functionallity I was looking for can be found in the QPainter class itsself. That's why I couldn't find anything in the QItemDelegate documentation. My problem was/is, that I don't understand the handling of the QItemDelegate functions like paint(). I know now, that the paint() function is called everytime, a QTableWidget item has to be painted, right? So if I have a table with 50 cells and I create it the first time, the QItemDelegate::paint(...) function is called 50 times, right? And each time, it uses the QPainter settings (default if not modified in reimplementation) to draw the cell, right?

So I tried to reimplement the paint() function, modified the passed QPainter instance and used the drawText() method to place an example string. I used a QRect(10, 10, 100, 20) to place the string. But the result was not what I expected. The QString is absolute positioned on the table and does not check for cell borders! What happened there? I thought, each QPainter is bound to its item or its index, so why can I position the QString everywhere on the table?

anda_skoa
9th September 2015, 13:36
well, I just thought it because I didn't get any answers.

There were just a couple of hours between your postings.



The functionallity I was looking for can be found in the QPainter class itsself. That's why I couldn't find anything in the QItemDelegate documentation.

The painter is the first argument of the function and it is called "painter", in a function called "paint", so the documentation writers probably thought that it was obvious enough that the painter would be used for painting :)


I know now, that the paint() function is called everytime, a QTableWidget item has to be painted, right?

Yes, otherwise it would be pretty useless



So if I have a table with 50 cells and I create it the first time, the QItemDelegate::paint(...) function is called 50 times, right?

If all the 50 cells have content, yes.


And each time, it uses the QPainter settings (default if not modified in reimplementation) to draw the cell, right?

The base implementation does, yes.



So I tried to reimplement the paint() function, modified the passed QPainter instance and used the drawText() method to place an example string. I used a QRect(10, 10, 100, 20) to place the string. But the result was not what I expected. The QString is absolute positioned on the table and does not check for cell borders! What happened there? I thought, each QPainter is bound to its item or its index, so why can I position the QString everywhere on the table?

You get the rectangle of the cell via the "option" argument.

Cheers,
_

Binary91
9th September 2015, 17:31
The painter is the first argument of the function and it is called "painter", in a function called "paint", so the documentation writers probably thought that it was obvious enough that the painter would be used for painting Good one... :D

You get the rectangle of the cell via the "option" argument.Ok, but basically the painter doesn't look for cell borders. That is interesting. What if I'd like to place clickable links into the cell. If the link can be placed everywhere on the table, how can I know to which cell they belong to?
Or if the user resizes the window to a smaller size --> how can I handle it that the string still cares for cell borders? I mean, the string could flow over the border into the next cell...


There were just a couple of hours between your postings.Yeah yeah, patience is not my strength :rolleyes::D

d_stranz
9th September 2015, 17:40
One would think that QTableWidget::itemAt() would be just the thing you need to map between a mouse click position and the QTableWidgetItem at that location. Or a slot to handle the QTableWidget::itemClicked() signal.

(9 minutes' turnaround. Is that fast enough for you?)

Binary91
9th September 2015, 19:00
Well, when I like to get the cell clicked when a user clicks the text, then I get the cell that is hovered by the mouse, but it is maybe not the cell that holds the text, because QPainter::drawText(..) handles absolute positioning. That is what I don't understand. What's the matter with that? Is there any sence or any reason why the cells text should appear over another cell?

The other problem, as I already mentioned, is that I get problems when the user changes the windows size. As a result, even the exactly positioned strings won't be positioned exactly anymore, because they are displayed as ghost strings over the table... Isn't there any way to "store" them exactly into the cells?


(9 minutes' turnaround. Is that fast enough for you?) You're on the right way.. :p

d_stranz
9th September 2015, 21:06
Is there any sence or any reason why the cells text should appear over another cell?

You have the wrong coordinates, probably. I don;t know if the coordinates you are receiving are relative to the QTableWidget, to the QMainWindow, or to the screen. When you click somewhere, do the coordinates make sense? If you click in the top left cell, do you get coordinates that are reasonable for being in that cell? If not, you might have to do some mapping between screen, table, or main window coordinates.

And since you are painting the content of the cell yourself, then you'll have to figure out whether the position within the cell corresponds to where you have painting the link text.


The other problem, as I already mentioned, is that I get problems when the user changes the windows size. As a result, even the exactly positioned strings won't be positioned exactly anymore, because they are displayed as ghost strings over the table... Isn't there any way to "store" them exactly into the cells?

So you aren't using a delegate for this? You're just painting from an override of QTableWidget::paintEvent()?

Binary91
9th September 2015, 21:52
You have the wrong coordinates, probably. I don;t know if the coordinates you are receiving are relative to the QTableWidget, to the QMainWindow, or to the screen. When you click somewhere, do the coordinates make sense? If you click in the top left cell, do you get coordinates that are reasonable for being in that cell? If not, you might have to do some mapping between screen, table, or main window coordinates.When I use x = 0 and y = 0, the QString is positioned on the top left corner of the top left cell of the table. The problem is, the QString corresponds to another cell. That's what I don't understand. Why isn't it positioned on the top left of the corresponding cell?

Let's take an example: I have 5 columns and 5 rows. Column 2 has a custom QItemDelegate that should display a QString on the top left corner of each cell in Column 2.
So I subclass QItemDelgate (or QStyledItemDelegate), reimplement the paint(..) function and use QPainter::drawText(QRect(0,0,50,20), "TestString"). So a string "TestString" should be positioned on each top left corner of the cells in column 2. BUT instead, all 5 TestStrings are positioned on the top left corner of the top left cell (index 0,0).
I don't see any sense in this behaviour. Why can data that is painted in the paint() function that is called for each table cell leave the cell frontiers?


So you aren't using a delegate for this? You're just painting from an override of QTableWidget:aintEvent()?No, I do subclass, that is my code:
Table.h

class test : public QWidget
{
Q_OBJECT

private:
QVBoxLayout *layoutMain;
QTableWidget *tableTest;

public:
test(QWidget *parent = 0);
~test();

private:
void buildWindow();
};

class testDelegate : public QStyledItemDelegate
{
public:
testDelegate(QWidget *widgetParent = 0);
~testDelegate();

virtual void paint(QPainter*, const QStyleOptionViewItem&, const QModelIndex&) const;
};
Table.cpp

void test::buildWindow()
{
this->layoutMain = new QVBoxLayout;
this->tableTest = new QTableWidget;
this->tableTest->setRowCount(5);
this->tableTest->setColumnCount(5);
QHeaderView *headerV = this->tableTest->verticalHeader();
QHeaderView *headerH = this->tableTest->horizontalHeader();
headerV->setSectionResizeMode(QHeaderView::Stretch);
headerH->setSectionResizeMode(QHeaderView::Stretch);

testDelegate *delegateTest = new testDelegate(this);
this->tableTest->setItemDelegateForColumn(2, delegateTest);
this->layoutMain->addWidget(this->tableTest);

this->setLayout(this->layoutMain);
this->setWindowTitle("TestTable");
this->setMinimumSize(500,300);
return;
}

void testDelegate::paint(QPainter *pPainter, const QStyleOptionViewItem &soviOption, const QModelIndex &miIndex) const
{
pPainter->drawText(QRect(0,0,50,20), "TestString");
QStyledItemDelegate::paint(pPainter, soviOption, miIndex);
return;
}Unfortunatelly, all TestString strings are positioned on the top left of the table instead of the top left corners of each cell in column 2... This behaviour is strange and makes window resize handling much more complicated, because the QItemDelegate doesn't store the data into the cells but lays the data over the cells without careing about the cell borders... While changing the window size, the table cells adjust but the data doesn't .... this shouldn't go this way, should it??:confused:

EDIT:
The most interesting and confusing fact is, that when I do it like anda_skoa mentioned and use the QRect() that is given when calling the option.rect member for positioning, then everything works as I expected! The strings are applied correctly on each top left corner of the cells in column 2 AND they correctly move while resizing the window! But where is the difference to my manually created QRect() ?? I mean, I could also check in a if-else condition which cell of column 2 has index and create a corresponding QRect() to position the strings. But they will not move correctly while resizing the window, they are not stored into the cell but only hover it... Can you tell me why?

anda_skoa
9th September 2015, 22:17
What's the matter with that? Is there any sence or any reason why the cells text should appear over another cell?

Likely not, but there are different ways to handle this.
Imposing one would likely be inflexible for very little gain.

The implementation of paint() could take the coordinates from the rect and paint with that, or it could translate the painter and paint with relative coordinates.
It could restrict its painting or make the painter clip.

So always translating and clipping would be very inefficient if the paint code takes care of that itself, e.g. painting a pixmap, or just filling the rectangle.

if you want the painter to be translated, just tell it to. Same for clipping.

Cheers,
_

Binary91
9th September 2015, 22:37
Well, I still don't understand it. I mean, assuming that option.rect returns a QRect(100,0,50,20) for cell1 in column2 and a QRect(100,50,20,20) for cell2 in column2. Where is the difference when I create the QRect manually with the same coordinates and pass it to the drawText function?? I mean, the compiler should replace both QRects with the same data, doesn't it?? So why does the first example show correct behaviour with correct positioning of each string on the top left of each cell and respecting cell borders, while the second example results in absolute positioning at the top left of the whole table and "hovering" the strings over all cells??

EDIT:
I catched the option.rect coordinates for all 5 cells of column 2 and printed them via qDebug() to get the x, y, w, h of the rect.
X is always 185
Y is a multiple of 51
w is always 91
h is always 50

So the following to methods do exactly the same (correct) positioning (I tested it):

painter->drawText(option.rect, "TestString");

painter->drawText(QRect(185,index.row()*51,91,50), "TestString");
BUT the difference is, that the first example stores the strings INTO the cells (changing window size also lets the strings move with its corresponding cells). The second example doesn't. In the second example, the strings are layed over the table, they don't belong to a cell and fly over their borders when resizing the window... They are like ghosts!

Can you explain this behaviour?

EDIT 2:
Ok, everything is clear now. I forgot the fact that paint(..) is also called while resizing and I would always override the new rect in option.rect with my own calculations...

Thread is solved :rolleyes:

anda_skoa
9th September 2015, 23:47
I mean, the compiler should replace both QRects with the same data, doesn't it??

Yes, the compiler will use the same code for all instances of QRect, only their data differs.
If two instances of QRect have the same data, then the same operations will have the same result.

Cheers,
_

Binary91
10th September 2015, 11:44
Well, maybe I have one more question:
In my code above you can see, that I subclass QItemDelegate but I only reimplement the paint function. So the editor behaviour should be the same as in default delegate mode.

What I'm asking myself now is, how the delegate (which, in my eyes, can be seen as a layer of data over the table) interacts with QTableWidgetItems. For a example, I inserted QTableWidgets in every table cell with a text "itemText". Also, I used the custom QItemDelegate and let the paint function paint a text "delegateText" over every cell.
The result is (as expected), that the "delegateText" is displayed over the "itemText". So the delegate is like a layer that is not interested in what the cell does.

BUT, what happens if I try to edit the QTableWidgetItem by double click? As expected, an editable cursor appears. But is this now the editor of the QTableWidgetItem or is it the editor of the QItemDelegate ?? And what would happen if I disable one of them?

anda_skoa
10th September 2015, 12:08
I am pretty sure that the item goes through the delegate.

Basically the Item based views are a convenience (all-in-one) and somewhat compatibility measure (similar API as in Qt3) over actual model/view.

So the QTableWidgetItem is just a way to put some data into the internal model of the QTableWidget, all output and input is handled via the mechanism of the QTableView, e.g. using delegates.

Cheers,
_

Binary91
10th September 2015, 12:23
So you mean that the QTableWidgetItem::setText(QString&) function results at least in the same as directly using QPainter::drawText(...) ? Hence, at least, the QTableWidgetItem with all its functionallity is just an "easier" way to use the QPainter directly? That means, that there are no two different editors, its always the same editor, but two ways to call/disable it?
That sounds logically. That would explain why no text appears when I try to show text via QTableWidgetItem while also reimplementing QItemDelegate::paint(...) as an empty function with no executing code... If I'm right, then QTableWidgetItem::setText("Test") also calls QItemDelegate::paint(..) --> so if I reimplement this function as an empty function, nothing will appear in the table...

anda_skoa
10th September 2015, 13:43
So you mean that the QTableWidgetItem::setText(QString&) function results at least in the same as directly using QPainter::drawText(...) ?

No, these are two different things.
QTableWidgetItem::setText() stores a text for a given cell.
The delegate's paint() method draws contents of a cell.


Hence, at least, the QTableWidgetItem with all its functionallity is just an "easier" way to use the QPainter directly?

There is no access to a QPainter via QTableWidgetItem


That means, that there are no two different editors, its always the same editor, but two ways to call/disable it?

Where do you see different ways to enable/disable it?



That would explain why no text appears when I try to show text via QTableWidgetItem while also reimplementing QItemDelegate::paint(...) as an empty function with no executing code

If the delegate in use does not paint anything, not data will be displayed, independent of how that data is stored.



If I'm right, then QTableWidgetItem::setText("Test") also calls QItemDelegate::paint(..) --> so if I reimplement this function as an empty function, nothing will appear in the table...
QTableWidgetItem::setText() stores data inside the QTableWidget.
QItemDelegate::paint() draws data it gets from the QTableWidget-

Cheers,
_

Binary91
10th September 2015, 15:27
QTableWidgetItem::setText() stores data inside the QTableWidget.
QItemDelegate:aint() draws data it gets from the QTableWidget-Ah, so the table handling is:
1. step: building the table and getting / storing all its contents
2. step: depending on the contents of step 1, informing the delegate about the contents that have to be painted

So, via QTableWidgetItem::setText(...) I can store the text and in later use, I can get information about the text again (QTableWidgetItem::text() or via index from QTableWidget directly). In contrast, the text I directly paint via QPainter::drawText(..) is not "registered" anywhere in the table (model) so I can't address it later, right??


Where do you see different ways to enable/disable it?Well, I can use QTableWidgetItem::setFlags(..) to disable editing, and I can reimplement the QItemDelegate::createEditor and any other editor functions from the delegate and left them empty. Or am I wrong? What would happen if I mix them?
I could imagine that this handling is the same as with the text example above. I guess, the QTableWidget (or the model) informs the delegate about whether to create an editor or not, so if I disable the edit functionallity via QTableWidgetItem::setFlags(..), the delegate will not create an editor. But nevertheless, I can still create the editor via reimplementing the delegate's editor functionallity, right?

anda_skoa
10th September 2015, 16:29
Ah, so the table handling is:
1. step: building the table and getting / storing all its contents
2. step: depending on the contents of step 1, informing the delegate about the contents that have to be painted

More or less, yes.
You don't have to inform the delegate about anything, the view (in your case the QTableWidget) takes care of calling the delegate when necessary.



So, via QTableWidgetItem::setText(...) I can store the text and in later use, I can get information about the text again (QTableWidgetItem::text() or via index from QTableWidget directly).

Exactly.



In contrast, the text I directly paint via QPainter::drawText(..) is not "registered" anywhere in the table (model) so I can't address it later, right??

The text that the delegate retrieves via the model index is the text stored by the table widget item.



Well, I can use QTableWidgetItem::setFlags(..) to disable editing, and I can reimplement the QItemDelegate::createEditor and any other editor functions from the delegate and left them empty. Or am I wrong? What would happen if I mix them?

Well, not implementing something is not the same as "disabling".
I'd call that "broken".



I could imagine that this handling is the same as with the text example above. I guess, the QTableWidget (or the model) informs the delegate about whether to create an editor or not, so if I disable the edit functionallity via QTableWidgetItem::setFlags(..), the delegate will not create an editor.

More or less, yes.




But nevertheless, I can still create the editor via reimplementing the delegate's editor functionallity, right?

Reimplementing the createEditor() function allows you to create your own editor widget.
The decision whether to call that function is still with the view, which makes that decision based on user events, its own configuration and cell flags.

Cheers,
_

Binary91
10th September 2015, 17:25
Ok thanks, that makes sense. I can reimplement as much as I want, as long as the view doesn't call the editor functions, nothing will happen..

Well, one last question. In my case, I let all data draw directly via QPainter, so the data is never stored into the table. One could say, the table doesn't know, that any data exists.

How can I address this data and how can I react on user interaction with this data? I mean, I'd like to have clickable links drawn with QPainter::drawText(..), but I don't know where to ask for information when the whole view doesn't know anything about the data... How can I do that?

EDIT:
And a second question:
What is better for performance: handling the whole table design (background color, text etc.) directly via QItemDelegate::paint(...) or would it be better to create QTableWidgetItems and set the specific background colors? I mean, setting background colors via delegate would result in updating the background color each time anything happens, but that is not neccessary for me, because I set the background colors once and they are static. On the other hand, I could imagine that the delegate will do that anyway, because, as I know now, it gets the information from the table so it will repaint the backgrounds like the QTableWidgetItems orders it...

So, is there any sense in using QTableWidgetItems when I use a custom delegate anyway?

d_stranz
10th September 2015, 18:16
And a second question:
What is better for performance: handling the whole table design (background color, text etc.) directly via QItemDelegate:aint(...) or would it be better to create QTableWidgetItems and set the specific background colors? I mean, setting background colors via delegate would result in updating the background color each time anything happens, but that is not neccessary for me, because I set the background colors once and they are static. On the other hand, I could imagine that the delegate will do that anyway, because, as I know now, it gets the information from the table so it will repaint the backgrounds like the QTableWidgetItems orders it...

So, is there any sense in using QTableWidgetItems when I use a custom delegate anyway?

It sounds to me that what you really need is to pop up a level in the widget hierarchy and use a QTableView (instead of QTableWidget) and derive a model from QAbstractTableModel. In the model's data() method, you have control over all kinds of appearance things. See Qt::ItemDataRole. In trying to implement a delegate that jumps through all these hoops, you are essentially reinventing all of the functionality that QAbstractTableModel / QTableView give you for free.

anda_skoa
10th September 2015, 19:42
n my case, I let all data draw directly via QPainter, so the data is never stored into the table. One could say, the table doesn't know, that any data exists.

You are saying that your delegate uses the row and column information to get the data from a separate data structure?



How can I address this data and how can I react on user interaction with this data? I mean, I'd like to have clickable links drawn with QPainter::drawText(..), but I don't know where to ask for information when the whole view doesn't know anything about the data... How can I do that?

Well, if you get your data from some custom data structure, then why can you access it in paint() but not when handling user event?


What is better for performance: handling the whole table design (background color, text etc.) directly via QItemDelegate::paint(...) or would it be better to create QTableWidgetItems and set the specific background colors? I mean, setting background colors via delegate would result in updating the background color each time anything happens, but that is not neccessary for me, because I set the background colors once and they are static. On the other hand, I could imagine that the delegate will do that anyway, because, as I know now, it gets the information from the table so it will repaint the backgrounds like the QTableWidgetItems orders it...

Well, the painting is always done by the delegate.
Whether you use the background brush provided by via the options or decide on color based on some data in the delegate shouldn't make much difference.



So, is there any sense in using QTableWidgetItems when I use a custom delegate anyway?

As I said before, those handle different levels.
The widget item is used as an API for adding data to the table.
The delegate is painting that data.

Whether you want to handle the data via widget items or via a model is orthogonal to using a delegate to achieve a custom visualization.

Cheers,
_