在 Widgets 中,不管是绘图( 使用 QPainter ),还是不规则窗口( 使用setMask )都很容易。
但是,Qt5 以后( Qt Quick / QWindow 中),事情就不那么简单了。
即使是绘制一张图片( Image )都非常麻烦,能用的方法是:
1、使用 OpenGL API。
2、使用 Scene Graph API。
3、使用 Image(QML) 并在后端提供 source (供 setMask 使用)。
在我前面的博客中就有 OpenGL in Qt Quick 的部分教程,即方法 1:OpenGL in Qt Quick
而方法 3并不建议使用,因此,本篇使用方法 2来实现多边形窗口(不规则窗口)。
首先,使用QWindow( QQuickWindow )来实现不规则窗口,步骤和 Widgets 一样:
1、使用 setMask() 设置窗口的遮罩,即:只接受给定遮罩区域之内的鼠标或触摸输入事件。
2、使用遮罩填充窗口,由于遮罩多半是 Image,因此也可以说,绘制图像。
void QWindow::setMask(const QRegion ®ion)
设置窗口的遮罩。
该掩码向窗口系统提示该应用程序不想在给定区域之外接收鼠标或触摸输入。
窗口管理器可能会或可能不会选择显示未包含在遮罩中的窗口的任何区域,因此应用程序有责任清除透明区域以外的区域。
其中,region 描述了遮罩的区域。
一个简便的生成 mask 的方法是使用 QBitmap,它可以来自 Image 。
相关代码如下:
void PolygonWindow::changeTexture()
{
auto texture = d->m_background.scaled(size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
if (!texture.isNull()) {
d->m_background = texture;
setMask(QRegion(d->m_background.mask()));
if (d->m_centerItem) {
d->m_centerItem->setSize(size());
d->m_centerItem->setTexture(createTextureFromImage(d->m_background.toImage(), TextureHasAlphaChannel));
}
}
}
通过使用 setMask() ,我们成功设置了窗口的形状( 形状为 Image的非透明部分( Alpha != 0) ),但是,并没有绘制 Image 自身,因此,接下来我们需要绘制图像。
在 QWidgets 中,绘制图像很容易,只需要重新 paintEvent,然后 QPainter::drawImage() 即可。
而在 QWindow 中,我们需要使用 QSG,不过,稍微有点复杂。
根据文档介绍,Qml 与场景图的交互发生在 updatePaintNode() 中,因此,我们一般在此函数中使用Scene Graph API。
相关代码如下:
QSGNode* PolygonItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
{
QSGSimpleTextureNode *n = static_cast(node);
if (n) {
if (!m_texture.isNull()) {
n->setTexture(m_texture.data());
n->setFiltering(QSGTexture::Linear);
n->setRect(boundingRect());
}
} else {
n = new QSGSimpleTextureNode();
if (!m_texture.isNull()) {
n->setTexture(m_texture.data());
n->setFiltering(QSGTexture::Linear);
n->setRect(boundingRect());
}
}
}
node 是上次调用该函数时返回的节点。 updatePaintNodeData 提供一个指向与此 QQuickItem 关联的 QSGTransformNode的指针,这里只用到了 node。
由于我们绘制一张图片,因此第一次会创建一个 QSGSimpleTextureNode 节点,它是简单的纹理节点,当然,在使用之前我们需要为它设置纹理。
然后它会作为第二次的调用中的 node 参数提供。
注意:并不需要每次都setTexture,最好是在发生改变后才设置。
还要注意的是:
并没有方便给 QQuickWindow 添加 QQuickItem ( C++中 ) 的方法,
这里给出我的办法:
connect(this, &QQuickWindow::sceneGraphInitialized, this, [this]() {
d->m_centerItem.reset(new PolygonItem(contentItem()));
changeTexture();
});
通过将自己添加为 contentItem() 的子节点,它将会被加入到该 Window 场景图中,注意,contentItem() 在场景图有效时可用。
另一个加入场景图的方法是 QSGEngine 和 QSGRenderer ( 当然更加复杂 )。
最后,上一下效果图:
其实我也是才学习使用QSG,相关博客基本没有,全靠文档、官方示例和自己理解,难免有很多不足之处,还望多多指正,一起学习、进步||ヽ(* ̄▽ ̄*)ノミ|Ю
最后,附上项目链接(多多star呀..⭐_⭐):
Github的:https://github.com/mengps/QmlControls。