QListView only showing a single item in the view
-
I am utilizing the Model View Delegate framework used by Qt for displaying lists of objects with custom 'views' or layouts.
Background:
I require showing a country flag, country name, city name and an optional 'premium' rating star in a list, which can be selected by a user.
To achieve this, I use the following components:
-
Model - a
QStandardItemModel
, see doc page, which holds all the data of theQListView
and interaction concerns, doc page -
Delegate - For drawing/displaying the data in a custom layout fashion, I use a
QStyledItemDelegate
, see doc page. -
View - For the view, a simple
QListView
will suffice, as this is a single column list containing a collection of objects with a custom layout.
Tutorials and Help:
Using the following tutorial(s),
- a messageviewer application showing how to implement a detailed
model-delegate-view
concept in accordance with, - the basics of a simple messaging menu system for Nokia smartphones, I have been able to create, with relative ease, the desired layout for my
QListView
.
Problem:
I am required to add
QStandardItem
items to my model, which will be added to my view. I do so, and it is confirmed by the delegate where each item is drawn by thepaint
override method.However, during runtime, the
QListView
only displays 1 item. But I can use my up/down arrow keys to select various other items in the list.Please see code below:
Setting up MVC(delegate):
mainwindow.h
//...
QStandardItemModel modelServers;
ServerDelegate serverDelegate;
//...mainwindow.cpp
modelServers = new QStandardItemModel(0); serverDelegate = new ServerDelegate(0); ui->listServers->setModel(modelServers); ui->listServers->setItemDelegate(serverDelegate);
and adding items (
QStandardItem
's) to list:for (int i = 0; i < someList->length(); ++i) { Server server = someList->value(i); QStandardItem *item = new QStandardItem(); item->setData(server.getCountryName, item->setData(QPixmap(Global::getCountryFlagFromCache(v.country)), ServerDelegate::DataRole::CountryFlag); item->setData(server.getCountry(), ServerDelegate::DataRole::CountryText); item->setData(server.getCity(), ServerDelegate::DataRole::CityText); item->setData(i, ServerDelegate::ListIndex); //... modelServer->appendRow(item) }
The finally the list displays the data, however the list is only populated with items, but only the first has visible text and images.
note: when scrolling down, only the top item is visible, see images below for an example.
e.g.
Initial Loaded list:
One scroll down
Selecting an item with no text/images:
Additional Code below:
ServerDelegate
class handling the custom layoutServerDelegate.h
#ifndef SERVERDELEGATE_H #define SERVERDELEGATE_H #include <QApplication> #include <QtGui> #include <QStyledItemDelegate> #include <QtWidgets> #include <qglobal.h> #include "global.h" class ServerDelegate : public QStyledItemDelegate { Q_OBJECT public: ServerDelegate(QStyledItemDelegate* parent = 0); virtual ~ServerDelegate(); enum DataRole{ CountryText = Qt::UserRole + 100, CityText = Qt::UserRole+101, CountryFlag = Qt::UserRole+102, SideIconFlag = Qt::UserRole+103, ListIndex = Qt::UserRole+105 }; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; private: QFont fontCountry, fontCity; }; #endif // SERVERDELEGATE_H
ServerDelegate.cpp
#include "serverdelegate.h" ServerDelegate::ServerDelegate(QStyledItemDelegate *parent) : QStyledItemDelegate(parent) { fontCountry = QApplication::font(); fontCountry.setBold(true); fontCountry.setPointSize(QApplication::font().pointSize() + 3); fontCity = QApplication::font(); fontCity.setItalic(true); fontCity.setPointSize(QApplication::font().pointSize() - 1); } ServerDelegate::~ServerDelegate(){ } QSize ServerDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const{ Q_UNUSED(index) QSize totalCountrySize = Global::getCountryFlagFromCache(index.data(DataRole::CountryText).toString()).size(); QSize totalSideIcon = QPixmap(":/res/images/premium", "PNG").size(); QFontMetrics fmCountry(fontCountry); QFontMetrics fmCity(fontCity); int fontHeight = (2 * AppGlobal::Style_List_Seperator_Width) + (2 * AppGlobal::Style_List_Text_Item_Margin) + fmCountry.height() + fmCity.height(); int iconHeight = (2 * AppGlobal::Style_List_Seperator_Width) + (totalCountrySize.height() > totalSideIcon.height() ? totalCountrySize.height() : totalSideIcon.height()); int height = (fontHeight > iconHeight) ? fontHeight : iconHeight; int width = option.rect.width(); QSize size = QSize(width, height); return size; } void ServerDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const{ QStyledItemDelegate::paint(painter, option, index); QRect rec = option.rect; painter->save(); painter->setClipRect(rec); QString countryText = index.data(DataRole::CountryText).toString(); QString cityText = index.data(DataRole::CityText).toString(); QPixmap countryFlag = QPixmap(qvariant_cast<QPixmap>(index.data(DataRole::CountryFlag))); QPixmap sideIcon = qvariant_cast<QPixmap>(index.data(DataRole::SideIconFlag)); // Get a rectangle by size x, y. // With cooridinates [0,0]; [x,0]; [x,y]; [0,y] QRect topLine = option.rect, bottomLine = option.rect; // create top 'seperator' of X px's width, green in color; topLine.setTop(0); topLine.setLeft(0); topLine.setRight(option.rect.width()); topLine.setBottom(AppGlobal::Style_List_Seperator_Width); // 1px down painter->setPen(AppGlobal::Style_List_Seperator_Color); painter->fillRect(topLine, AppGlobal::Style_List_Seperator_Color); painter->drawRect(topLine); // create bottom 'seperator' of X px's width, green in color; bottomLine.setTop(option.rect.height() - AppGlobal::Style_List_Seperator_Width); bottomLine.setLeft(0); bottomLine.setRight(option.rect.width()); bottomLine.setBottom(option.rect.height()); // 1px down painter->setPen(AppGlobal::Style_List_Seperator_Color); painter->fillRect(bottomLine, AppGlobal::Style_List_Seperator_Color); painter->drawRect(bottomLine); // create background rectangle QRect content(0, topLine.bottom(), option.rect.width(), bottomLine.top()); painter->setPen(AppGlobal::Style_List_Background_Color); painter->fillRect(content, AppGlobal::Style_List_Background_Color); painter->drawRect(content); // create content rectangles from content container. QRect rectCountryFlag = content, rectSideIcon = content; // create country icon rectangle QSize countryFlagSize = countryFlag.size(); int cFPos = (rectCountryFlag.height() / 2) - (countryFlagSize.height() / 2) - 8; rectCountryFlag.setTop(cFPos); rectCountryFlag.setBottom(content.height() - cFPos); rectCountryFlag.setLeft(AppGlobal::Style_List_Left_Item_Margin - 8); rectCountryFlag.setRight(AppGlobal::Style_List_Left_Item_Margin + 16 + countryFlagSize.width()); painter->drawPixmap(rectCountryFlag, countryFlag); // create side icon rectangle QSize sideIconSize = sideIcon.size(); int siPos = (rectSideIcon.height() / 2) - (sideIconSize.height() / 2) - 4; rectSideIcon.setTop(siPos); rectSideIcon.setBottom(content.height() - siPos); rectSideIcon.setLeft(rec.width() - (AppGlobal::Style_List_Right_Item_Margin + 8 + sideIconSize.width())); rectSideIcon.setRight(rec.width() - AppGlobal::Style_List_Right_Item_Margin); painter->drawPixmap(rectSideIcon, sideIcon); const QRect textContent(rectCountryFlag.right() + AppGlobal::Style_List_Text_Item_Margin + AppGlobal::Style_List_Left_Item_Margin, content.top() + AppGlobal::Style_List_Text_Item_Margin, rectSideIcon.left() - AppGlobal::Style_List_Text_Item_Margin, content.bottom() - AppGlobal::Style_List_Text_Item_Margin); // create country text rectangle QRect rectCountryText = content, rectCityText = content; rectCountryText.setLeft(textContent.left()); rectCountryText.setTop(textContent.top()); rectCountryText.setRight(textContent.right()); rectCountryText.setBottom(qRound(textContent.height() * 0.6) - AppGlobal::Style_List_Text_Item_Margin); painter->setPen(AppGlobal::Style_Heading_Color); painter->setFont(fontCountry); painter->drawText(rectCountryText, countryText); // create city text rectangle rectCityText.setLeft(textContent.left() + ( 2 * AppGlobal::Style_List_Text_Item_Margin)); rectCityText.setTop(rectCountryText.bottom() + AppGlobal::Style_List_Text_Item_Margin); rectCityText.setRight(textContent.right()); rectCityText.setBottom(textContent.height()); painter->setPen(AppGlobal::Style_SubText_Color); painter->setFont(fontCity); painter->drawText(rectCityText, cityText); // restore painter painter->restore(); }
-
-
Hi,
What version of Qt are you using ?
On what platform ? -
@SGaist
Platform - Linux x86_64
Qt Version - 5.9.3@JNBarchan
fontHeight produced a integer value +/- 56
iconHeight produced an integer value ( < fontHeight)@VRonin
TheRole
's where not the problem. Please see my updated delegate below (kudos for calling it, the delegate being the problem)
Well this is embarrasing. I did not fully realize what I was coding until I had battled with the delegate all night.
I understood that I was coding a template, and assumed that on each
item
which was added, the origin remained at [0,0]. This was not the case as there was an offset added of value specified in thesizeHint
(in my case,56
). Thus all myQListView
'sQStandardItem
's were in fact there, but drawn over each other.After changing values and experimenting over a long period (a number of hours), I finally came to the desired result, shown below.
Also, I need to thank scopchanov for his hint into this
offset
problemStackoverFlow question as a reference
Updated ServerDelegate.cpp
#include "serverdelegate.h" ServerDelegate::ServerDelegate(QStyledItemDelegate *parent) : QStyledItemDelegate(parent) { fontCountry = QApplication::font(); fontCountry.setBold(true); fontCountry.setPointSize(QApplication::font().pointSize() + 3); fontCity = QApplication::font(); fontCity.setItalic(true); fontCity.setPointSize(QApplication::font().pointSize() - 1); } ServerDelegate::~ServerDelegate(){ } QSize ServerDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const{ Q_UNUSED(index) QSize totalCountrySize = Global::getCountryFlagFromCache(index.data(DataRole::CountryText).toString()).size(); QSize totalSideIcon = QPixmap(":/res/images/premium", "PNG").size(); QFontMetrics fmCountry(fontCountry); QFontMetrics fmCity(fontCity); int fontHeight = (2 * AppGlobal::Style_List_Seperator_Width) + (2 * AppGlobal::Style_List_Text_Item_Margin) + fmCountry.height() + fmCity.height(); int iconHeight = (2 * AppGlobal::Style_List_Seperator_Width) + (totalCountrySize.height() > totalSideIcon.height() ? totalCountrySize.height() : totalSideIcon.height()); int height = (fontHeight > iconHeight) ? fontHeight : iconHeight; int width = option.rect.width(); QSize size = QSize(width, height); return size; } void ServerDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const{ QStyledItemDelegate::paint(painter, option, index); QFontMetrics fmCountry(fontCountry); QFontMetrics fmCity(fontCity); QRect rec = option.rect; painter->save(); painter->setClipRect(rec); QString countryText = index.data(DataRole::CountryText).toString(); QString cityText = index.data(DataRole::CityText).toString(); QPixmap countryFlag = QPixmap(qvariant_cast<QPixmap>(index.data(DataRole::CountryFlag))); QPixmap sideIcon = qvariant_cast<QPixmap>(index.data(DataRole::SideIconFlag)); // Get a rectangle by size x, y. // With cooridinates [0,0]; [x,0]; [x,y]; [0,y] QRect topLine = option.rect, bottomLine = option.rect; // create top 'seperator' of X px's width, green in color; topLine.setTop(rec.top()); topLine.setLeft(rec.left()); topLine.setRight(rec.right()); topLine.setBottom(rec.top() + AppGlobal::Style_List_Seperator_Width); // 1px down painter->setPen(AppGlobal::Style_List_Seperator_Color); painter->fillRect(topLine, AppGlobal::Style_List_Seperator_Color); painter->drawRect(topLine); // create bottom 'seperator' of X px's width, green in color; bottomLine.setTop(rec.bottom() - AppGlobal::Style_List_Seperator_Width); bottomLine.setLeft(rec.left()); bottomLine.setRight(rec.right()); bottomLine.setBottom(rec.bottom()); // 1px down painter->setPen(AppGlobal::Style_List_Seperator_Color); painter->fillRect(bottomLine, AppGlobal::Style_List_Seperator_Color); painter->drawRect(bottomLine); // create background rectangle QRect content(rec.left(), topLine.bottom(), (rec.right() - rec.left()), (bottomLine.top() - topLine.bottom())); painter->setPen(AppGlobal::Style_List_Background_Color); painter->fillRect(content, ((option.state & QStyle::State_MouseOver) ? AppGlobal::Style_List_Hover_Color : AppGlobal::Style_List_Background_Color )); painter->drawRect(content); // create content rectangles from content container. QRect rectCountryFlag = content, rectSideIcon = content; // create country icon rectangle QSize countryFlagSize = countryFlag.size(); int cFPos = ((rectCountryFlag.bottom() - rectCountryFlag.top()) / 2) - (countryFlagSize.height() / 2) - 8; rectCountryFlag.setTop(rectCountryFlag.top() + cFPos); rectCountryFlag.setBottom(content.bottom() - cFPos); rectCountryFlag.setLeft(AppGlobal::Style_List_Left_Item_Margin - 8); rectCountryFlag.setRight(AppGlobal::Style_List_Left_Item_Margin + 16 + countryFlagSize.width()); painter->drawPixmap(rectCountryFlag, countryFlag); // create side icon rectangle QSize sideIconSize = sideIcon.size(); int siPos = ((rectSideIcon.bottom() - rectSideIcon.top()) / 2) - (sideIconSize.height() / 2) - 4; rectSideIcon.setTop(rectSideIcon.top() + siPos); rectSideIcon.setBottom(content.bottom() - siPos); rectSideIcon.setLeft(rec.width() - (AppGlobal::Style_List_Right_Item_Margin + 8 + sideIconSize.width())); rectSideIcon.setRight(rec.width() - AppGlobal::Style_List_Right_Item_Margin); painter->drawPixmap(rectSideIcon, sideIcon); int textContentLeft = rectCountryFlag.right() + AppGlobal::Style_List_Text_Item_Margin + AppGlobal::Style_List_Left_Item_Margin, textContentTop = content.top() + AppGlobal::Style_List_Text_Item_Margin; const QRect textContent( textContentLeft , textContentTop, (rectSideIcon.left() - AppGlobal::Style_List_Text_Item_Margin) - textContentLeft, (content.bottom() - AppGlobal::Style_List_Text_Item_Margin) - textContentTop); // create country text rectangle QRect rectCountryText = content, rectCityText = content; rectCountryText.setLeft(textContent.left()); rectCountryText.setTop(textContent.top()); rectCountryText.setRight(textContent.right()); rectCountryText.setBottom(textContent.top() + fmCountry.height()); painter->setPen(AppGlobal::Style_Heading_Color); painter->setFont(fontCountry); painter->drawText(rectCountryText, countryText); // create city text rectangle rectCityText.setLeft(textContent.left() + ( 2 * AppGlobal::Style_List_Text_Item_Margin)); rectCityText.setTop(rectCountryText.bottom()); rectCityText.setRight(textContent.right()); rectCityText.setBottom(textContent.bottom() + fmCity.height()); painter->setPen(AppGlobal::Style_SubText_Color); painter->setFont(fontCity); painter->drawText(rectCityText, cityText); // restore painter painter->restore(); }