PDA

View Full Version : How to simplify a complex item delegate?



Adam Badura
8th February 2013, 13:04
I have a QListView which paints (and measures) its items with a custom QStyledItemDelegate.

But over time the item grew quite complex. When it was still a thumbnail and to the right of it a bunch of header/value label pairs aligned in a column the code was long and ugly but it was still manageable. But when now I have to add some additional icons the measuring and painting code got really awful. It feels more than natural to use a custom QWidget instead since it can easily use all the goodies of UI Designer, styling and so on.

However by reading documentation and tutorials I felt like it is the *View family of widgets that gets "more love" than the *Widget family. While my personal experience leads me to think that QAbstractItemModel-based approach to Model/View framework is actually quite limited and doing anything more than what Qt has thought about quickly becomes quite difficult.

Or is there maybe some other approach to writing custom QStyledItemDelegate which deals with complexity better?

wysota
8th February 2013, 13:23
It really depends on what you want to do. For some cases it might be feasible to replace QListView with QDeclarativeView and implement the list in QML where it is much easier to code delegates. But again, maybe it is easy to do what you want with item views and you just need to restructure your code.

Adam Badura
8th February 2013, 22:39
It really depends on what you want to do.

For example something like the Windows Media Player list view shown here (http://stackoverflow.com/questions/4409535/windows-media-player-styled-listview). Or even its sub-case: just the album thumbnail with labels on the right. The more data you have the more difficult it gets to do it with QAbstractItemDelegate. Not to mention how styling gets difficult.

wysota
8th February 2013, 22:57
If you have a single constant layout then implementing such a delegate is trivial. I thought you wanted something more complex. Just bear in mind this is a table (or even a tree) and not a list.

Adam Badura
8th February 2013, 23:51
If you have a single constant layout then implementing such a delegate is trivial.

I would not agree with that. Especially after writing such a delegate just few days ago (and mine had just a thumbnail + 2 pairs of header/value labels). It took me some time (OK, I'm know to Qt) and resulting code is long, difficult to understand and maintain. Even thou it contains mostly just calls to measuring functions. Adding another 2 labels or some extra icons, especially by someone else after some time would be really painful.

And such layout code felt like a natural candidate for generalization and extraction as a separate class/function. With this question I was trying to know those generalization.


I thought you wanted something more complex. Just bear in mind this is a table (or even a tree) and not a list.

It is not really important. After all it was just an example.

wysota
9th February 2013, 00:59
I would not agree with that. Especially after writing such a delegate just few days ago (and mine had just a thumbnail + 2 pairs of header/value labels). It took me some time (OK, I'm know to Qt) and resulting code is long, difficult to understand and maintain.
Well, you have every right to disagree however in my opinion the delegate for the thumbnail and those three labels is around 3-6 lines long (depending how you choose to implement it).


And such layout code felt like a natural candidate for generalization and extraction as a separate class/function. With this question I was trying to know those generalization.
You can write such layout engine however in my opinion it is an overkill since your "layout" is static and always the same. Having a layout engine makes sense if you expect to have different layouts in every item or you expect that some pieces of data will change their visual position and the layout will have to be recalculated.



It is not really important. After all it was just an example.
On the contrary, this is very important. In a list you have to render the whole row in one go. With a table or a tree every column is rendered separately thus implementation of each delegate is greatly simplified. You have to choose proper tools for each job.

Adam Badura
9th February 2013, 09:22
Well, you have every right to disagree however in my opinion the delegate for the thumbnail and those three labels is around 3-6 lines long (depending how you choose to implement it).

Could you provide an example? Since maybe I just did it in some very sub-optimal way.

In my case it is a QTreeView but it could be limited to QListView as we are using it that way currently. There is a thumbnail in Qt::DecorationRole. To the right of it there are two text fields stacked vertically: "Name: ..." with value taken from Qt::DisplayRole and "Length: ..." with value from custom role. The header labels are aligned to the left, and the values are aligned to the left so there are two columns.

Adam Badura
20th February 2013, 07:42
Dear wysota, you wrote:


If you have a single constant layout then implementing such a delegate is trivial.

and


Well, you have every right to disagree however in my opinion the delegate for the thumbnail and those three labels is around 3-6 lines long (depending how you choose to implement it).

Since I found it highly questionable I asked for that simple thing:


Could you provide an example? Since maybe I just did it in some very sub-optimal way.

In my case it is a QTreeView but it could be limited to QListView as we are using it that way currently. There is a thumbnail in Qt::DecorationRole. To the right of it there are two text fields stacked vertically: "Name: ..." with value taken from Qt::DisplayRole and "Length: ..." with value from custom role. The header labels are aligned to the left, and the values are aligned to the left so there are two columns.

Over 10 days passed. You were here. And yet you did not provide those trivial 3-6 lines... Let others who come here with similar issue judge that...

Lykurg
20th February 2013, 08:27
Let others who come here with similar issue judge that...
Well it is because he don't want to write the delegate for you and one annoying thing is when people requesting code but didn't provide what they have tried so far. This leads to the conclusion - which my not be valid in every case - that nothing has be done so far... So I can understand him. And the attitude you are showing is not helpful either. (Beside, if someone has more than 28.000 posts and more then 4.000 thanks, which is the most here, he probably is a nice guy. In this case, I can confirm that;))

As to your problem: Have you seen the Star delegate Example? From there:
void StarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (qVariantCanConvert<StarRating>(index.data())) {
StarRating starRating = qVariantValue<StarRating>(index.data());

if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, option.palette.highlight());

starRating.paint(painter, option.rect, option.palette,
StarRating::ReadOnly);
} else {
QStyledItemDelegate::paint(painter, option, index);
}
} Which total represent your example link of a music player.

