2

I need to put a custom widget inside a QTableView cell from a subclassed QAbstractTableModel.

I searched for already given solutions but no one catch my needs. The custom widget must stay here all the time and not only in editing mode like with the QItemDelegate::createEditor. The custom widget may be everything, i'm searching for a general solutions for all the widget not only QPushButtons or QCheckBox.

Sorry for my english.

8
  • you can implement the method : paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const of a custom QStyledItemDelegate
    – Venom
    Commented Apr 7, 2017 at 12:38
  • yes but in this way i can just paint a static widget, i would like to have an interactive one
    – lo-re
    Commented Apr 7, 2017 at 12:41
  • Sounds very strange. What is the aim for this?
    – ilotXXI
    Commented Apr 7, 2017 at 12:46
  • I have some widget that are interactive, and i would like to show them inside the table because they interact outside the table
    – lo-re
    Commented Apr 7, 2017 at 12:56
  • 1
    @lo-re QTableWidget (not QTableView) provides such functionality but breaks MVC principles (if they are needed in this case). QGridLayout can display any widgets in table order. Isn't it enough for you?
    – ilotXXI
    Commented Apr 7, 2017 at 13:00

2 Answers 2

3

You can use QAbstractItemView::setIndexWidget and overriding QAbstractItemView::dataChanged to achieve what you want as follows:

class MyTableView : public QTableView
{
protected:
  void dataChanged(const QModelIndex &topLeft, const QModelIndex & bottomRight, const QVector<int> & roles)
  {
    QPushButton *pPushButton = qobject_cast<QPushButton*>(indexWidget(topLeft));
    if (pPushButton)
        pPushButton->setText(model()->data(topLeft, Qt::DisplayRole).toString());
    else
        QTableView::dataChanged(topLeft, bottomRight, roles);
  }
};

void main ()
{
    MyTableView table;
    table.setIndexWidget(table.model()->index(0, 0, QModelIndex()),new QPushButton());
}

Note that it is an incomplete implementation, but it should show you how you can solve your problem. A real implementation should update all QPushButton between topLeft and bottomRight.

4
  • so the solution is only to subclass the view instead of the model
    – lo-re
    Commented Apr 7, 2017 at 15:17
  • I think subclassing the view is indeed a solution for your problem, but you should try it yourself if it works well for your specific problem. I have no idea why you would subclass the model to solve this problem, because your problem is about the visualisation.
    – m7913d
    Commented Apr 7, 2017 at 15:31
  • i opted for subclass the model and paint static "widget", but your answer solved the initial problem so i set to accepted. Thanks
    – lo-re
    Commented Apr 10, 2017 at 15:25
  • You may also add your own answer and accept that one, it may be interesting to other users to view the different possibilities.
    – m7913d
    Commented Apr 10, 2017 at 15:45
0

You should subclass the QTableView and also QItemDelegate:

In the table view, you should open persistent editor for all cells in the visible area and update theme accordingly:

table-view.h

#ifndef TABLE_VIEW_H
#define TABLE_VIEW_H

#include <QTableView>

class TableView : public QTableView
{
    Q_OBJECT

public:
    TableView(QWidget* parent = nullptr);

    void setModel(QAbstractItemModel* model) override;
    void scrollContentsBy(int dx, int dy) override;
    void scrollTo(const QModelIndex& index, ScrollHint hint) override;

protected slots:
    void resizeEvent(QResizeEvent* event) override;
    void dataChanged(const QModelIndex&  topLeft,
                     const QModelIndex&  bottomRight,
                     const QVector<int>& roles) override;
    void rowsInserted(const QModelIndex& parent, int start, int end) override;

private:
    void showAllVisiblePersistentEditors();
    QModelIndexList m_editor_list;
};

#endif // TABLE_VIEW_H

table-view.cpp

TableView::TableView(QWidget* parent) :
    QTableView(parent)
{
    setItemDelegate(new Delegate);
}

void TableView::setModel(QAbstractItemModel* model)
{
    QTableView::setModel(model);
    executeDelayedItemsLayout();
    showAllVisiblePersistentEditors();
}

void TableView::scrollContentsBy(int dx, int dy)
{
    QTableView::scrollContentsBy(dx, dy);
    showAllVisiblePersistentEditors();
}

