前言
将一个二进制文件直接拖放到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();
//修正一下localpos
QPointF 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();
//修正一下localpos
QPointF 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