Adam Badura
20th February 2013, 10:00
Which total represent your example link of a music player.

It does not. The star sample skips the most important (for me) part: the thumbnail and stacked labels on the right of it. And that is what I actually want to achieve (the Windows Media Player sample was just first that same to my mind - and that I found - when trying to provide some sample). What I wanted to achieve I have described in the very first post (second paragraph) and in the one that asked explicitly for the "trivial code" (also second paragraph).

By the way: In my Qt 4.8.4 when I build Examples and Demos with qmake -tp vc -r the resulting Visual Studio solution did not open cleanly in Visual Studio since some projects had same names and the solution didn't use its folders. Yet I believe this is only a "GUI issue". MS Build shouldn't care about it anyway. More important issue was that the star example wasn't build at all! It is not included in its "parent .pro file".

I didn't post more details on my current state since I considered that irrelevant and "obfuscated" to much with particular details of my project while my investigations and research lead me to think it is a general issue rather than my specific one. But sure, I can give more details. This is how it looks now:

8742

It is a QTreeView (from historical reasons and for future extensions; for what it is now it could very well be just a QListView). The white header at the begining (with "Adam Badura") is a header with single column. Bellow are the items. The backgrounds alternate (although even-background is not visible here). Various states are represented with various background colors. On the left of each item there is a thumbnail (in this quickly-taken snapshot all thumbnails are same). To the right of it are the stacked labels (likely soon we will want to add at least two more labels). The green arrow is a quickly added icon. It does not align to anythig (yet).

The view itself works in two modes. This is the "expanded" one. The "simple" one has shorter single-line items done mostly with default drawing. It is done by alternating item delegate. But I'm considering switching to alternating QTreeViews instead (that would just share single model) as it seems simpler and does not require use of apparently undocumented QTreeView::doItemsLayout() function.

Now the crucial part of the code it auxiliary delegate function measureItem (it is my own function). It measures and lays out all the parts of the item and the item itself. Then I call it from QStyledItemDelegate::sizeHint (for the item size) and from QStyledItemDelegate::paint (which next just calls a bunch of drawing functions within the already computed rectangles). Code is following:


