QML implements file hexadecimal data display

Foreword

Drag and drop a binary file directly into Qt Creator to directly view the data format displayed in hexadecimal, such as:

It still takes a lot of time to achieve such an effect.

I found many examples on the Internet. One of the open source codes works well (refer here), but it is implemented in QWidget, and data scrolling is achieved by inheriting QAbstractScrollArea.

If you want to customize drawing in paint in QML, you need to inherit QQuickPaintedItem, then it will be more troublesome to implement such scrolling paging display. I tried to directly encapsulate the effects implemented in Widget and put them into QML for use. It would be easier. After all, the original open source effects have been done very well.
With this in mind, let’s get started! ! !

Let’s look at the effect first:

This is implemented in the QML project, and the middle data display part is implemented using the QHexView code Widget.

Click to download the demo of this article

Text

When embedding Widget into QML, let’s first look at the problems you will encounter.

As mentioned above, if you want to customize drawing in paint in QML, you need to inherit QQuickPaintedItem and then rewrite the paint function. QML Scene Graph scene graph drawing is in two different threads. This is explained in the Qt help document:

That is, paint() is not called from the main GUI thread, but from the GL-enabled renderer thread.

So what problems will this bring? Keep reading.
Since the main UI framework is made of QML, if we want to embed Widget into QML for use, we need to create a new class that inherits QQuickPaintedItem and then put the Widget into it, encapsulate it and pass the class through qmlRegisterTypeThe registration is handed over to QML for reference. The various events in the Widget window need to be sent from QML, so an event forwarding is required, that is to say, the events obtained by QQuickPaintedItem are reasonably forwarded to QWidget so that QWidget can handle the corresponding information. This can be forwarded by filtering events in QQuickPaintedItem.
The UI rendering is performed by calling the widget’s rander in paint. We know that the UI rendering in the widget is on the main thread. As mentioned above, paint() in QQuickPaintedItem is not called from the main GUI thread, but from the GL-enabled renderer thread. Called, so an assertion error will appear when calling:

 "Cannot send events to objects owned by a different thread. Current thread 0x0x24f6d9d9db0. Receiver ''....

Fortunately, we can directly use the Release mode to avoid this assertion. We have no choice but to do it this way. This is the only way to achieve this unconventional call.

Inherit the key code encapsulated by QQuickPaintedItem:

HexViewItem::HexViewItem()
{<!-- -->
    this->setAcceptHoverEvents(true);
    this->setAcceptedMouseButtons(Qt::AllButtons);
    setFlag(ItemAcceptsInputMethod, true);
    setFlag(ItemIsFocusScope, true);
    setFlag(ItemHasContents, true);
}

//Cannot be called in the constructor
void HexViewItem::init()
{<!-- -->
    m_pHexContainer = new HexViewContainer();
    m_pHexContainer->init();
    qDebug() << __FUNCTION__ << "this size" << size();
    m_pHexContainer->resize(this->size().toSize());
    if(m_pHexContainer)
    {<!-- -->
        m_pHexContainer->createWinId();
        m_pHexContainer->installEventFilter(this);

        QWindow* pw = (QWindow*)(window());
        pw->installEventFilter(this);
        this->update();
    }
}

bool HexViewItem::sendEventToWidget(QEvent *e)
{<!-- -->
    if(!m_pHexContainer)return false;
    QWindow* wHandle = m_pHexContainer->windowHandle();
    bool res = false;
    if(wHandle)
    {<!-- -->
        res = qApp->sendEvent(wHandle, e);
    }
    return res;
}


void HexViewItem::paint(QPainter *painter)
{<!-- -->
    painter->save();
    if(m_pHexContainer)
    {<!-- -->
        m_pHexContainer->render(painter);
    }
    painter->restore();
}

