Solved How to use scrollbars in my component?
-
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 -
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.
-
Hi,
For scrollbars look at QScrollArea -
@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 ? -
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.
-
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();
-
This post is deleted! -
In my simple example of QScrollArea is odd refreshing problem:
After scroll:
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. -
@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, aQLabel
or aQWidget
in the scroll area. What widget type are you displaying in yourQScrollArea
? Note that if it is aQTextEdit
that has its own scrollbars so does not need to go into aQScrollArea
, if you are doing that maybe they are interfering with each other somehow? -
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