void ElExtendedPlaylistEditorDelegate::measureItem(
QStyleOptionViewItemV4 const& option,
QStyle const* stylePtr,
QModelIndex const& index,
QRect& item,
QRect& thumbnail,
QRect& nameHeaderLabel,
QRect& nameDataLabel,
QRect& lengthHeaderLabel,
QRect& lengthDataLabel,
QRect& videoEffectImageRect
) const
{
// More or less arbitrarily selected values.

// Margins within the item in which components will be laid out.
QMargins padding( 2, 2, 2, 2 );
// Enough to show a reasonable icon.
int minimumThumbnailHeight = 64;
// HD aspect ratio.
boost::rational< int > thumbnailAspectRatio( 1920, 1080 );

int thumbnailLabelsMargin =
stylePtr->layoutSpacing(
QSizePolicy::DefaultType,
QSizePolicy::Label,
Qt::Horizontal,
&option,
option.widget
);
if ( thumbnailLabelsMargin == -1 )
thumbnailLabelsMargin = 5;

int labelsVerticalMargin =
stylePtr->layoutSpacing(
QSizePolicy::Label,
QSizePolicy::Label,
Qt::Vertical,
&option,
option.widget
);
if ( labelsVerticalMargin == -1 )
labelsVerticalMargin = 2;

// Obtain/reformat some data.

EbTimecode length =
index.data(
sps::spsGUI::PlaylistModel::CustomRole::ITEM_LENGT H
).value< EbTimecode >();
QString lengthString = length.getTimecodeString();


// Measure components which are not flexible.

// Note that the measuring includes a space between header and data. This way
// margin is scaled well with the font that is used rather than fixed to given
// pixels value.

nameHeaderLabel =
stylePtr->itemTextRect(
option.fontMetrics,
QRect(),
Qt::AlignLeft | Qt::AlignBottom,
( option.state & QStyle::State_Enabled ) != 0,
tr( "Name:" ) + " "
);

nameDataLabel =
stylePtr->itemTextRect(
option.fontMetrics,
QRect(),
Qt::AlignLeft | Qt::AlignBottom,
( option.state & QStyle::State_Enabled ) != 0,
option.text
);

lengthHeaderLabel =
stylePtr->itemTextRect(
option.fontMetrics,
QRect(),
Qt::AlignLeft | Qt::AlignBottom,
( option.state & QStyle::State_Enabled ) != 0,
tr( "Length:" ) + " "
);

lengthDataLabel =
stylePtr->itemTextRect(
option.fontMetrics,
QRect(),
Qt::AlignLeft | Qt::AlignBottom,
( option.state & QStyle::State_Enabled ) != 0,
lengthString
);

// it will be changed after demo:)
videoEffectImageRect = QRect( 0, 0, 30, 20 );

// Now start laying out the components with labels.

nameHeaderLabel.translate(
-nameHeaderLabel.left(),
-nameHeaderLabel.top()
+ option.rect.top()
+ padding.top()
);
nameDataLabel.translate(
-nameDataLabel.left(),
-nameDataLabel.top()
+ option.rect.top()
+ padding.top()
);

Q_ASSERT( nameHeaderLabel.height() == nameDataLabel.height() );
int nameJoinedLabelHeight =
( std::max )( nameHeaderLabel.height(), nameDataLabel.height() );

lengthHeaderLabel.translate(
-lengthHeaderLabel.left(),
-lengthHeaderLabel.top()
+ option.rect.top()
+ padding.top()
+ nameJoinedLabelHeight
+ labelsVerticalMargin
);
lengthDataLabel.translate(
-lengthDataLabel.left(),
-lengthDataLabel.top()
+ option.rect.top()
+ padding.top()
+ nameJoinedLabelHeight
+ labelsVerticalMargin
);
videoEffectImageRect.translate(
-videoEffectImageRect.left(),
-videoEffectImageRect.top()
+ option.rect.top()
+ padding.top()
+ nameJoinedLabelHeight
+ labelsVerticalMargin
+ labelsVerticalMargin
);

Q_ASSERT( lengthHeaderLabel.height() == lengthDataLabel.height() );
int lengthJoinedLabelHeight =
( std::max )( lengthHeaderLabel.height(), lengthDataLabel.height() );

int headersWidth =
( std::max )( nameHeaderLabel.width(), lengthHeaderLabel.width() );

nameHeaderLabel.translate(
option.rect.left()
+ padding.left(),
0
);
nameDataLabel.translate(
option.rect.left()
+ padding.left()
+ headersWidth,
0
);

lengthHeaderLabel.translate(
option.rect.left()
+ padding.left(),
0
);
lengthDataLabel.translate(
option.rect.left()
+ padding.left()
+ headersWidth,
0
);
videoEffectImageRect.translate(
option.rect.left()
+ padding.left()
+ headersWidth
+ lengthDataLabel.width(),
0
);

int labelsWidth =
headersWidth
+ ( std::max )( nameDataLabel.width(), lengthDataLabel.width() );
int labelsHeight =
nameJoinedLabelHeight
+ labelsVerticalMargin
+ lengthJoinedLabelHeight;


// Now that the labels are laid out we have to include the thumbnail.

int thumbnailHeight =
(std::max )( minimumThumbnailHeight, labelsHeight );
auto thumbnailWidthExact = thumbnailAspectRatio * thumbnailHeight;
int thumbnailWidth = boost::rational_cast< int >( thumbnailWidthExact );

Q_ASSERT( thumbnailHeight >= labelsHeight );
int itemContentsHeight = thumbnailHeight;

int itemContentsWidth =
thumbnailWidth
+ thumbnailLabelsMargin
+ labelsWidth;


thumbnail.setCoords(
option.rect.left()
+ padding.left(),
option.rect.top()
+ padding.top(),
option.rect.left()
+ padding.left()
+ thumbnailWidth,
option.rect.top()
+ padding.top()
+ itemContentsHeight
);

// Offset labels by the thumbnail width (plus margin).

nameHeaderLabel.translate(
thumbnailWidth + thumbnailLabelsMargin,
0
);
nameDataLabel.translate(
thumbnailWidth + thumbnailLabelsMargin,
0
);
lengthHeaderLabel.translate(
thumbnailWidth + thumbnailLabelsMargin,
0
);
lengthDataLabel.translate(
thumbnailWidth + thumbnailLabelsMargin,
0
);

videoEffectImageRect.translate(
thumbnailWidth + thumbnailLabelsMargin,
0
);

// Finally item size itself.

item.setCoords(
option.rect.left(),
option.rect.top(),
option.rect.left()
+ padding.left()
+ itemContentsWidth
+ padding.right(),
option.rect.top()
+ padding.top()
+ itemContentsHeight
+ padding.bottom()
);
}