void TableView::scrollTo(const QModelIndex& index, ScrollHint hint)
{
    QTableView::scrollTo(index, hint);
    showAllVisiblePersistentEditors();
}

void TableView::resizeEvent(QResizeEvent* event)
{
    QTableView::resizeEvent(event);
    executeDelayedItemsLayout();
    showAllVisiblePersistentEditors();
}

void TableView::dataChanged(const QModelIndex&  topLeft,
                            const QModelIndex&  bottomRight,
                            const QVector<int>& roles)
{
    QTableView::dataChanged(topLeft, bottomRight, roles);
    showAllVisiblePersistentEditors();
}

void TableView::rowsInserted(const QModelIndex& parent, int start, int end)
{
    QTableView::rowsInserted(parent, start, end);
    showAllVisiblePersistentEditors();
}

void TableView::showAllVisiblePersistentEditors()
{
    const auto* model { this->model() };

    if (model) {
        const auto rect { viewport()->rect() };

        const auto first_index { indexAt(rect.topLeft()) };

        const auto valid_bottom_right {
            visualRect(model->index(model->rowCount() - 1, model->columnCount() - 1))
            .bottomRight() };
        const auto bottom_right { rect.bottomRight() };
        const auto last_index { indexAt({ std::min(bottom_right.x(), valid_bottom_right.x()),
                                          std::min(bottom_right.y(), valid_bottom_right.y()) }) };

        const auto r1 { first_index.row() };
        const auto r2 { last_index.row() };
        const auto c1 { first_index.column() };
        const auto c2 { last_index.column() };

        QModelIndex index;

        const auto editor_count { m_editor_list.size() };
        for (auto i { editor_count - 1 }; i >= 0; --i) {
            index = qAsConst(m_editor_list)[i];
            if (!visualRect(index).intersects(rect)) {
                closePersistentEditor(index);
                m_editor_list.removeLast();
            }
        }

        for (auto r { r1 }; r <= r2; ++r) {
            for (auto c { c1 }; c <= c2; ++c) {
                index = model->index(r, c);
                if (index.flags().testFlag(Qt::ItemIsEditable) &&
                    visualRect(index).intersects(rect) &&
                    !isPersistentEditorOpen(index))
                {
                    m_editor_list.push_back(index);
                    openPersistentEditor(index);
                }
            }
        }
    }
}

In the delegate you can provide any widget you like for the editor:

delegate.h

#ifndef DELEGATE_H
#define DELEGATE_H

#include <QAbstractItemDelegate>

class Delegate : public QAbstractItemDelegate
{
    Q_OBJECT

public:
    Delegate(QObject* parent = nullptr);

    void paint(QPainter*                   painter,
               const QStyleOptionViewItem& option,
               const QModelIndex&          index) const override;

    QSize sizeHint(const QStyleOptionViewItem& option,
                   const QModelIndex&          index) const override;

    QWidget* createEditor(QWidget*                    parent,
                          const QStyleOptionViewItem& option,
                          const QModelIndex&          index) const override;

    void updateEditorGeometry(QWidget*                    editor,
                              const QStyleOptionViewItem& option,
                              const QModelIndex&          index) const override;
};

#endif // DELEGATE_H

delegate.cpp

#include "delegate.h"

#include <QPushButton>

Delegate::Delegate(QObject* parent) :
    QAbstractItemDelegate(parent)
{}

void Delegate::paint(QPainter*                   painter,
                     const QStyleOptionViewItem& option,
                     const QModelIndex&          index) const
{
    Q_UNUSED(painter)
    Q_UNUSED(option)
    Q_UNUSED(index)
}

QSize Delegate::sizeHint(const QStyleOptionViewItem& option,
                         const QModelIndex&          index) const
{
    Q_UNUSED(index)
    return option.decorationSize;
}

QWidget* Delegate::createEditor(QWidget*                    parent,
                                const QStyleOptionViewItem& option,
                                const QModelIndex&          index) const
{
    Q_UNUSED(option)
    Q_UNUSED(index)
    return new QPushButton(parent);
}

void Delegate::updateEditorGeometry(QWidget*                    editor,
                                    const QStyleOptionViewItem& option,
                                    const QModelIndex&          index) const
{
    Q_UNUSED(index)
    editor->setGeometry(option.rect);
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.