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. Fast search through QTableWidget rows
QtWS25 Last Chance

Fast search through QTableWidget rows

Scheduled Pinned Locked Moved General and Desktop
13 Posts 4 Posters 4.5k Views
  • 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.
  • C Offline
    C Offline
    Chris Kawa
    Lifetime Qt Champion
    wrote on 14 Dec 2016, 20:24 last edited by
    #2

    A couple of suggestions:

    • Don't do two passes to hide all and then show only some. Just loop once and use setRowHidden(row, true/false).
    • Using string operations to compare dates is very inefficient. Instead of that store a date as a date in some user role of the item and get it back directly i.e.:
    //when you add an item:
    QDate d = ... //whatever 
    QTableWidgetItem* item = new QTableWidgetItem(d.toString("dd.MM.yyyy"));
    item->setData(Qt::UserRole, d); //store the date in custom role as date not as text
    

    and then when you compare them:

    QDate date = tabela->item(z,2)->data(Qt::UserRole).toDate();
    

    This way you avoid a lot of slooow conversions.

    • What's with the explicit date1.operator <=(date)? Why not just date1 < date?

    Other than that you should consider switching from QTableWidget to QTableView + QSortFilterProxyModel + QStandardItemModel. Proxy models are better for filtering tasks as they bulk their operations.

    M 1 Reply Last reply 15 Dec 2016, 07:40
    3
    • C Chris Kawa
      14 Dec 2016, 20:24

      A couple of suggestions:

      • Don't do two passes to hide all and then show only some. Just loop once and use setRowHidden(row, true/false).
      • Using string operations to compare dates is very inefficient. Instead of that store a date as a date in some user role of the item and get it back directly i.e.:
      //when you add an item:
      QDate d = ... //whatever 
      QTableWidgetItem* item = new QTableWidgetItem(d.toString("dd.MM.yyyy"));
      item->setData(Qt::UserRole, d); //store the date in custom role as date not as text
      

      and then when you compare them:

      QDate date = tabela->item(z,2)->data(Qt::UserRole).toDate();
      

      This way you avoid a lot of slooow conversions.

      • What's with the explicit date1.operator <=(date)? Why not just date1 < date?

      Other than that you should consider switching from QTableWidget to QTableView + QSortFilterProxyModel + QStandardItemModel. Proxy models are better for filtering tasks as they bulk their operations.

      M Offline
      M Offline
      mardzo
      wrote on 15 Dec 2016, 07:40 last edited by mardzo
      #3

      @Chris-Kawa said in Fast search through QTableWidget rows:

      QSortFilterProxyModel

      Thank for your answer. I have switched to QTableView with a standard model and i cant figure out how to use QSortFilterProxy model. Any suggestions?

      1 Reply Last reply
      0
      • V Offline
        V Offline
        VRonin
        wrote on 15 Dec 2016, 08:06 last edited by
        #4

        either you use the QAbstarctItemModel interface model->setData(model->index(row,column),d,Qt::UserRole) or you just act on the underlying QStandardItemModel and use item->setData(Qt::UserRole, d); on it

        "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
        ~Napoleon Bonaparte

        On a crusade to banish setIndexWidget() from the holy land of Qt

        1 Reply Last reply
        2
        • M Offline
          M Offline
          mardzo
          wrote on 16 Dec 2016, 18:22 last edited by
          #5

          Okay, so I have implemented the QSortFilterProxyModel and the filtering is much faster like this. I have another question and that is how to convert this model to html table so i can export it as pdf? Looping with a for loop between the data in the rows in order to create html is fine when i have 1000 rows, but when i have 6000 rows i have to wait 2-3 minutes in order to create the html. Any suggestions?

          1 Reply Last reply
          0
          • C Offline
            C Offline
            Chris Kawa
            Lifetime Qt Champion
            wrote on 16 Dec 2016, 18:43 last edited by
            #6

            Minutes? There's got to be something wrong with how you generate the html.
            Consider this code:

                QElapsedTimer timer;
                timer.start();
            
                QStandardItemModel model;
                for (int i = 0; i < 100000; ++i) {
                     QDate d = QDate::fromJulianDay(qrand());
                     QStandardItem* item = new QStandardItem(d.toString("dd.MM.yyyy"));
                     item->setData(d, Qt::UserRole);
                     model.appendRow(item);
                }
            
                qDebug() << timer.restart() << "ms";
            
                QString html = "<html><body><table>";
                html.reserve(40 * model.rowCount()); //40 is some heuristic value
                for (int i = 0; i < model.rowCount(); ++i)
                {
                    html += "<tr><td>";
                    html += model.data(model.index(i, 0), Qt::DisplayRole).toString();
                    html += "</td></tr>";
                }
                html += "</table></body></html>";
            
                qDebug() << timer.restart() << "ms";
            

            For 100 000 elements it takes on my machine 230 milliseconds to create the model and 40ms to generate the html.

            Btw. Make sure you're not doing measurements in Debug mode.

            1 Reply Last reply
            2
            • M Offline
              M Offline
              mardzo
              wrote on 16 Dec 2016, 19:21 last edited by
              #7

              Ok so this is my code and im trying in release mode. Im sorry for the bad formatting Still its very slow.

              
              void AccControl::exportToPdf()  {
              
              
                  ACCSortFilterProxyModel* proxyModel = this->findChild<ACCSortFilterProxyModel*>("proxy");
              
                  QComboBox* searchCombo = this->findChild<QComboBox*>("searchCombo");
              
                  QDateEdit* c1 = this->findChild<QDateEdit*>("c1");
              
                  QDateEdit* c2 = this->findChild<QDateEdit*>("c2");
              
                  QString tablecss = " style = 'margin-top:10px; margin-right:20px; border-collapse: separate; background-color: #000;'";
              
                  QString tdthcss = " style = 'text-align: left; padding: 1px; background-color: #fff; font-size:6pt; ' ";
              
                  QString htmltable = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">"
                                                               "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"><meta name=\"qrichtext\" content=\"1\">"
                                                               "<title>QTextEdit Example</title>"
                                                               "<style type=\"text/css\">p, li { white-space: pre-wrap; }"
                                                               "</style></head><body style=\" font-family:Tahoma; font-size:9pt; font-weight:300; font-"
                                                               "style:normal;\">" "<table align=center style = 'margin-top:50px;  margin-right:20px; '><tr><th><h2><span>Извештај за настани од контрола на пристап</span></h2></th></tr></table><table align=center style='margin-top:10px;  margin-right:20px; '><tr><th"+ tdthcss +"><span>Филтер:</span></th><th"+ tdthcss +"><span>" +  searchCombo->currentText() + "</span></th><th"+ tdthcss +"><span>Од:</span></th><th"+ tdthcss +"><span>"+ c1->date().toString("dd.MM.yyyy") +"</span></th><th"+ tdthcss +"><span>До:</span></th><th"+ tdthcss +"><span>"+ c2->date().toString("dd.MM.yyyy") +"</span></th></tr></table>";
              
                  htmltable= htmltable + "<table align=center cellspacing=1 width=440 " + tablecss + "> <tr> <th" + tdthcss  + "><span>Бр.</span></th> <th" + tdthcss  + "><span>Уред</span></th> <th" + tdthcss  + "><span>Лог</span></th> <th" + tdthcss  + "><span>Врата</span></th> <th" + tdthcss  + "><span>UID</span></th> <th" + tdthcss  + "><span>Корисник</span></th> <th" + tdthcss  + "><span>Датум и Време</span></th> </tr>";
              
              
                  int j = 1;
              
                  int k = 1;
              
                  QStringList split1;
                  QStringList split2;
              
                  for (int i =0; i < proxyModel->rowCount(); i++) {
              
              
              
                          //ova za koloritnost na redici
                         if ((j%2)!=0) { tdthcss = " style = 'text-align: left; padding: 1px; background-color: #dddddd; font-size:6pt;' "; } else { tdthcss = " style = 'text-align: left; padding: 1px; background-color: #fff; font-size:6pt;' "; }
              
                         //za reden broj
              
                         htmltable = htmltable+"<tr> <td" + tdthcss  + "><span>" + QString::number(j) + "</span></td>";
              
              
                          //za device
              
                          htmltable = htmltable+"<td" + tdthcss  + "><span>" + proxyModel->index(i,0).data(Qt::DisplayRole).toString() + "</span></td>";
              
              
                          //za log
              
                          htmltable = htmltable+"<td" + tdthcss  + "><span>" + proxyModel->index(i,1).data(Qt::DisplayRole).toString() + "</span></td>";
              
                          //za vrata
              
                          htmltable = htmltable+"<td" + tdthcss  + "><span>" + proxyModel->index(i,2).data(Qt::DisplayRole).toString() + "</span></td>";
              
                          //za uid go zakomentirav jer realno ne se sobira na A4  dokolku go vrakam treba da ja dodadam kolonatai i vo html tabelata
              
                          //pravam splitovi da go izvadam samo edniot tip na prikaz kartica
              
                           if(proxyModel->index(i,3).data(Qt::DisplayRole).toString().contains('(')) {
              
                           split1 = proxyModel->index(i,3).data(Qt::DisplayRole).toString().split('(');
              
                           // ovaj if e za zastita da ne vlezam u nedefiniran element na lista
                           if(split1.length()==2) {
              
                           split2 = split1[1].split(')');
              
                           htmltable = htmltable+"<td" + tdthcss  + ">" + split2[0] + "</td>";
              
                           } else {htmltable = htmltable+"<td" + tdthcss  + ">" + "</td>";}
              
                           split1.clear();
                           split2.clear();
              
                           }
                           else { htmltable = htmltable+"<td" + tdthcss  + ">" + "</td>";}
              
              
              
                          //za name
              
                          htmltable = htmltable+"<td" + tdthcss  + "><span>" + proxyModel->index(i,4).data(Qt::DisplayRole).toString() + "</span></td>";
              
              
                          //za datum
              
                          htmltable = htmltable+"<td" + tdthcss  + "><span>" + proxyModel->index(i,5).data(Qt::DisplayRole).toString() + "</span></td></tr>";
              
                          j++;
                          k++;
              
                          //za da ja seckam na stranici
                          if (k == 51) {
              
                          k = 1;
              
                          htmltable = htmltable + "</table> </body> </html>";
              
                          htmltable = htmltable + "<html  > <head>  </head> <body style = ''><table align=center style = 'margin-top:50px;  margin-right:20px; '><tr><th><h2><span>Извештај за настани од контрола на пристап</h2></span></th></tr></table><table align=center style='margin-top:10px;  margin-right:20px; '><tr><th"+ tdthcss +"><span>Филтер:</span></th><th"+ tdthcss +"><span>" +  searchCombo->currentText() + "</span></th><th"+ tdthcss +"><span>Од:</span></th><th"+ tdthcss +"><span>"+ c1->date().toString("dd.MM.yyyy") +"</span></th><th"+ tdthcss +"><span>До:</span></th><th"+ tdthcss +"><span>"+ c2->date().toString("dd.MM.yyyy") +"</span></th></tr></table>";
              
                          htmltable= htmltable + "<table align=center cellspacing=1 width=440 " + tablecss + "> <tr> <th" + tdthcss  + "><span>Бр.</span></th> <th" + tdthcss  + "><span>Уред</span></th> <th" + tdthcss  + "><span>Лог</span></th> <th" + tdthcss  + "><span>Врата</span></th> <th" + tdthcss  + "><span>UID</span></th><th" + tdthcss  + "><span>Корисник</span></th> <th" + tdthcss  + "><span>Датум и Време</span></th> </tr>";
              
              
                          };
              
              
                  }
              
                  htmltable = htmltable + "</table> </body> </html>";
              
                  QString fileName = QFileDialog::getSaveFileName(this,tr("Снимај листа на логови како PDF"),"", tr("PDF files (*.pdf)"));
              
                      if (fileName.isEmpty())
              
                          return;
              
              
              
              
                      QPrinter printer;
              
                      printer.setResolution(QPrinter::HighResolution);
              
                      printer.setOutputFormat(QPrinter::PdfFormat);
              
                      printer.setOutputFileName(fileName);
              
                      // printer.setFullPage(true);
              
                      printer.setOrientation(QPrinter::Portrait);
              
                      printer.setPaperSize(QPrinter::A4);
              
                      QSizeF size = printer.paperSize(QPrinter::Point);
              
                     // QTextEdit *edit = new QTextEdit(this);
              
                      //edit->setHtml(htmltable);
              
                      QTextDocument doc;
              
                      doc.setHtml(htmltable);
              
                      doc.setPageSize(size);
              
                      doc.print(&printer);
              
              
              }
              
              1 Reply Last reply
              0
              • C Offline
                C Offline
                Chris Kawa
                Lifetime Qt Champion
                wrote on 16 Dec 2016, 19:47 last edited by Chris Kawa
                #8

                The problem in this code is the classical one - it's that there's no one super-slow place in it. It's that all of it is kinda slow in small amounts and it just piles up.
                Couple of examples that hopefully you can expand on:

                if ((j%2)!=0) { tdthcss = " style = 'text-align: left; padding: 1px; background-color: #dddddd; font-size:6pt;' "; }
                        else { tdthcss = " style = 'text-align: left; padding: 1px; background-color: #fff; font-size:6pt;' "; }
                

                Thousands of times you recreate new string tdthcss, while you could just once create both versions as separate constants and use one or the other.

                htmltable = htmltable+"<tr> <td" + tdthcss  + "><span>" + QString::number(j) + "</span></td>";
                

                Each + and = here is a costly reallocation:

                htmltable+"<tr> <td" //allocates a temporary and copies both strings into it
                + tdthcss // reallocates the temporary to make room and copies
                + "><span>" //reallocates the temporary to make room and copies
                + QString::number(j) // I hope you see where this is going...
                

                Instead of doing all those reallocations create the string once, reserve some expected amount of storage and then add to it:

                htmlTable.reserve(<whatever you think is gonna be enouh>);
                for (...) {
                   htmlTable += "<tr> <td"; //no reallocations
                   htmlTable += tdthcss; //no reallocations
                   ...
                }
                

                On to the next lines:

                if(proxyModel->index(i,3).data(Qt::DisplayRole).toString().contains('(')) {
                            split1 = proxyModel->index(i,3).data(Qt::DisplayRole).toString().split('(');
                

                Conversions do cost. Don't do them twice if you don't have to:

                QString foo = proxyModel->index(i,3).data(Qt::DisplayRole).toString();
                if(foo.contains('(')) {
                            split1 = foo.split('(');
                

                Next - the splits. Splitting string is very costly. It creates copies of each part and QStringList is not a great structure either. Instead use splitRef, which creates a nice vector of small QStringRef's. These are just pointers into the existing data so no character copying is made at all.

                Better yet - why split at all? What you really want to do is find text between ( and ) so why allocate any dynamic container (even for refs).
                Here's how you could just find the fragment in-place without any dynamic allocations:

                QString foo = proxyModel->index(i,3).data(Qt::DisplayRole).toString();
                int start = foo.indexOf('('); //just search no allocations
                if (start >= 0) {
                   int end = foo.indexOf(')', start); //just search no allocations
                   if (end >=0)
                      htmlTable += foo.midRef(start, end - start); //just copy, no reallocations
                   else ...
                }
                else ...
                

                And finally

                split1.clear();
                split2.clear();
                

                That's just waste of time. You're reassigning the variables each time so clearing them afterwards is just wasted cycles.

                1 Reply Last reply
                2
                • H Offline
                  H Offline
                  hskoglund
                  wrote on 16 Dec 2016, 20:19 last edited by
                  #9

                  Hi, just want to add to @Chris-Kawa's great advice, to ease the burden on that poor QString htmltable that gets reassigned lots of times, you could try using a local QString inside your loop, like this:

                  for (int i =0; i < proxyModel->rowCount(); i++) {
                      ...
                      //za reden broj
                      QString htmlrow;
                      htmlrow.reserve(1337);
                      htmlrow = "<tr> <td" + tdthcss  + "><span>" + QString::number(j) + "</span></td>";
                      
                      //za device
                      htmlrow += "<td" + tdthcss  + "><span>" + proxyModel->index(i,0).data(Qt::DisplayRole).toString() + "</span></td>";
                      ...
                  

                  then at the end of the loop you add htmlrow to htmltable

                      htmlrow += "<table align=center cellspacing=1 width=440 " + tablecss + "> <tr> <th" + tdthcss  + "><span>Бр.</span></th> <th" + tdthcss  + "><span>Уред</span></th> <th" + tdthcss  + "><span>Лог</span></th> <th" + tdthcss  + "><span>Врата</span></th> <th" + tdthcss  + "><span>UID</span></th><th" + tdthcss  + "><span>Корисник</span></th> <th" + tdthcss  + "><span>Датум и Време</span></th> </tr>";
                      };
                      
                      htmltable += htmlrow;
                   }
                  1 Reply Last reply
                  1
                  • M Offline
                    M Offline
                    mardzo
                    wrote on 17 Dec 2016, 11:04 last edited by
                    #10

                    Thanks guys for your advice. Your optimisations increased the generation speed of my table a lot. The only thing that seems still to be slow is the QPrinter when table has thousands of rows. Should i move the printer in a different thread in order to prevent gui freezing?

                      QPrinter printer;
                    
                            printer.setResolution(QPrinter::HighResolution);
                    
                            printer.setOutputFormat(QPrinter::PdfFormat);
                    
                            printer.setOutputFileName(fileName);
                    
                            // printer.setFullPage(true);
                    
                            printer.setOrientation(QPrinter::Portrait);
                    
                            printer.setPaperSize(QPrinter::A4);
                    
                            QSizeF size = printer.paperSize(QPrinter::Point);
                    
                           // QTextEdit *edit = new QTextEdit(this);
                    
                            //edit->setHtml(htmltable);
                    
                            QTextDocument doc;
                    
                            doc.setHtml(htmltable);
                    
                            doc.setPageSize(size);
                    
                            doc.print(&printer);
                    
                    1 Reply Last reply
                    0
                    • M Offline
                      M Offline
                      mardzo
                      wrote on 17 Dec 2016, 11:17 last edited by mardzo
                      #11

                      debug shows that it is not actually the printer that is making the gui freezes. It is the line that sets the html to the QTextDocument that is slowing down. Any suggestions?

                      doc.setHtml(htmltable);
                      
                      1 Reply Last reply
                      0
                      • C Offline
                        C Offline
                        Chris Kawa
                        Lifetime Qt Champion
                        wrote on 17 Dec 2016, 11:53 last edited by
                        #12

                        I guess you're doing the work twice. Instead of building html string and then parse it to convert to QTextDocument maybe you could just build the QTextDocument directly using QTextCursor. That should be faster.

                        1 Reply Last reply
                        0
                        • M Offline
                          M Offline
                          mardzo
                          wrote on 17 Dec 2016, 13:02 last edited by
                          #13

                          Thanks for the suggestion but as i already have a worker thread used for stg else, i am now printing in the worker thread and im sending the string via a signal.

                          1 Reply Last reply
                          0

                          11/13

                          17 Dec 2016, 11:17

                          • Login

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