As can be seen obviously it is neither trivial nor 3-6 lines...

And now consider that user requests a change and wants the "header labels" ("Name:" and "Length:") to be aligned to right rather than to left in its column. It can be done. But it requires a programmer who understands the code. While conceptually it should be just "changing an enum value" and I believe UI Designer would allow such change much easier. But since I have QTreeView I cannot design my items in UI Designer...

lanz
20th February 2013, 10:26
I have a crazy idea. :D

What if you add to your delegate pointer to a shadow QWidget.
Then you'll do all the custom layout in designer, and add resulting QWidget to your delegate.
Then in paint event you apply necessary tweaks to the widget, and then render it using QWidget::render ()!

Lykurg
20th February 2013, 10:33
It does not. The star sample skips the most important (for me) part: the thumbnail and stacked labels on the right of it.
Ups, I call that selective perception... Right.


Now the crucial part of the code
Ok, you have a tree view: 1st column the image -> handled by the standard item delegate. 2nd column the labels. To simplify that and to extend it easily use loops e.g.
QList<QPair<QString,QString> > labels; // a pair has "label:value"
// max width of label description:
int maxDescWidth = 0;
foreach(QPair<QString,QString> l, labels)
maxDescWidth = qMax(maxDescWidth, fontmetrics.width(l.first));

// then drawing
int height = fontmetrics.height() + padding;
for(int i = /*...*/) {
painter->drawText(QRect(0,i*height, maxDescWidth, height), Qt::AlignRight, labels.at(i).first);
// same for the text.
}


That's just a quick mockup but I guess you see where it is going. This way you don't need a function calculating each bounding rect.

Adam Badura
20th February 2013, 10:41
This way you don't need a function calculating each bounding rect.

(Just a quick note, I will think about both above solutions a bit more later.)

I need to know the size for QStyledItemDelegate::sizeHint as well...

Also I considered using columns. At least to separate the thumbnail and labels issues. But I found it to complex and risky in face of the two modes I described earlier. And thus I considered making those two separate QTreeView in the first place. I will think about it as well.

Lykurg
20th February 2013, 11:03
I need to know the size for QStyledItemDelegate::sizeHint as well...Yes, but only the height and width, not every exact position of the elements. Also I guess the count of labels is known and static, so you can cache that information. So it would be easy:
int countOfLabels = 3; // or whatever
int h = 0;
h = countOfLabels * (fontmetrics.height() + padding)
h = qMax(h, heightOfThumbnail);



Also I considered using columns. Also that doesn't change much of my example:
QList<QPair<QString,QString> > labels; // a pair has "label:value"
// Draw the pixmap
int currentX = pixmap.width() + padding /* +... */;

