1

I'm new to Qt framework. In new application I want to create list with customised items. These items are quite simple and must contain title label, thumbnail and description label (picture here)

For now I don't want to play with custom drawing and all that stuff becuase I think it's easier to do items I want with proper widget/layout so I decided to use QListwidget and subclassed QAbstractItemModel (KeyframeModel) and QListWidgetItem (TileWidgetItem).

After some coding it looks how I wanted but strange thing happens to QListWidget (grid mode) when I add some items. In my case a QListWidget is resizable (due to how it's embedded inside main layout) and number of columns should be dependent on list and items width. Items are fixed size (at least for now). But when I resize the list one of the items at some list's width is misaligned and not I don't know what's going on. Below are te screenshots from app:

Pic. 1 List initial state (right after start)

Pic. 2 List after resizing #1

Pic. 3 List after resizing #2

Resizing #2 is a few pixels wider than resizing #1 and resizing #1 is hard to get (border case) - few pixels width less and I've got 2 columns (it's okay) but some pixels more and I end up with case #2. In all cases number of columns is okay.

Sometimes also last item is misaligned after program start right-away like here (right after start like in pic. 1 but as you can see different result despite same list width). I wonder why is it so inconsistent after start-up.

Do I missing something? Do I must do some parts in different way? Or is it just some glitches in debug mode?

Below I post some code:

Application:

// Source file 

QtGuiApplication1::QtGuiApplication1(QWidget *parent) 
    : QMainWindow(parent)
{
    ui.setupUi(this);

    //--------------------------------------------------------------------------------
    // Add elements to the list

    TileWidgetItem *item = new TileWidgetItem();
    item->setData(TileWidgetItem::TileRole::TitleRole, QVariant("Long title"));
    item->setData(TileWidgetItem::TileRole::DescriptionRole, QVariant("My long info"));
    item->setText("My super text");
    qDebug() << "Widget size hint: " << item->sizeHint();

    ui.listWidget_moves->addItem(item);
    item->updateView();

    TileWidgetItem *item1 = new TileWidgetItem();
    item1->setData(TileWidgetItem::TileRole::TitleRole, QVariant("Item #2"));
    item1->setText("Tile #2");
    ui.listWidget_moves->addItem(item1);
    item1->updateView();

    TileWidgetItem *item2 = new TileWidgetItem();
    ui.listWidget_moves->addItem(item2);
    item2->updateView();

    TileWidgetItem *item3 = new TileWidgetItem();
    ui.listWidget_moves->addItem(item3);
    item3->updateView();

    //--------------------------------------------------------------------------------
    // Adjust cell size

    QSize cellSize;

    for (uint i = 0; i < ui.listWidget_moves->count(); i++)
    {
        int dim = ui.listWidget_moves->item(i)->sizeHint().height();

        if (dim > cellSize.height())
            cellSize.setHeight(dim);

        dim = ui.listWidget_moves->item(i)->sizeHint().width();

        if (dim > cellSize.width())
            cellSize.setWidth(dim);
    }

    ui.listWidget_moves->setGridSize(cellSize);
}

Item widget:

// Source file

constexpr int MAX_THUMB_SIZE = 100;

TileWidgetItem::TileWidgetItem(QListWidget *listview)
    : QListWidgetItem(listview, ItemType::UserType)
{
    /* Prepare main widget */

    QWidget *view = new QWidget();
    view->setObjectName("tile");
    view->setStyleSheet(
        "QWidget#tile { margin: 4 8; background-color: #404040; border: 1 solid rgba(0,0,0,30%); border-radius: 4px }\n"
        "QWidget#tile::hover { border: 1 solid #EEE; background-color: #484848 }\n"
        "QWidget#tile[selected=true] { background-color: #00F }"
    );
    
    //-----------------------------------------------------------
    /* Prepare layout */

    QVBoxLayout *layout = new QVBoxLayout();
    layout->setSizeConstraint(QLayout::SizeConstraint::SetFixedSize);

    //-----------------------------------------------------------
    /* Prepare title with icon */

    QHBoxLayout *titleLayout = new QHBoxLayout();

    QLabel *titleIcon = new QLabel();
    titleIcon->setObjectName("titleIcon");
    titleIcon->setStyleSheet("background-color: black");
    titleIcon->setFixedSize(QSize(16, 16));
    titleLayout->addWidget(titleIcon);

    QLabel *title = new QLabel("Title");
    title->setObjectName("title");
    title->setMinimumWidth(60);
    title->setStyleSheet("background-color: #800;");
    titleLayout->addWidget(title);
    
    QWidget *titleWidget = new QWidget();
    titleWidget->setStyleSheet("background-color: #080");
    titleWidget->setLayout(titleLayout);

    layout->addWidget(titleWidget);

    //-----------------------------------------------------------
    /* Prepare thumbnail */
    
    QLabel *thumbnail = new QLabel();
    thumbnail->setObjectName("thumbnail");
    thumbnail->setStyleSheet("background-color: black; border: 1 solid #F00");
    thumbnail->setFixedSize(QSize(MAX_THUMB_SIZE, MAX_THUMB_SIZE * 0.7f));
    thumbnail->setPixmap(QPixmap("Resources/moto.jpg").scaledToWidth(MAX_THUMB_SIZE));
    layout->addWidget(thumbnail);
    
    //-----------------------------------------------------------
    /* Preparing additional info */

    QLabel *description = new QLabel("Description");
    description->setObjectName("description");
    //description->setToolTip("Custom info tip");
    description->setContentsMargins(4, 2, 4, 2);
    layout->addWidget(description);

    //-----------------------------------------------------------

    view->setLayout(layout);

    _customView = view;
    _titleView = title;
    _descriptionView = description;

    setSizeHint(_customView->sizeHint());

    updateView();
}

TileWidgetItem::~TileWidgetItem()
{
}

void TileWidgetItem::setData(int role, const QVariant &value)
{
    QListWidgetItem::setData(role, value);

    if (value.type() == QVariant::Type::String)
    {
        if (role == TileRole::TitleRole)
        {
            this->_titleView->setText(value.toString());
        }
        else if (role == TileRole::DescriptionRole)
        {
            this->_descriptionView->setText(value.toString());
        }

        setSizeHint(_customView->sizeHint());
    }
}

void TileWidgetItem::updateView()
{
    if (listWidget() != nullptr)
    {
        listWidget()->setItemWidget(this, this->_customView);
    }
}
// Header file

class TileWidgetItem : public QListWidgetItem
{
public:
    enum TileRole
    {
        TitleRole = Qt::UserRole + 1,
        DescriptionRole,
        ThumbnailRole
    };

public:
    TileWidgetItem(QListWidget *listview = nullptr);
    ~TileWidgetItem();

    void setData(int role, const QVariant &value) override;
    void updateView();

    QWidget *customView() const { return _customView; };

    QString getTitle() const { return _titleView->text(); };

    QString getInfo() const { return _descriptionView->text(); };

private:
    QWidget *_customView;
    QLabel *_titleView;
    QLabel *_descriptionView;
};

Platform: Windows 10
Qt version: 5.14.2
IDE: Visual Studio 2019 (with Qt VS Tools)

  • welcome to stackoverflow, please read https://stackoverflow.com/help/how-to-ask – Dean Taler Aug 17 '20 at 12:53
  • Especially provide a [example] – Thrasher Aug 17 '20 at 13:01
  • I would not do: `QWidget *view = new QWidget();` – scopchanov Aug 17 '20 at 16:12
  • @scopchanov Any explanation why? In many Stacks posts it's viewed badly not to give proper explanation to tips/arguments. Nevertheless for now I focus on running few basic functions that will make it to main program so it's not the time for optimizing – Kamil Szepietowski Aug 17 '20 at 19:29
  • It's not an optimisation issue, but a matter of memory management. You create your `QWidget` object without a parent, so noone, neither you (at least not in the code you have provided), nor Qt, is taking care of deleting it. This is an obvoius source of memory leak. You can't make it a child of `TileWidgetItem` though, since it inherits from `QListWidgetItem` and the later is intentionally left out of the `QObject` tree, namely for perfomance reasons. In short, without trying to discourage you, I believe you should rethink your approach and use delegates, as intended by the creators of Qt. – scopchanov Aug 17 '20 at 19:49
  • For a further explanation and tips, please read [this answer](https://stackoverflow.com/a/45386801/5366641). Please take a special note on _You're not supposed to create widgets and put them in a model._ – scopchanov Aug 17 '20 at 20:00
  • @scopchanov Ok, thanks for tips. Also my bad to mix code with 2 different approach. In "keyframe" list I tried model and in "moves" list I tries QWidgetList. For now I test only QWidgetList and putting QWidgetItems into it (which as I understand storage data). I'm aware of not putting widget objects to any data model. I'll edit post to remove unnecessary code to avoid confusion. – Kamil Szepietowski Aug 18 '20 at 06:58
  • Too bad that Qt approach to make list with custom items kinda sucks in compare to, for example, Android and its RecyclerView (model based with ease of utilising custom layouts without need to drawing them manually in class). – Kamil Szepietowski Aug 18 '20 at 07:02
  • 1
    There's no `QWidgetList`, but a `QListWidget`. This means, that not widgets are listed, but it is a widget, which lists items. There is a fundamental difference between those two. Widgets are using layouts to auto-size and position their children, which is convinient, but they also participate in the event loop and put a load on it, which makes the app's perfomance deteriorate rapidly. That is what the items are for. They're lightweight objects, which in contrast to widgets use delegates to display their contents. They aren't meant to be loaded with widgets, cause that'll defeat their purpose. – scopchanov Aug 18 '20 at 09:03
  • @scopchanov Yeah, good point. I'm digging a little bit step by step into it and I starting to see how it works. What I used is only a static work around. SetItemWidget() function can display widget in place of draw item but in reality it is not supposed to work as I wanted in long term due performance problems which I was aware of from the start that could be a problem in one way or another. Nevertheless looks like I must stick with model/view approach and create custom delegate :/ – Kamil Szepietowski Aug 18 '20 at 09:23
  • 1
    _I must stick with model/view approach and create custom delegate_ I believe this is the proper way to achieve what you want. – scopchanov Aug 18 '20 at 09:33

1 Answers1

0

In the end I just used custom delegates which solved problems.

I wanted to overuse system and in I was defeated :)