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. How to use scrollbars in my component?
QtWS25 Last Chance

How to use scrollbars in my component?

Scheduled Pinned Locked Moved Solved General and Desktop
9 Posts 4 Posters 369 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.
  • A Offline
    A Offline
    AndrzejB
    wrote on last edited by
    #1

    My ask seems to be trivial, but I start component for viewing huge files without reading in whole to memory.
    I need vertical and horizontal scrollbars.
    My gist is here: [https://gist.github.com/borneq/317f6fa28e6d40b443645ee8b5509125](Github gist)
    Each change scroll should call paintEvent

    1 Reply Last reply
    0
    • M Offline
      M Offline
      mpergand
      wrote on last edited by mpergand
      #4

      Quick example with basic caching of current lines.

      int main(int argc, char *argv[])
      {
      QApplication app(argc, argv);
      
          QMainWindow bigTextWindow;
          auto scrollArea=new QScrollArea;
      
          bigTextWindow.setCentralWidget(scrollArea);
      
          auto fileViewer=new BigFileViewer;
          scrollArea->setWidget(fileViewer);
      
          if(!fileViewer->setFile(filepath))   // file path of your big file
          {
              qDebug()<<filepath<<"Error openning the file!";
          }
      
          bigTextWindow.resize(600,600);
          bigTextWindow.show();
      
      return app.exec();
      }
      

      BigFileViewer.h

      #ifndef BIGFILEVIEWER_H
      #define BIGFILEVIEWER_H
      
      #include <QWidget>
      #include <QFile>
      #include <QHash>
      #include <QQueue>
      #include <QDebug>
      
      class LineCache
      {
          public:
      
              explicit LineCache(int max=500) : _maxLines(max) { _lineCache.reserve(max+1); }
      
                      void addLine(int num,const QString s)
                      {
                          _lineCache[num]=s;
                          _lineQueue.enqueue(num);
      
                          if(_lineQueue.size()>_maxLines) // dequeue the lastest
                              {
                              num=_lineQueue.dequeue();
                              _lineCache.remove(num);
                              qDebug()<<"dequeue"<<num;
                              }
                      }
      
                  const QString* lineAt(int num)
                      {
                          if(_lineQueue.contains(num)) return &_lineCache[num];
                          return nullptr;
                      }
      
          private:
      
                QHash<int,QString> _lineCache;
                      QQueue<int>  _lineQueue;   // insertion order of lines
                               int _maxLines;
      
      };
      
      class BigFileViewer : public QWidget
      {
              Q_OBJECT
      
          public:
      
                      explicit BigFileViewer(QWidget *parent = nullptr);
                         bool  setFile(const QString& filepath);
      
      
          private slots:
                          void nextLine(QFile* file);    // for counting lines
      
          private:
      
               const QString *getline(int num);
                        void  paintEvent(QPaintEvent* ev);
      
                       QFile  _file;
                         int  _lineCount=0;
                    LineCache _LineCache;
                          int _LineHeight;        // height of a line in pixel
                          int _WidestLine=100;    // widest line in pixel
      };
      
      #endif // BIGFILEVIEWER_H
      

      BigFileViewer.cpp

      #include "BigFileViewer.h"
      #include <QFontMetrics>
      #include <QPainter>
      #include <QTimer>
      #include <QRegion>
      #include <QPaintEvent>
      #include <QDebug>
      
      #define InvokeLaterWithArg(slot,argType,arg)  QMetaObject::invokeMethod(this, #slot,Qt::QueuedConnection,Q_ARG(argType,arg))
      
      BigFileViewer::BigFileViewer(QWidget *parent) : QWidget(parent)
      {
          //setVisible(true);
      }
      
      bool BigFileViewer::setFile(const QString &filepath)
      {
          _file.setFileName(filepath);
          if(!_file.open(QIODevice::ReadOnly|QIODevice::Text)) return false;
      
          // height of a line in the default font
          _LineHeight=fontMetrics().height();
      
         // get line count
          QFile* file=new QFile(filepath);
          if(!file->open(QIODevice::ReadOnly|QIODevice::Text)) 
           {
            delete file;
            return false;
           }
          nextLine(file);
          resize(_WidestLine,300);
      
          return true;
      }
      
      const QString *BigFileViewer::getline(int num)
      {
          int n=num;
          const QString* line=_LineCache.lineAt(num);
      
          if(line == nullptr)
              {
              _file.seek(0);
              QByteArray data;
      
           do
              {
              if(_file.atEnd()) return nullptr;
              data=_file.readLine();
              }while(num--);
      
              QString s=QString::fromUtf8(data);
              _LineCache.addLine(n,s);
              }
      
          line=_LineCache.lineAt(n);
          return line;
      }
      
      void BigFileViewer::nextLine(QFile *file)
      {
      
          if(!file->atEnd())
          {
              file->readLine();
              _lineCount++;
      
              InvokeLaterWithArg(nextLine,QFile*,file);
          }
          else
          {
              //qDebug()<<"line count"<<_lineCount;
              _lineCount++;
               resize(_WidestLine,_lineCount*_LineHeight);
              delete file;
          }
      }
      
      
      void BigFileViewer::paintEvent(QPaintEvent *ev)
      {
          QPainter painter(this);
          painter.setPen(Qt::black);
          auto reg = ev->region();
          int originY=reg.boundingRect().topLeft().y();
          int endY=reg.boundingRect().bottomLeft().y();
          int firstLine=originY/_LineHeight;
          int lastLine=endY/_LineHeight+1;
          //qDebug()<<firstLine<<lastLine;
      
          for(int l=firstLine; l<lastLine; l++)
              {
              const QString* str=getline(l);
      
              if(str)
                  {//qDebug()<<l<<*str<<originY;
                  QRect rec=fontMetrics().boundingRect(*str);
                  if(rec.width()+20>_WidestLine)
                      {
                      _WidestLine=rec.width()+20;
                      resize(_WidestLine,_lineCount*_LineHeight);
                      }
      
                  painter.drawText(QPoint(10,originY+_LineHeight), *str);
                  originY+=_LineHeight;
                  }
              }
      
          painter.drawRect(reg.boundingRect().adjusted(1,1,-2,-2));
      }
      

      For the cache, I think a better idea would be to memorize the position of each line, and do a QFile::seek(position) to get that line.

      V 1 Reply Last reply
      1
      • M Offline
        M Offline
        mpergand
        wrote on last edited by
        #2

        Hi,
        For scrollbars look at QScrollArea

        1 Reply Last reply
        0
        • A Offline
          A Offline
          AndrzejB
          wrote on last edited by
          #3

          @mpergand said in How to use scrollbars in my component?:

          QScrollArea

          QScrollArea is high-level way with automatic scroll large views to frame.
          But I don't know visual size of whole text. Text can be huge and I need some low-level scrolls, for example user move on file by scrolls, goto 50% or 80% of file length, my component read this value and next read part of huge file. If QStrollBar controls are sometimes used alone without QScrollArea ?

          1 Reply Last reply
          0
          • M Offline
            M Offline
            mpergand
            wrote on last edited by mpergand
            #4

            Quick example with basic caching of current lines.

            int main(int argc, char *argv[])
            {
            QApplication app(argc, argv);
            
                QMainWindow bigTextWindow;
                auto scrollArea=new QScrollArea;
            
                bigTextWindow.setCentralWidget(scrollArea);
            
                auto fileViewer=new BigFileViewer;
                scrollArea->setWidget(fileViewer);
            
                if(!fileViewer->setFile(filepath))   // file path of your big file
                {
                    qDebug()<<filepath<<"Error openning the file!";
                }
            
                bigTextWindow.resize(600,600);
                bigTextWindow.show();
            
            return app.exec();
            }
            

            BigFileViewer.h

            #ifndef BIGFILEVIEWER_H
            #define BIGFILEVIEWER_H
            
            #include <QWidget>
            #include <QFile>
            #include <QHash>
            #include <QQueue>
            #include <QDebug>
            
            class LineCache
            {
                public:
            
                    explicit LineCache(int max=500) : _maxLines(max) { _lineCache.reserve(max+1); }
            
                            void addLine(int num,const QString s)
                            {
                                _lineCache[num]=s;
                                _lineQueue.enqueue(num);
            
                                if(_lineQueue.size()>_maxLines) // dequeue the lastest
                                    {
                                    num=_lineQueue.dequeue();
                                    _lineCache.remove(num);
                                    qDebug()<<"dequeue"<<num;
                                    }
                            }
            
                        const QString* lineAt(int num)
                            {
                                if(_lineQueue.contains(num)) return &_lineCache[num];
                                return nullptr;
                            }
            
                private:
            
                      QHash<int,QString> _lineCache;
                            QQueue<int>  _lineQueue;   // insertion order of lines
                                     int _maxLines;
            
            };
            
            class BigFileViewer : public QWidget
            {
                    Q_OBJECT
            
                public:
            
                            explicit BigFileViewer(QWidget *parent = nullptr);
                               bool  setFile(const QString& filepath);
            
            
                private slots:
                                void nextLine(QFile* file);    // for counting lines
            
                private:
            
                     const QString *getline(int num);
                              void  paintEvent(QPaintEvent* ev);
            
                             QFile  _file;
                               int  _lineCount=0;
                          LineCache _LineCache;
                                int _LineHeight;        // height of a line in pixel
                                int _WidestLine=100;    // widest line in pixel
            };
            
            #endif // BIGFILEVIEWER_H
            

            BigFileViewer.cpp

            #include "BigFileViewer.h"
            #include <QFontMetrics>
            #include <QPainter>
            #include <QTimer>
            #include <QRegion>
            #include <QPaintEvent>
            #include <QDebug>
            
            #define InvokeLaterWithArg(slot,argType,arg)  QMetaObject::invokeMethod(this, #slot,Qt::QueuedConnection,Q_ARG(argType,arg))
            
            BigFileViewer::BigFileViewer(QWidget *parent) : QWidget(parent)
            {
                //setVisible(true);
            }
            
            bool BigFileViewer::setFile(const QString &filepath)
            {
                _file.setFileName(filepath);
                if(!_file.open(QIODevice::ReadOnly|QIODevice::Text)) return false;
            
                // height of a line in the default font
                _LineHeight=fontMetrics().height();
            
               // get line count
                QFile* file=new QFile(filepath);
                if(!file->open(QIODevice::ReadOnly|QIODevice::Text)) 
                 {
                  delete file;
                  return false;
                 }
                nextLine(file);
                resize(_WidestLine,300);
            
                return true;
            }
            
            const QString *BigFileViewer::getline(int num)
            {
                int n=num;
                const QString* line=_LineCache.lineAt(num);
            
                if(line == nullptr)
                    {
                    _file.seek(0);
                    QByteArray data;
            
                 do
                    {
                    if(_file.atEnd()) return nullptr;
                    data=_file.readLine();
                    }while(num--);
            
                    QString s=QString::fromUtf8(data);
                    _LineCache.addLine(n,s);
                    }
            
                line=_LineCache.lineAt(n);
                return line;
            }
            
            void BigFileViewer::nextLine(QFile *file)
            {
            
                if(!file->atEnd())
                {
                    file->readLine();
                    _lineCount++;
            
                    InvokeLaterWithArg(nextLine,QFile*,file);
                }
                else
                {
                    //qDebug()<<"line count"<<_lineCount;
                    _lineCount++;
                     resize(_WidestLine,_lineCount*_LineHeight);
                    delete file;
                }
            }
            
            
            void BigFileViewer::paintEvent(QPaintEvent *ev)
            {
                QPainter painter(this);
                painter.setPen(Qt::black);
                auto reg = ev->region();
                int originY=reg.boundingRect().topLeft().y();
                int endY=reg.boundingRect().bottomLeft().y();
                int firstLine=originY/_LineHeight;
                int lastLine=endY/_LineHeight+1;
                //qDebug()<<firstLine<<lastLine;
            
                for(int l=firstLine; l<lastLine; l++)
                    {
                    const QString* str=getline(l);
            
                    if(str)
                        {//qDebug()<<l<<*str<<originY;
                        QRect rec=fontMetrics().boundingRect(*str);
                        if(rec.width()+20>_WidestLine)
                            {
                            _WidestLine=rec.width()+20;
                            resize(_WidestLine,_lineCount*_LineHeight);
                            }
            
                        painter.drawText(QPoint(10,originY+_LineHeight), *str);
                        originY+=_LineHeight;
                        }
                    }
            
                painter.drawRect(reg.boundingRect().adjusted(1,1,-2,-2));
            }
            

            For the cache, I think a better idea would be to memorize the position of each line, and do a QFile::seek(position) to get that line.

            V 1 Reply Last reply
            1
            • A Offline
              A Offline
              AndrzejB
              wrote on last edited by AndrzejB
              #5

              Thanks for great example!

              @mpergand said in How to use scrollbars in my component?:

              For the cache, I think a better idea would be to memorize the position of each line, and do a QFile::seek(position) to get that line.

              In my (non-visual) trial I have been using Boost file mapping:

              #include <boost/interprocess/file_mapping.hpp>
              #include <boost/interprocess/mapped_region.hpp>
              .................
                  file_mapping m_file(fileName.c_str(), read_only);
                  mapped_region region(m_file, read_only);
                  addr = (char *)region.get_address();
                  fileSize = region.get_size();
              
              1 Reply Last reply
              0
              • M mpergand

                Quick example with basic caching of current lines.

                int main(int argc, char *argv[])
                {
                QApplication app(argc, argv);
                
                    QMainWindow bigTextWindow;
                    auto scrollArea=new QScrollArea;
                
                    bigTextWindow.setCentralWidget(scrollArea);
                
                    auto fileViewer=new BigFileViewer;
                    scrollArea->setWidget(fileViewer);
                
                    if(!fileViewer->setFile(filepath))   // file path of your big file
                    {
                        qDebug()<<filepath<<"Error openning the file!";
                    }
                
                    bigTextWindow.resize(600,600);
                    bigTextWindow.show();
                
                return app.exec();
                }
                

                BigFileViewer.h

                #ifndef BIGFILEVIEWER_H
                #define BIGFILEVIEWER_H
                
                #include <QWidget>
                #include <QFile>
                #include <QHash>
                #include <QQueue>
                #include <QDebug>
                
                class LineCache
                {
                    public:
                
                        explicit LineCache(int max=500) : _maxLines(max) { _lineCache.reserve(max+1); }
                
                                void addLine(int num,const QString s)
                                {
                                    _lineCache[num]=s;
                                    _lineQueue.enqueue(num);
                
                                    if(_lineQueue.size()>_maxLines) // dequeue the lastest
                                        {
                                        num=_lineQueue.dequeue();
                                        _lineCache.remove(num);
                                        qDebug()<<"dequeue"<<num;
                                        }
                                }
                
                            const QString* lineAt(int num)
                                {
                                    if(_lineQueue.contains(num)) return &_lineCache[num];
                                    return nullptr;
                                }
                
                    private:
                
                          QHash<int,QString> _lineCache;
                                QQueue<int>  _lineQueue;   // insertion order of lines
                                         int _maxLines;
                
                };
                
                class BigFileViewer : public QWidget
                {
                        Q_OBJECT
                
                    public:
                
                                explicit BigFileViewer(QWidget *parent = nullptr);
                                   bool  setFile(const QString& filepath);
                
                
                    private slots:
                                    void nextLine(QFile* file);    // for counting lines
                
                    private:
                
                         const QString *getline(int num);
                                  void  paintEvent(QPaintEvent* ev);
                
                                 QFile  _file;
                                   int  _lineCount=0;
                              LineCache _LineCache;
                                    int _LineHeight;        // height of a line in pixel
                                    int _WidestLine=100;    // widest line in pixel
                };
                
                #endif // BIGFILEVIEWER_H
                

                BigFileViewer.cpp

                #include "BigFileViewer.h"
                #include <QFontMetrics>
                #include <QPainter>
                #include <QTimer>
                #include <QRegion>
                #include <QPaintEvent>
                #include <QDebug>
                
                #define InvokeLaterWithArg(slot,argType,arg)  QMetaObject::invokeMethod(this, #slot,Qt::QueuedConnection,Q_ARG(argType,arg))
                
                BigFileViewer::BigFileViewer(QWidget *parent) : QWidget(parent)
                {
                    //setVisible(true);
                }
                
                bool BigFileViewer::setFile(const QString &filepath)
                {
                    _file.setFileName(filepath);
                    if(!_file.open(QIODevice::ReadOnly|QIODevice::Text)) return false;
                
                    // height of a line in the default font
                    _LineHeight=fontMetrics().height();
                
                   // get line count
                    QFile* file=new QFile(filepath);
                    if(!file->open(QIODevice::ReadOnly|QIODevice::Text)) 
                     {
                      delete file;
                      return false;
                     }
                    nextLine(file);
                    resize(_WidestLine,300);
                
                    return true;
                }
                
                const QString *BigFileViewer::getline(int num)
                {
                    int n=num;
                    const QString* line=_LineCache.lineAt(num);
                
                    if(line == nullptr)
                        {
                        _file.seek(0);
                        QByteArray data;
                
                     do
                        {
                        if(_file.atEnd()) return nullptr;
                        data=_file.readLine();
                        }while(num--);
                
                        QString s=QString::fromUtf8(data);
                        _LineCache.addLine(n,s);
                        }
                
                    line=_LineCache.lineAt(n);
                    return line;
                }
                
                void BigFileViewer::nextLine(QFile *file)
                {
                
                    if(!file->atEnd())
                    {
                        file->readLine();
                        _lineCount++;
                
                        InvokeLaterWithArg(nextLine,QFile*,file);
                    }
                    else
                    {
                        //qDebug()<<"line count"<<_lineCount;
                        _lineCount++;
                         resize(_WidestLine,_lineCount*_LineHeight);
                        delete file;
                    }
                }
                
                
                void BigFileViewer::paintEvent(QPaintEvent *ev)
                {
                    QPainter painter(this);
                    painter.setPen(Qt::black);
                    auto reg = ev->region();
                    int originY=reg.boundingRect().topLeft().y();
                    int endY=reg.boundingRect().bottomLeft().y();
                    int firstLine=originY/_LineHeight;
                    int lastLine=endY/_LineHeight+1;
                    //qDebug()<<firstLine<<lastLine;
                
                    for(int l=firstLine; l<lastLine; l++)
                        {
                        const QString* str=getline(l);
                
                        if(str)
                            {//qDebug()<<l<<*str<<originY;
                            QRect rec=fontMetrics().boundingRect(*str);
                            if(rec.width()+20>_WidestLine)
                                {
                                _WidestLine=rec.width()+20;
                                resize(_WidestLine,_lineCount*_LineHeight);
                                }
                
                            painter.drawText(QPoint(10,originY+_LineHeight), *str);
                            originY+=_LineHeight;
                            }
                        }
                
                    painter.drawRect(reg.boundingRect().adjusted(1,1,-2,-2));
                }
                

                For the cache, I think a better idea would be to memorize the position of each line, and do a QFile::seek(position) to get that line.

                V Offline
                V Offline
                vinHuang
                wrote on last edited by
                #6
                This post is deleted!
                1 Reply Last reply
                0
                • A Offline
                  A Offline
                  AndrzejB
                  wrote on last edited by
                  #7

                  In my simple example of QScrollArea is odd refreshing problem:
                  After scroll:
                  alt text
                  How does work QScrollArea? To speedup is copying pixels when scroll and repaint new area?
                  This pixels are shifted. Probably it is not error of QScrollArea but I must change something in code.

                  JonBJ 1 Reply Last reply
                  0
                  • A AndrzejB

                    In my simple example of QScrollArea is odd refreshing problem:
                    After scroll:
                    alt text
                    How does work QScrollArea? To speedup is copying pixels when scroll and repaint new area?
                    This pixels are shifted. Probably it is not error of QScrollArea but I must change something in code.

                    JonBJ Offline
                    JonBJ Offline
                    JonB
                    wrote on last edited by
                    #8

                    @AndrzejB
                    It ought not look like that. You should not have to do anything special in code, and only if you are actively doing something "odd" should it come out looking like that. Try an absolutely basic, standalone example, with a widget of, say, a QLabel or a QWidget in the scroll area. What widget type are you displaying in your QScrollArea? Note that if it is a QTextEdit that has its own scrollbars so does not need to go into a QScrollArea, if you are doing that maybe they are interfering with each other somehow?

                    1 Reply Last reply
                    0
                    • A Offline
                      A Offline
                      AndrzejB
                      wrote on last edited by AndrzejB
                      #9

                      Only one place where can be error is paintEvent:

                      void FileViewer::paintEvent(QPaintEvent *event) {
                          QPainter painter(this);
                          painter.setPen(Qt::black);
                          auto region = event->region();
                          int originY=region.boundingRect().topLeft().y();
                          int endY=region.boundingRect().bottomLeft().y();
                          int firstLine=originY/_LineHeight;
                          int lastLine=endY/_LineHeight+1;
                          qDebug()<<firstLine<<lastLine;
                      
                          for(int l=firstLine; l<lastLine; l++)
                              {
                              const QString str=QString(lines[l].c_str());
                                  QRect rec=fontMetrics().boundingRect(str);
                                  painter.drawText(QPoint(10,originY+_LineHeight), str);
                                  originY+=_LineHeight;
                      
                              }
                          //painter.drawRect(reg.boundingRect().adjusted(1,1,-2,-2));
                      }
                      

                      before using QScrollArea I used event.rect, now event.region.
                      Especially in line:

                      painter.drawText(QPoint(10,originY+_LineHeight), str);
                      

                      where

                      _LineHeight is 16,
                      int originY=region.boundingRect().topLeft().y();
                      

                      Probably error is because I am using discrete line

                        int firstLine=originY/_LineHeight;
                        int lastLine=endY/_LineHeight+1;
                      

                      whereas region can be anywhere , and I must correct

                      int originY=region.boundingRect().topLeft().y() + delta;
                      ```
                      where delta is between 0 and _LineHeight-1
                      
                      simple correction:
                      ```
                          auto region = event->region();
                          int originYreg=region.boundingRect().topLeft().y();
                          int endY=region.boundingRect().bottomLeft().y();
                          int firstLine=originYreg/_LineHeight;
                          int lastLine=endY/_LineHeight+1;
                          int originY = firstLine*_LineHeight;
                      ```
                      instead using region.boundingRect().topLeft().y() in drawText, I use firstLine*_LineHeight
                      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