// then drawing
int height = fontmetrics.height() + padding;
for(int i = /*...*/) {
painter->drawText(QRect(currentX, i*height, maxDescWidth, height), Qt::AlignRight, labels.at(i).first);
// same for the text.
}

The solution with a QWidget is possible but then you could simply use Q***Widget. And also the delegate functions are called quite a lot so it isn't with good performance.

wysota
20th February 2013, 11:58
Over 10 days passed. You were here. And yet you did not provide those trivial 3-6 lines...

Ouch, sorry, I forgot about it.

This should work (not tested but you should get the picture...)


void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const {
QPixmap px = index.data(Qt::DecorationRole).toPixmap();
painter->drawPixmap(option.rect.topLeft()+QPoint(1,1), px);
painter->drawText(option.rect.adjusted(px.width+1+5, 1, -1, -1), Qt::AlighLeft|Qt::AlignTop, QString("%1\n%2\n%3").arg(index.data(Qt::DisplayRole).toString()).arg( index.data(...).toString()).arg(index.data(...).to String()));
}

Similar for sizeHint(), just use QFontMetrics::boundingRect() instead of painting.

Edit: Next time, instead of assuming my bad will, just send me a PM with a reminder or bump the thread.

Adam Badura
24th February 2013, 22:12
This should work (not tested but you should get the picture...)


void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const {
QPixmap px = index.data(Qt::DecorationRole).toPixmap();
painter->drawPixmap(option.rect.topLeft()+QPoint(1,1), px);
painter->drawText(option.rect.adjusted(px.width+1+5, 1, -1, -1), Qt::AlighLeft|Qt::AlignTop, QString("%1\n%2\n%3").arg(index.data(Qt::DisplayRole).toString()).arg( index.data(...).toString()).arg(index.data(...).to String()));
}

Only that this does not do what I wanted. The 3 text fields consist of two columns which have to be aligned properly. I did mention that and it was visible on the image of current state I posted in the mean time.

Although I do like the idea of drawing multiline text. I wasn't aware this is possible. Some simplification at least.


The solution with a QWidget is possible but then you could simply use Q***Widget. And also the delegate functions are called quite a lot so it isn't with good performance.

Why do you think this is bad? I am actually very attracted to this idea as it would allow me to do what I indeed wanted: use UI for designing the items. Also this might ease styling. After all the QWidget drawing/layout would do the same thing that I would do "by hand".

wysota
24th February 2013, 23:38
Only that this does not do what I wanted. The 3 text fields consist of two columns which have to be aligned properly. I did mention that and it was visible on the image of current state I posted in the mean time.
I don't see any "two columns" on the image you posted (http://stackoverflow.com/questions/4409535/windows-media-player-styled-listview). Could you elaborate what you mean? I can see a thumbnail and four rows of single word strings.


Why do you think this is bad?
This is really bad, trust me :) Try having more than 10 rows with cell widgets and notice how performance drops. If you need anything complex I would really consider using QtQuick for the view.

Anyway I will think about some mechanism for simplified delegate layout. I think for static cases such as yours this is possible. It's much more complicated if you want to do animations (e.g. like in QProgressBar on modern Windows). I used to tutor a bachelor thesis that aimed to work around this problem.

If you don't hear back from me in a couple of days, send me a PM or bump the thread.

Adam Badura
25th February 2013, 06:54
I don't see any "two columns" on the image you posted (http://stackoverflow.com/questions/4409535/windows-media-player-styled-listview). Could you elaborate what you mean? I can see a thumbnail and four rows of single word strings.

See second paragraph of my first post. Then second paragraph of the post in which I asked you for sample. Then the post in which I provided my current code and a snapshot of what it does. The Windows Media Player example was just that. An example of similar layout which I found quickly. I didn't know any better example and didn't want to spend to much time looking for one.



This is really bad, trust me :) Try having more than 10 rows with cell widgets and notice how performance drops. If you need anything complex I would really consider using QtQuick for the view.

I didn't want to have QWidget for every item. (This way I could use QTreeWidget instead...) I wanted to have just one object hidden behind the scenes which I would feed with data (either directly or by sharing model and changing "active index") to than size/paint through it. Would that be bad to? Why? What more would it do (as compared to ordinary delegate) that it would influence performance so much?

