views:

3994

answers:

2

I'm writing my first Qt application with PyQt 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.

EDIT: To clarify, this widget is interactive (contains buttons), so the solution requires more than painting a widget.

+7  A: 

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.

Idan K
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?
Daniel
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.
Idan K
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.
Daniel
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.
Idan K
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.
Daniel
A: 

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.

Max