The best option is to create a custom widget because the task is private and none of the widgets are accommodated to the task.
For this we will inherit from QAbstractScrollArea, from this class inherit QTableView, QListView, QListWidget and QTableWidget. It is designed to display data within a QScrollArea.
In addition documentation of this class tells us how to create a custom class.
When inheriting QAbstractScrollArea, you need to do the following:
- Control the scroll bars by setting their range, value, page step, and
tracking their movements.
- Draw the contents of the area in the
viewport according to the values of the scroll bars.
- Handle events
received by the viewport in viewportEvent()
- notably resize events.
Use viewport->update() to update the contents of the viewport instead
of update() as all painting operations take place on the viewport.
Taking that reference I created the following widget:
memoryviewer.h
#ifndef MEMORYVIEWER_H
#define MEMORYVIEWER_H
#include <QAbstractScrollArea>
#include <QBuffer>
class MemoryViewer : public QAbstractScrollArea
{
Q_OBJECT
public:
MemoryViewer(QWidget *parent = 0);
~MemoryViewer();
void setData(const QByteArray &ba);
bool setData(QIODevice &device);
protected:
void paintEvent(QPaintEvent *);
void resizeEvent(QResizeEvent *);
private:
void adjustContent();
void init();
int addressWidth();
int hexWidth();
int asciiWidth();
QByteArray data(qint64 pos=0, qint64 count=-1);
int nBlockAddress;
int mBytesPerLine;
int pxWidth;
int pxHeight;
qint64 startPos;
qint64 endPos;
int nRowsVisible;
QBuffer buffer;
QIODevice *ioDevice;
qint64 size;
QByteArray dataVisible;
QByteArray dataHex;
};
#endif // MEMORYVIEWER_H
memoryviewer.cpp
#include "memoryviewer.h"
#include <QPainter>
#include <QScrollBar>
MemoryViewer::MemoryViewer(QWidget *parent):QAbstractScrollArea(parent)
{
ioDevice = new QBuffer(this);
init();
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &MemoryViewer::adjustContent);
connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &MemoryViewer::adjustContent);
}
MemoryViewer::~MemoryViewer()
{
}
void MemoryViewer::init()
{
nBlockAddress = 2;
mBytesPerLine = 16;
pxWidth = fontMetrics().width(QChar('0'));
pxHeight = fontMetrics().height();
}
int MemoryViewer::addressWidth()
{
return (nBlockAddress*4+ nBlockAddress -1)*pxWidth;
}
int MemoryViewer::hexWidth()
{
return (mBytesPerLine*3+1)*pxWidth;
}
int MemoryViewer::asciiWidth()
{
return (mBytesPerLine*2 +1)*pxWidth;
}
QByteArray MemoryViewer::data(qint64 pos, qint64 count)
{
QByteArray buffer;
if (pos >= size)
return buffer;
if (count < 0)
count = size;
else
if ((pos + count) > size)
count = size - pos;
if(ioDevice->open(QIODevice::ReadOnly)){
ioDevice->seek(pos);
buffer = ioDevice->read(count);
ioDevice->close();
}
return buffer;
}
void MemoryViewer::setData(const QByteArray &ba)
{
buffer.setData(ba);
setData(buffer);
}
bool MemoryViewer::setData(QIODevice &device)
{
ioDevice = &device;
bool ok = ioDevice->open(QIODevice::ReadOnly);
if(ok){
size = ioDevice->size();
ioDevice->close();
}
else{
QBuffer *buf = new QBuffer(this);
ioDevice = buf;
}
init();
adjustContent();
return ok;
}
void MemoryViewer::resizeEvent(QResizeEvent *)
{
adjustContent();
}
void MemoryViewer::paintEvent(QPaintEvent *)
{
QPainter painter(viewport());
int offsetX = horizontalScrollBar()->value();
int y = pxHeight;
QString address;
painter.setPen(viewport()->palette().color(QPalette::WindowText));
for(int row = 0; row <= dataVisible.size()/mBytesPerLine; row++){
QString str = QString("%1").arg(startPos + mBytesPerLine*row, nBlockAddress*4, 16, QChar('0')).toUpper();
int i = 0;
address = "";
while(i < nBlockAddress){
address += str.mid(i*4, 4) + ":";
i++;
}
address.remove(address.size()-1, 1);
painter.drawText(pxWidth/2 -offsetX , y, address);
y+=pxHeight;
}
int x;
int lx = addressWidth() +pxWidth;
painter.drawLine(lx-offsetX, 0, lx-offsetX, height());
lx += pxWidth/2;
y = pxHeight;
//hex data
x = lx-offsetX+3*pxWidth;
int w = 3*pxWidth;
for(int col =0; col < mBytesPerLine/2; col++){
painter.fillRect(x-pxWidth/2, 0, w, height(), viewport()->palette().color(QPalette::AlternateBase));
x+= 6*pxWidth;
}
int bPos = 0;
for(int row=0; row < nRowsVisible; row++){
x = lx-offsetX;
for(int col =0; (col < mBytesPerLine) && (bPos < dataHex.size()) ; col++){
QString str = dataHex.mid(bPos*2,2).toUpper();
painter.drawText(x, y, str);
x += 3*pxWidth;
bPos += 1;
}
y+= pxHeight;
}
lx = addressWidth() + hexWidth();
painter.drawLine(lx-offsetX, 0, lx-offsetX, height());
lx += pxWidth/2;
bPos = 0;
y = pxHeight ;
int ch;
for(int row=0; row < nRowsVisible; row++){
x = lx-offsetX;
for(int col =0; (col < mBytesPerLine) && (bPos < dataVisible.size()) ; col++){
ch = (uchar)dataVisible.at(bPos);
if ( ch < 0x20 )
ch = '.';
painter.drawText(x, y, QChar(ch));
x += 2*pxWidth;
bPos += 1;
}
y+= pxHeight;
}
}
void MemoryViewer::adjustContent()
{
int w = addressWidth() + hexWidth() + asciiWidth();
horizontalScrollBar()->setRange(0, w - viewport()->width());
horizontalScrollBar()->setPageStep(viewport()->width());
nRowsVisible = viewport()->height()/pxHeight;
int val = verticalScrollBar()->value();
startPos = (qint64)val*mBytesPerLine;
endPos = startPos + nRowsVisible*mBytesPerLine -1;
int lineCount = size/mBytesPerLine;
verticalScrollBar()->setRange(0, lineCount-nRowsVisible);
verticalScrollBar()->setPageStep(nRowsVisible);
if(endPos >= size){
endPos = size-1;
}
dataVisible = data(startPos, endPos-startPos + mBytesPerLine +1);
dataHex = dataVisible.toHex();
viewport()->update();
}
An advantage of the implementation is that you do not have to load all the bytes directly since you can pass an object that inherits from QIODevice
such as QFile
and read the data when the view needs it by eliminating the memory overhead. In addition you can pass a QByteArray
In the following link you will find an example