I cannot switch to QtQuick. I would like but the decision is not up to me.

wysota
25th February 2013, 09:12
See second paragraph of my first post. Then second paragraph of the post in which I asked you for sample. Then the post in which I provided my current code and a snapshot of what it does. The Windows Media Player example was just that. An example of similar layout which I found quickly. I didn't know any better example and didn't want to spend to much time looking for one.
Your delegate is still pretty simple.


I didn't want to have QWidget for every item. (This way I could use QTreeWidget instead...)
I don't understand how using QTreeWidget influences anything here.


I wanted to have just one object hidden behind the scenes which I would feed with data (either directly or by sharing model and changing "active index") to than size/paint through it. Would that be bad to? Why?
You would have to modify most properties of that object during every paint which is quite heavy.


I cannot switch to QtQuick.
Not even for this single view?

wysota
25th February 2013, 15:11
Just to let you know, I already have a preliminary working solution. It's not pixel-perfect yet, but it already gives acceptable results.

8769


DelegateLayout *l = new DelegateLayoutFrame(Qt::Horizontal);
l->setSpacing(10);
l->setMargins(QMargins(5,5,5,5));
DelegatePixmap *p1 = new DelegatePixmap(l);
DelegateText *t1 = new DelegateText(l);
t1->setRole(Qt::UserRole+1);
t1->setMargins(QMargins(5,5,5,5));
t1->setMinimumSize(QSize(QFontMetrics(f).width("MMMM"), 1));

DelegateLayout *headersl = new DelegateLayout(Qt::Vertical, l);
DelegateStaticText *h1 = new DelegateStaticText(headersl);
h1->setText("Name:");
DelegateStaticText *h2 = new DelegateStaticText(headersl);
h2->setText("Created:");

DelegateLayout *valuesl = new DelegateLayout(Qt::Vertical, l);
DelegateText *t2 = new DelegateText(valuesl);
t2->setRole(Qt::DisplayRole);
t2->setForegroundColor(Qt::darkGreen);
DelegateText *t3 = new DelegateText(valuesl);
t3->setRole(Qt::UserRole);
QListView v;

LayoutDelegate *ld = new LayoutDelegate(&v);
ld->setLayout(l);
v.setItemDelegate(ld);
v.setModel(&model);

v.show();

Adam Badura
27th February 2013, 11:23
I'm waiting. Looks very promising. Those Delegate* types are your own, are they?

wysota
27th February 2013, 11:28
I'm waiting. Looks very promising. Those Delegate* types are your own, are they?

Yes, they are mine.

Here is the current state:

8778

Adam Badura
8th March 2013, 06:56
How is this going? I got assigned back to the task at least for some time so I can now introduce improvements.

wysota
8th March 2013, 09:34
Are you interested only in the display of items or also about editing and interaction?

Adam Badura
8th March 2013, 09:51
Are you interested only in the display of items or also about editing and interaction?

Only display for now.

wysota
8th March 2013, 10:08
In that case I will post my current solution and provide a link to it during the weekend. You want to use it in a commercial project?

Adam Badura
8th March 2013, 10:10
In that case I will post my current solution and provide a link to it during the weekend. You want to use it in a commercial project?

I hoped I will not have to use it exactly as you did but rather get inspired how to do those things in Qt. But yes, it is a commercial project.

wysota
8th March 2013, 11:07
Well, I can tell you how I did it. I have a tree of items that models the structure of the delegate. Items in that tree are subclasses of "DelegateLayoutItem" which can be either subclasses of "DelegateLayout" (which currently can be just DelegateLinearLayout) or DelegateWidget that can be subclassed any way one wants (I have subclasses that offer text or image that comes from the model and static text or static pixmap). The basic idea is that each of them provides a sizeHint() implementation and a paint() implementation. Both of them take a model index and QStyleOptionViewItem instance. The rest is just to calculate sizeHint() down the tree for a specific index and item option and paint everything. Just like for regular widgets or graphics items. I don't respect size policies yet and I only respect right alignment for a vertical layout. There is no event forwarding yet but the same idea can be employed for it.