bool HexViewItem::eventFilter(QObject *obj, QEvent *e)
{<!-- -->
    QWindow* pw = (QWindow*)(window());
    bool res = QQuickPaintedItem::eventFilter(obj, e);
    if(obj == m_pHexContainer)
    {<!-- -->
        switch(e->type())
        {<!-- -->
        case QEvent::Paint:
        {<!-- -->
            QPaintEvent* pe = (QPaintEvent*)e;
            this->update(pe->rect());
        }
            break;
        }
    }
    else if(obj == pw)
    {<!-- -->
        //* If it is a mouse, etc. (an event with mouse coordinate information.), we have to calculate the offset and correct it. Here we only process QMouseEvent and QWheelEvent as examples.
        //* If there are other similar ones, they also need to be corrected, otherwise the coordinates may be offset.
        switch(e->type())
        {<!-- -->
        case QEvent::MouseButtonDblClick:
        case QEvent::MouseButtonPress :
        case QEvent::MouseButtonRelease:
        case QEvent::MouseMove:
        case QEvent::MouseTrackingChange:
        case QEvent::Move:
        {<!-- -->
            QMouseEvent *me = (QMouseEvent*)e;
            QEvent::Type type = me->type();
            QPointF localPosF = me->localPos();
            Qt::MouseButton mouseButton = me->button();
            Qt::MouseButtons mouseButtons = me->buttons();
            Qt::KeyboardModifiers modifiers = me->modifiers();

            //Correct localpos
            QPointF offsetF = mapToScene(QPoint(0,0));
            QPointF diffPosF = localPosF - offsetF;

            QMouseEvent tme(type, diffPosF, mouseButton, mouseButtons, modifiers);
            sendEventToWidget( & amp;tme);
        }
            break;
        case QEvent::Wheel:
        {<!-- -->
            QWheelEvent *we = (QWheelEvent*)e;
            QPointF localPosF = we->posF();
            QPointF gloabalPosF = we->globalPosF();
            QPoint pixelDelta = we->pixelDelta();
            QPoint angleDelta = we->angleDelta();
            int qt4Delta = we->delta();
            Qt::Orientation orientation = we->orientation();
            Qt::MouseButtons mouseButtons = we->buttons();
            Qt::KeyboardModifiers modifiers = we->modifiers();

            //Correct localpos
            QPointF offsetF = mapToScene(QPoint(0,0));
            QPointF diffPosF = localPosF - offsetF;

            QWheelEvent twe(diffPosF, gloabalPosF, pixelDelta, angleDelta, qt4Delta, orientation, mouseButtons, modifiers);
            sendEventToWidget( & amp;twe);
        }
            break;
        default:
        {<!-- -->
// sendEventToWidget(e);
        }
            break;
        }
    }

    return res;
}

void HexViewItem::geometryChanged(const QRectF & amp;newGeometry, const QRectF & amp;oldGeometry)
{<!-- -->
    QQuickPaintedItem::geometryChanged(newGeometry, oldGeometry);

    if(m_pHexContainer)
    {<!-- -->
        m_pHexContainer->setGeometry(newGeometry.toRect());
    }
}

bool HexViewItem::event(QEvent *e)
{<!-- -->
    return __super::event(e);
}

The key point is to register the event filter for the QQuickItem window:

QWindow* pw = (QWindow*)(window());
pw->installEventFilter(this);

The eventFilter is to filter some key events that need to be used for forwarding, and call the render of the Widget in paint() to refresh the UI.

The QHexView source code has undergone some modifications, and some interfaces have been added for quick settings in QML, such as: highlighting a certain piece of data, quick positioning, head and bottom alignment, theme switching, screenshot saving, switching display width, etc.

Q_INVOKABLE void setFilePath(int index, QString filePath);
Q_INVOKABLE void updateGeo();
Q_INVOKABLE void setSelect(int index,int pos,int len);
Q_INVOKABLE void closeFile(int index); //Close the file
Q_INVOKABLE void alignment(bool head,int index = -1); //Head and tail alignment
Q_INVOKABLE QStringList renderHexView(QList<int> fileIndexs);
Q_INVOKABLE void setTheme(bool darkTheme);
Q_INVOKABLE void setHexLineWidth(quint8 value);

It can be expanded on this basis.

Overall directory structure:

Click to download the demo of this article

Reference article:
https://blog.csdn.net/r5014/article/details/92642626
https://github.com/Dax89/QHexView