Table with large amount of widgets ( 100'000 + )
-
I am new in qt and maybe there is something that I don't see, I would be very grateful for the help.
I have plots with a lot of dots (maybe 100'000 or more) and want to create edit table to it(with 3 widgets qlabel, spinbox and button). There is problem which class I need to use:- QTableWidget doesn't work with such amount of rows(because it creates separate QWidgets for every rows and it would thousand of widgets)
- QTableView works too slow. If we would not create widgets for all rows and just relocate them within scrolling using setIndexWidget. But function create and delete widget every time but not just change it position)
- Implement your own table with QScrollArea and QScrollBar, not create and delete widgets but just relocate their position. But there are a lot of problems: widget location on scrollarea, reimplement own scrollBar to display correct slider position depending on rows count, reimplement scroll-update, scrollarea-resize.
Now I work at 3 variant, because think it can only work.
So my question: if there is right variant to create table with a lot of widgets and which steps it contents
Thanks a lot
-
Use QTreeView (yes, tree, not table) with 3 columns.
Set uniformRowHeights to true (very important with a large data set).
Set column width to anything but automatic, so that the view doesn't try to go through all the rows to calculate it.
Subclass QStyledItemDelegate and implement paint to draw the widgets. Don't actually create any widgets, just use QStyle and QPainter to draw them.
Implement createEditor to only create the real widget when editing.Alternatively, if you don't need a separate header for each column, you can just have a single column and just draw the widgets as if they were in columns.
With this approach you can have millions of entries and it will perform well.
-
Thanks for answer, Yes I tried to use delegator before, but with delegate I need to to double click to enter in edit mode. But I need table only for editing plot so it has to behave like real widgets(not only paint) for example: spinBox with it wheel Event, when you change it value (and do it without enter a delegate). And other widget too with it specific event, so in variant with delegate I need to reimplement it all by my own.
-
You can change editTriggers so you don't have to double click. I don't remember if you can enable scroll wheel as a trigger this way but if not then just add an event filter on the view that will call
edit()
.so in variant with delegate I need to reimplement it all by my own
How many different widgets do you have? I had a tree like that with custom widgets. Implemented a bunch of them few years ago and then never had to look at that again. Works well to this day with virtually no maintenance.
-
I wrote custom-table with scrollBar and custom-logic. We have fixed number of widgets, while scrolling, just change data in rows.
table.h#ifndef TABLE_H #define TABLE_H #include <QGridLayout> #include <QGuiApplication> #include <QLabel> #include <QPushButton> #include <QResizeEvent> #include <QScreen> #include <QScrollBar> #include <QSpinBox> namespace { // CellWidget [[maybe_unused]] constexpr int cellWidgetWidth = 100; [[maybe_unused]] constexpr int cellWidgetHeight = 20; // Table [[maybe_unused]] constexpr int tableRowHeight = cellWidgetHeight; [[maybe_unused]] constexpr int tableLayoutHeightReserve = tableRowHeight; [[maybe_unused]] constexpr int tableMinimalHeight = 3 * tableRowHeight + tableLayoutHeightReserve; [[maybe_unused]] constexpr int tableHeaderHeight = cellWidgetHeight; [[maybe_unused]] constexpr int tableHeaderNumberWidth = 95; [[maybe_unused]] constexpr int tableHeaderTimeWidth = 95; [[maybe_unused]] constexpr int tableHeaderDurationWidth = 73; [[maybe_unused]] constexpr int tableHeaderStateWidth = 55; } // namespace class HeaderWidget : public QWidget { public: HeaderWidget(QWidget *parent = nullptr); private: QScopedPointer<QWidget> _blank; QScopedPointer<QLabel> _time; QScopedPointer<QLabel> _duration; QScopedPointer<QLabel> _state; }; class CellWidget : public QWidget { public: CellWidget(const QString &time = "0", QWidget *parent = nullptr); void setText(const QString &text); private: QScopedPointer<QLabel> _number; QScopedPointer<QLabel> _time; QScopedPointer<QSpinBox> _duration; QScopedPointer<QPushButton> _state; }; class Table : public QWidget { public: Table(size_t size, QWidget *parent = nullptr); virtual void resizeEvent(QResizeEvent *event) override; virtual void wheelEvent(QWheelEvent *event) override; private: void initScrollBar(); void initHeader(); void initWidgetRows(int height); void onScrollTableUpdate(int min, int max); void onTableResize(int newMin, int newMax); void updateScrollBar(int height); QScopedPointer<QScrollBar> _scrollBar; QScopedPointer<HeaderWidget> _header; std::vector<std::unique_ptr<CellWidget>> _widgets; QVBoxLayout *_innerVLayout = nullptr; QGridLayout *_mainGLayout = nullptr; bool isInitialized = false; size_t _size = 0; }; #endif // TABLE_H
And table.cpp
#include "table.h" HeaderWidget::HeaderWidget(QWidget *parent) : QWidget(parent) { QHBoxLayout *layout = new QHBoxLayout(); layout->setAlignment(Qt::AlignHCenter | Qt::AlignTop); layout->setContentsMargins({}); layout->setSpacing(0); // Blank _blank.reset(new QWidget(this)); _blank->setFixedSize(tableHeaderNumberWidth, cellWidgetHeight); _blank->setStyleSheet("background-color: grey;"); layout->addWidget(_blank.get()); // Time _time.reset(new QLabel(this)); _time->setText("Time"); // _time->setFixedHeight(buttonHeight); _time->setFixedSize(tableHeaderTimeWidth, cellWidgetHeight); _time->setStyleSheet( "border-width: 1px;" "border-style: solid;" "border-bottom-style: none;" "border-color: black;"); layout->addWidget(_time.get()); // Duration _duration.reset(new QLabel(this)); _duration->setText("Duration"); // _time->setFixedHeight(buttonHeight); _duration->setFixedSize(tableHeaderDurationWidth, cellWidgetHeight); _duration->setStyleSheet( "border-width: 1px;" "border-style: solid;" "border-bottom-style: none;" "border-color: black;"); layout->addWidget(_duration.get()); // State _state.reset(new QLabel(this)); _state->setText("State"); // _time->setFixedHeight(buttonHeight); _state->setFixedSize(tableHeaderStateWidth, cellWidgetHeight); _state->setStyleSheet( "border-width: 1px;" "border-style: solid;" "border-bottom-style: none;" "border-color: black;"); layout->addWidget(_state.get()); setLayout(layout); } CellWidget::CellWidget(const QString &time, QWidget *parent) : QWidget(parent) { QHBoxLayout *layout = new QHBoxLayout(this); layout->setAlignment(Qt::AlignHCenter | Qt::AlignTop); layout->setContentsMargins(QMargins()); layout->setSpacing(0); // Number _number.reset(new QLabel(this)); _number->setText(time); _number->setAlignment(Qt::AlignCenter); _number->setFixedSize(tableHeaderNumberWidth, cellWidgetHeight); _number->setStyleSheet( "border-width: 1px;" "border-style: solid;" "border-right-style: none;" "border-bottom-style: none;" "border-color: black;"); layout->addWidget(_number.get()); // Time _time.reset(new QLabel(this)); _time->setText(time); _time->setAlignment(Qt::AlignLeft); // _time->setFrameShape(QFrame::StyledPanel); // _time->setFixedHeight(buttonHeight); _time->setFixedSize(tableHeaderTimeWidth, cellWidgetHeight); _time->setStyleSheet( "border-width: 1px;" "border-style: solid;" "border-right-style: none;" "border-bottom-style: none;" "border-color: black;"); layout->addWidget(_time.get()); // Duration _duration.reset(new QSpinBox(this)); _duration->setMinimum(0); _duration->setMaximum(1'000); _duration->setValue(100); _duration->setFixedSize(tableHeaderDurationWidth, cellWidgetHeight); layout->addWidget(_duration.get()); // State _state.reset(new QPushButton(this)); _state->setText("State"); _state->setFixedSize(tableHeaderStateWidth, cellWidgetHeight); layout->addWidget(_state.get()); setLayout(layout); } void CellWidget::setText(const QString &text) { _time->setText(text); } Table::Table(size_t size, QWidget *parent) : QWidget(parent), _size(size) { setSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding); setMinimumHeight(tableMinimalHeight); _mainGLayout = new QGridLayout(); _mainGLayout->setAlignment(Qt::AlignTop); _innerVLayout = new QVBoxLayout(); _innerVLayout->setSpacing(0); _innerVLayout->setAlignment(Qt::AlignTop | Qt::AlignHCenter); _mainGLayout->addLayout(_innerVLayout, 0, 0); initScrollBar(); initHeader(); setLayout(_mainGLayout); } void Table::onScrollTableUpdate(int min, int max) { // Round bottom border max -= max % tableRowHeight; // Add widgets to layout for (int i = 0, pos = min; pos < max - tableLayoutHeightReserve && i < _widgets.size(); ++i, pos += tableRowHeight) { const int index = pos / tableRowHeight; if (index >= _size) break; _widgets[i]->setText(QString::number(index)); } } void Table::resizeEvent(QResizeEvent *event) { const int widgetHeight = event->size().height() - tableHeaderHeight; QWidget::resizeEvent(event); updateScrollBar(widgetHeight); if (!isInitialized) initWidgetRows(widgetHeight); else onTableResize(_scrollBar->value(), _scrollBar->value() + widgetHeight); } void Table::wheelEvent(QWheelEvent *event) { _scrollBar->event(event); } void Table::initScrollBar() { _scrollBar.reset(new QScrollBar(this)); _scrollBar->setSingleStep(tableRowHeight); _scrollBar->setPageStep(tableRowHeight); _mainGLayout->addWidget(_scrollBar.get(), 0, 1); connect(_scrollBar.get(), &QScrollBar::valueChanged, this, [&](auto pos) { return onScrollTableUpdate(pos, pos + this->height()); }); updateScrollBar(this->height()); } void Table::initHeader() { _header.reset(new HeaderWidget(this)); _innerVLayout->addWidget(_header.get()); } void Table::initWidgetRows(int height) { // Calculate max buttons number depending on screen const auto screens = QGuiApplication::screens(); int _maxWidgetNumber = 0; for (int i = 0; i < screens.size(); ++i) { const int temp = screens[i]->geometry().height() / tableRowHeight; if (temp > _maxWidgetNumber) _maxWidgetNumber = temp; } // Round bottom border height -= height % tableRowHeight; // Add new widgets for (int i = 0, pos = 0; i < _maxWidgetNumber; ++i, pos += tableRowHeight) { _widgets.push_back(std::make_unique<CellWidget>(QString::number(i), this)); // // _widgets.back()->setFixedSize(cellWidgetWidth, tableRowHeight); // _widgets.back()->setMinimumHeight(1); _innerVLayout->addWidget(_widgets.back().get()); if (pos >= height - tableLayoutHeightReserve || i >= _size) _widgets[i]->hide(); } isInitialized = true; } void Table::onTableResize(int newMin, int newMax) { blockSignals(true); // Remove widgets from layout for (int i = 0; i < _widgets.size(); ++i) { _widgets[i]->hide(); } // Round bottom border newMax -= newMax % tableRowHeight; // Add widgets to layout for (int i = 0, pos = newMin; i < _widgets.size() && pos < newMax - tableLayoutHeightReserve; ++i, pos += tableRowHeight) { const int index = pos / tableRowHeight; if (index >= _size) break; _widgets[i]->setText(QString::number(index)); _widgets[i]->show(); } blockSignals(false); } void Table::updateScrollBar(int height) { int scrollBarMax = _size * tableRowHeight - height + tableLayoutHeightReserve; // Hide when there are too little widgets if (scrollBarMax <= 0) { _scrollBar->setMinimum(0); _scrollBar->setMaximum(0); _scrollBar->hide(); return; } // Round up if (scrollBarMax % tableRowHeight != 0) scrollBarMax += tableRowHeight - scrollBarMax % tableRowHeight; _scrollBar->setMaximum(scrollBarMax); _scrollBar->show(); }