Here is a sample (not actual, I don't have the code with me right now) implementation of sizeHint:


QSize DelegateText::sizeHint(const QModelIndex &index, const QStyleOptionViewItem &option) const {
QString text = index.data(m_role).toString();
return option.fontMetrics.boundingRect(option.rect, 0, text).adjusted(-leftMargin(), -topMargin(), rightMargin(), bottomMargin()).size();
}

and a sample paint:


void DelegateText::paint(QPainter *painter, const QModelIndex &index, const QStyleOptionViewItem &option, const QRect &rect) {
QRect r = rect.adjusted(leftMargin(), topMargin(), -rightMargin(), -bottomMargin());
painter->drawText(r, 0, index.data(m_role).toString());
}

Adam Badura
21st March 2013, 23:39
That is interesting! But how scalable is that?

Since now I'm facing a much bigger thing of similar sort. We have a list (QAbstractListModel) of channels. The main window shows those channels in a vertical list-like widget. It is not done as a real list. Just looks and behaves like if it was.

But since I'm reworking that piece of code (and its me who introduced that QAbstractListModel instead of custom structures in the first place) I'm considering making it... well... better. The issue is that channel widget is quite a complex widget. Multiple buttons, various modes (influencing what is show within) and so on. The original issue was a playlist which is basically one of three variants of what can be inside that channel widget (surrounded with other controls). Would your solution still be well usable in that case? Now it is not only lots of drawing but also lots of user interaction.

Also I already have .ui files (from previous version) covering the channel widget and all its parts. So not using that would be a waste somewhat. On a plus side we moved to Qt5 as well. Sadly QML is not much of an option (even thou I would lie to try it) mostly due to .ui files being already there.

wysota
22nd March 2013, 00:17
That is interesting! But how scalable is that?
Hard to say. For ~15k items it takes some time to perform an initial layout but then I'm not noticing any slowdowns.

Considering that there is completely no optimisation, I think the speed is satisfactory.


Since now I'm facing a much bigger thing of similar sort. We have a list (QAbstractListModel) of channels. The main window shows those channels in a vertical list-like widget. It is not done as a real list. Just looks and behaves like if it was.

But since I'm reworking that piece of code (and its me who introduced that QAbstractListModel instead of custom structures in the first place) I'm considering making it... well... better. The issue is that channel widget is quite a complex widget. Multiple buttons, various modes (influencing what is show within) and so on. The original issue was a playlist which is basically one of three variants of what can be inside that channel widget (surrounded with other controls). Would your solution still be well usable in that case?

A general answer is "probably yes" but it depends on your particular usecase. As a last resort you can always use QtQuick.


Sadly QML is not much of an option (even thou I would lie to try it) mostly due to .ui files being already there.

I don't see how that's relevant. You can place a QQuickView (or whatever it was called) in a widget (see http://blog.qt.digia.com/blog/2013/02/19/introducing-qwidgetcreatewindowcontainer/) and have your list view implemented in QtQuick.

Adam Badura
22nd March 2013, 00:48
Well, numer of items is limited, few I guess at most. Its only that each item is a complex widget by its own.

Is it possible to have an ordinary .ui based widget in a QQuickView?

wysota
22nd March 2013, 06:26
Is it possible to have an ordinary .ui based widget in a QQuickView?

No, currently not. You'd have to convert your ui design to QML. Bearing the fact that ui files are xml, you could even write a tool for it.

Adam Badura
22nd March 2013, 06:53
OK, so I guess this closes the topic at least for some time (until I will go deeper into implementation ;)).

But to finish it: how complex it would be, in your opinion, to make a class similar to Q[List|Tree]Widget that instead of keeping the model internal, would take external model and a delegate for creating item widgets? Having such a class seems useful for me since I already have the model, have the widgets and creating delegate is trivial...

Adam Badura
22nd March 2013, 12:32
But to finish it: how complex it would be, in your opinion, to make a class similar to Q[List|Tree]Widget that instead of keeping the model internal, would take external model and a delegate for creating item widgets? Having such a class seems useful for me since I already have the model, have the widgets and creating delegate is trivial...

Now when I read that I think it should be simple. MyListWidget class would take MyListModel and MyDelegate. Then it would create ItemViewWiget for each item in the model (and in general keep in sync to it) and add it to its private QListWidget.

wysota
23rd March 2013, 14:21
But to finish it: how complex it would be, in your opinion, to make a class similar to Q[List|Tree]Widget that instead of keeping the model internal, would take external model and a delegate for creating item widgets?
I'm not sure what you mean. It seems this is exactly what QListView, QTableView and QTreeView do.