QListView/QListWidget with custom items and custom item widgets
Asked Answered
C

5

39

I'm writing a PyQt application and am having some trouble creating a custom list view. I'd like the list to contain arbitrary widgets (one custom widget in particular). How would I go about this?

It seems that the alternative would be to create a table or grid view wrapped in a scrollbar. However, I'd like to be able to take advantage of the model/view approach as well as the nesting (tree-view) support the built-ins handle.

To clarify, the custom widgets are interactive (contain buttons), so the solution requires more than painting a widget.

Creech answered 4/6, 2009 at 3:24 Comment(0)
K
32

I think you need to subclass QItemDelegate.

QItemDelegate can be used to provide custom display features and editor widgets for item views based on QAbstractItemView subclasses. Using a delegate for this purpose allows the display and editing mechanisms to be customized and developed independently from the model and view.

This code is taken from Qt's examples, the torrent application.

class TorrentViewDelegate : public QItemDelegate
{
    Q_OBJECT
public:
    inline TorrentViewDelegate(MainWindow *mainWindow) : QItemDelegate(mainWindow) {}

    inline void paint(QPainter *painter, const QStyleOptionViewItem &option,
                      const QModelIndex &index ) const
    {
        if (index.column() != 2) {
            QItemDelegate::paint(painter, option, index);
            return;
        }

        // Set up a QStyleOptionProgressBar to precisely mimic the
        // environment of a progress bar.
        QStyleOptionProgressBar progressBarOption;
        progressBarOption.state = QStyle::State_Enabled;
        progressBarOption.direction = QApplication::layoutDirection();
        progressBarOption.rect = option.rect;
        progressBarOption.fontMetrics = QApplication::fontMetrics();
        progressBarOption.minimum = 0;
        progressBarOption.maximum = 100;
        progressBarOption.textAlignment = Qt::AlignCenter;
        progressBarOption.textVisible = true;

        // Set the progress and text values of the style option.
        int progress = qobject_cast<MainWindow *>(parent())->clientForRow(index.row())->progress();
        progressBarOption.progress = progress < 0 ? 0 : progress;
        progressBarOption.text = QString().sprintf("%d%%", progressBarOption.progress);

        // Draw the progress bar onto the view.
        QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);
    }
};

Basically as you can see it checks if the column to be painted is of a specific index, if so it paints a progress bar. I think you could tweak it a little and instead of using a QStyleOption you could use your own widget.

edit: don't forget to setup your item delegate with your QListView using setItemDelegate.

While investigating your question I've stumbled upon this thread, which elaborates how to paint a custom widget using a QItemDelegate, I believe it has all the info you might need.

Kolomna answered 4/6, 2009 at 7:38 Comment(6)
Thank you for the post, I'll look into it more tomorrow. Could you elaborate on how I'd go about replacing the QStyleOption drawing with a custom widget?Creech
well it's just a matter of painting your widget with the QPainter provided in the QItemDelegate's paint method. I've edited my post to include a useful link.Kolomna
Well, I can get something rough working using this technique. But the issue is the "arbitrary widget" aspect - I've got a compound widget that contains buttons, etc, that are interactive - so they need to be physically placed in the list or the user isn't capable of clicking on them. correct me if I'm wrong, but this will only draw an inactive widget.Creech
that's true. but that's why you have "edit mode", that's when the list should be interactive, not when it's displaying static data.Kolomna
well not that I need to defend myself, but I have reasons for wanting to do it this way. the controls aren't to edit the data, but to spawn actions based on it. there are many examples of good UIs doing this... open the "Downloads" window in your browser.Creech
Shouldn't the constructor take and pass a QObject?Baby
E
22

If I understand your question correctly, you want something like this:

enter image description here

where each row contains a custom set of widgets.

To achieve this, two steps.

Implement the row with a custom Widget

First, implement a custom widget that contains all the widgets needed per list row.

Here I am using a label and two buttons per row, with an horizontal layout.

class MyCustomWidget(QWidget):
    def __init__(self, name, parent=None):
        super(MyCustomWidget, self).__init__(parent)

        self.row = QHBoxLayout()

        self.row.addWidget(QLabel(name))
        self.row.addWidget(QPushButton("view"))
        self.row.addWidget(QPushButton("select"))

        self.setLayout(self.row)

Add rows to the list

Instanciating multiple rows is then just a matter of creating a widget item, and associate the custom widget to the item's row.

# Create the list
mylist = QListWidget()

# Add to list a new item (item is simply an entry in your list)
item = QListWidgetItem(mylist)
mylist.addItem(item)

# Instanciate a custom widget 
row = MyCustomWidget()
item.setSizeHint(row.minimumSizeHint())

# Associate the custom widget to the list entry
mylist.setItemWidget(item, row)
Ergo answered 14/3, 2018 at 8:34 Comment(1)
This breaks if I set Movement.Free and try to rearrange items.Sergu
S
5

@Idan's answer works well, but I'll post a simpler example I came up with. This item delegate just draws a black rectangle for each item.

class ItemDelegate : public QItemDelegate
{
public:
    explicit ItemDelegate(QObject *parent = 0) : QItemDelegate(parent) {}
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
    {
        painter->fillRect(option.rect.adjusted(1, 1, -1, -1), Qt::SolidPattern);
    }
};

And then you just need to set it for the list widget:

ui->listWidget->setItemDelegate(new ItemDelegate(ui->listWidget));
Spelunker answered 12/3, 2014 at 10:39 Comment(0)
S
2

Assist says, that:

void QTableWidget::setCellWidget (int row, int column, QWidget * widget)  

Sets the given widget to be displayed in the cell in the given row and column, passing the ownership of the widget to the table. If cell widget A is replaced with cell widget B, cell widget A will be deleted.

And there is analogs to this method in the most of QAbstractItemView descendants.

You have to subclass Q***Delegate only when you want editor widget to appear in View only when you hit EditTrigger, then vanish and let delegate render the view item in some way.

If I correct, you wanted to see control in item view all the time and be able to hit controls without the need to enter editing mode and wait while delegate creates editor and set its state, so you do not need to make specific delegate, just set widget into the view's item.

Showoff answered 19/3, 2010 at 10:48 Comment(2)
Unfortunately such functions are only available for QTableWidget, QTreeWidget and QListWidget. They are not available when using a view with a custom model.Mothy
From the docs "This function should only be used to display static content in the place of a list widget item. If you want to display custom dynamic content or implement a custom editor widget, use QListView and subclass QItemDelegate instead."Tempestuous
D
1

another way you can add custom items in listWidget. for this purpose you need to add new class. name it as you want i just give it "myList" i hope you know how to add new items in project. :)

then in this new frame add your control as many as u want. like QLabels,QlineEdit etc

then in main class u have to add a listWidget name list or as you want and the write the following code

MyList *myList = new MyList();
QListWidgetItem *item = new QListWidgetItem();
ui->list->insertItem(ui->list->size().height(),item);
item->setSizeHint(QSize(50,30));
ui->list->setItemWidget(item,myList);

latter u can also change items properties using signals/slots ..

hope its help you ..!

Disadvantageous answered 1/4, 2013 at 12:11 Comment(2)
Who delete myList on item destroy? I can't see any ownership related code in this sample.Indefatigable
Well, I am java developer and JAVA take cares of garbage collection :) that was my little knowledge of C++Disadvantageous

© 2022 - 2024 — McMap. All rights reserved.