前言
将一个二进制文件直接拖放到Qt Creator中可以直接查看到以十六进制显示的数据格式,如:
要实现一个这样的效果,还是要花不少时间的。
在网上找了挺多示例,其中一个开源代码效果不错(参考这里),但是是在QWidget中实现的,通过继承QAbstractScrollArea来实现数据滚动绘制。
如果QML中要自定义在paint中绘制,需要继承QQuickPaintedItem,那要实现这样的滚动分页展示就比较麻烦了。尝试了一下直接将Widget中实现的效果封装一下放到QML中去使用,就会比较省事,毕竟原有的开源效果已经做得很好了。
以此思路,开搞!!!
先来看效果:
这是在QML项目中实现的,中间的数据展示部分是使用QHexView代码Widget实现的。
本文demo 点击下载
正文
将Widget嵌入到QML中使用,先来看会遇到什么问题。
上面说到,QML中要自定义在paint中绘制,需要继承QQuickPaintedItem,然后重写paint函数,而QML Scene Graph场景图绘制是在两个不同的线程中,这个在Qt帮助文档中有说明:
也就是说,paint()不是从主GUI线程调用的,而是从启用GL的渲染器线程调用的.。
那这会带来什么问题呢,继续看
由于主UI框架是用QML做的,我们想把Widget嵌入到QML中使用,就需要新建一个类继承QQuickPaintedItem
然后把Widget放到其中,封装起来后将该类通过qmlRegisterType
注册交给QML去引用。那Widget窗口中的各种事件就需要从QML这边发送过去,所以需要进行一次事件转发,也就是说将QQuickPaintedItem
获得的事件合理的转发给QWidget让QWidget能处理对应的消息。这个可以通过在QQuickPaintedItem
中过滤事件后进行转发。
而UI渲染是在paint中调用widget的rander进行,我们知道Widget中UI渲染是在主线程,上面讲到 QQuickPaintedItem中的paint()不是从主GUI线程调用的,而是从启用GL的渲染器线程调用的,所以这样调用就会出现断言报错:
"Cannot send events to objects owned by a different thread. Current thread 0x0x24f6d9d9db0. Receiver ''....
好在我们可以直接用Release
模式规避这个断言,没办法,只能这样搞了,也只有这种方式能实现这种非常规的调用。
继承QQuickPaintedItem封装的关键代码:
HexViewItem::HexViewItem()
{this->setAcceptHoverEvents(true);this->setAcceptedMouseButtons(Qt::AllButtons);setFlag(ItemAcceptsInputMethod, true);setFlag(ItemIsFocusScope, true);setFlag(ItemHasContents, true);
}//不能在构造函数中调用
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){//* 如果是鼠标等(有鼠标坐标信息的事件。)的话我们得计算一下偏移量并修正一下,这里就只处理QMouseEvent和QWheelEvent作为示例//* 如果有其他类似的也需要修正,不然可能坐标偏移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();//修正一下localposQPointF offsetF = mapToScene(QPoint(0,0));QPointF diffPosF = localPosF - offsetF;QMouseEvent tme(type, diffPosF, mouseButton, mouseButtons, modifiers);sendEventToWidget(&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();//修正一下localposQPointF offsetF = mapToScene(QPoint(0,0));QPointF diffPosF = localPosF - offsetF;QWheelEvent twe(diffPosF, gloabalPosF, pixelDelta, angleDelta, qt4Delta, orientation, mouseButtons, modifiers);sendEventToWidget(&twe);}break;default:{
// sendEventToWidget(e);}break;}}return res;
}void HexViewItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
{QQuickPaintedItem::geometryChanged(newGeometry, oldGeometry);if(m_pHexContainer){m_pHexContainer->setGeometry(newGeometry.toRect());}
}bool HexViewItem::event(QEvent *e)
{return __super::event(e);
}
关键点,将QQuickItem的窗口注册事件过滤器:
QWindow* pw = (QWindow*)(window());
pw->installEventFilter(this);
eventFilter就是过滤一些需要用到的关键事件进行转发,在paint()中调用Widget的render进行UI刷新。
QHexView源码进行过一些修改,添加了一些接口可供QML中快速设置,如:高亮某段数据、快速定位,头部底部对齐,主题切换,截图保存,切换展示宽度等
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); //关闭文件
Q_INVOKABLE void alignment(bool head,int index = -1); //头部 尾部对齐
Q_INVOKABLE QStringList renderHexView(QList<int> fileIndexs);
Q_INVOKABLE void setTheme(bool darkTheme);
Q_INVOKABLE void setHexLineWidth(quint8 value);
可在此基础上进行扩展。
整体目录结构:
本文demo 点击下载
参考文章:
https://blog.csdn.net/r5014/article/details/92642626
https://github.com/Dax89/QHexView