Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Table with large amount of widgets ( 100'000 + )
Forum Updated to NodeBB v4.3 + New Features

Table with large amount of widgets ( 100'000 + )

Scheduled Pinned Locked Moved Solved General and Desktop
6 Posts 2 Posters 841 Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • Z Offline
    Z Offline
    zonman
    wrote on last edited by
    #1

    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:

    1. QTableWidget doesn't work with such amount of rows(because it creates separate QWidgets for every rows and it would thousand of widgets)
    2. 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)
    3. 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
    1 Reply Last reply
    0
    • Chris KawaC Offline
      Chris KawaC Offline
      Chris Kawa
      Lifetime Qt Champion
      wrote on last edited by Chris Kawa
      #2

      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.

      1 Reply Last reply
      2
      • Z Offline
        Z Offline
        zonman
        wrote on last edited by
        #3

        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.

        1 Reply Last reply
        0
        • Chris KawaC Offline
          Chris KawaC Offline
          Chris Kawa
          Lifetime Qt Champion
          wrote on last edited by
          #4

          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.

          1 Reply Last reply
          2
          • Z Offline
            Z Offline
            zonman
            wrote on last edited by
            #5

            Ok, thank you a lot. I will do it this week and then post result and some code for others

            1 Reply Last reply
            1
            • Z Offline
              Z Offline
              zonman
              wrote on last edited by
              #6

              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();
              }
              
              
              1 Reply Last reply
              0

              • Login

              • Login or register to search.
              • First post
                Last post
              0
              • Categories
              • Recent
              • Tags
              • Popular
              • Users
              • Groups
              • Search
              • Get Qt Extensions
              • Unsolved