【写在前面】
在 Qml 中,实现多视图很容易,无非是多个 Image 而已。
但是,如果需要动态刷新,则变得困难。
再或者,来自多个不同的图像源,将更加复杂。
实际上,这在 Qt ( Widgets ) 中实现却很容易,究其原因,是 Qml 中缺少对 QImage ( 或者说 原始图像 ) 的支持 。
即便如此,Qt 仍提供了一种解决方法。
本篇主要内容:
1、QML 中支持 QImage / QPixmap 。
2、QML 中实现多视图。
3、QML 中实现多图像源视图。
【正文开始】
首先,展示一下效果图。
多视图的:
多图像源的:
- 想要在 QML 中实现上面的效果,必须学会在 QML 中使用 QImage / QPixmap 。
其中,核心的 Class 为 QQuickImageProvider。
QQuickImageProvider is used to provide advanced image loading features in QML applications. It allows images in QML to be:
Loaded using QPixmaps rather than actual image files.
Loaded asynchronously in a separate thread.
To specify that an image should be loaded by an image provider, use the "image:" scheme for the URL source of the image, followed by the identifiers of the image provider and the requested image.
翻译:
QQuickImageProvider 用于在 QML 应用程序中提供高级图像加载功能。 它允许 QML 中的图像为:
使用 QPixmaps 而不是实际的图像文件加载。
在单独的线程中异步加载。
要指定图像提供者应加载图像,请对图像的URL源使用“ image:”方案,然后使用图像提供者的标识符和请求的图像。
根据文档以及示例,很容易明白 QQuickImageProvider 的工作方式:
1、写一个自己的 QQuickImageProvider。
2、使用 void QQmlEngine::addImageProvider(const QString &providerId, QQmlImageProviderBase *provider),添加到 QmlEngine 中,并且拥有其所有权。
3、Qml 中 Image 的 source 前缀为:"image://" 来请求图像。
4、请求将调用 QQuickImageProvider 中的 requestImage() / requestPixmap() / requestTexture(),藉此返回 C++ 提供的图像 ( QImage / QPixmap / QQuickTextureFactory )。
对于 request*() 函数,需要注意的是:该方法可能被多个线程调用,因此请确保该方法的实现是可重入 ( 即线程安全 ) 的。
- 现在我们来实现第一个:多视图。
Qml 部分关键代码如下:
Page {
id: page1
width: 860
height: 660
Connections {
target: view
onViewNeedUpdate: {
/**
* 1. index为视图索引
* 2. ###用于分隔
* 3. Date.now()用于更新
*/
viewRepeater.itemAt(index).source = "image://MultiView/" + index + "###" + Date.now();
}
}
Grid {
rows: 3
columns: 3
anchors.fill: parent
Repeater {
id: viewRepeater
model: 9
Image {
mipmap: true
width: page1.width / 3
height: page1.height / 3
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.top: parent.top
anchors.topMargin: 12
font.bold: true
font.pointSize: 30
color: "red"
text: index
}
Rectangle {
anchors.fill: parent
border.width: 2
border.color: "yellow"
color: "transparent"
}
}
}
}
}
其中,viewRepeater.itemAt(index).source = "image://MultiView/" + index + "###" + Date.now() 将调用 requestImage()。
然后,我们实现自己的 ImageProvider ( 这里是 ViewProvider ):
#ifndef VIEWPROVIDER_H
#define VIEWPROVIDER_H
#include <QQuickImageProvider>
class ViewProvider : public QQuickImageProvider
{
public:
ViewProvider();
void updateView(int index, const QImage &view);
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
private:
/**
* @note 为什么使用 hash 而不是 vector
* 因为这样可以扩展到更广泛的使用场景
*/
QHash<int, QImage> m_views;
};
#endif // VIEWPROVIDER_H
#include "viewprovider.h"
ViewProvider::ViewProvider()
: QQuickImageProvider(QQuickImageProvider::Image)
{
}
void ViewProvider::updateView(int index, const QImage &view)
{
m_views[index] = view;
}
QImage ViewProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
//这里可以id.left(1);
int index = id.left(id.indexOf("###")).toInt();
QImage view = m_views[index];
if (!view.isNull()) {
view.scaled(requestedSize);
if (size) *size = requestedSize;
}
return view;
}
接着,使用 addImageProvider Qml 引擎中。
其中 ProviderId 分别为 MultiView ( Qml 中使用 image://MultiView 访问),MultiSource ( Qml 中使用 image://MultiSource 访问):
#include "view.h"
#include "viewprovider.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
View *view = new View;
QQmlApplicationEngine engine;
engine.addImageProvider(QLatin1String("MultiView"), view->multiView());
engine.addImageProvider(QLatin1String("MultiSource"), view->multiSource());
engine.rootContext()->setContextProperty("view", view);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
最后,我们只需送入 QImage 并提示 Qml 更新即可:
//多视图图像生成器
d->m_generator = new QTimer(this);
connect(d->m_generator, &QTimer::timeout, this, [this]() {
static int index = 0;
QImage image = QGuiApplication::primaryScreen()->grabWindow(0).toImage();
d->m_multiView->updateView(index, image);
emit viewNeedUpdate(index);
if (++index == 9) index = 0;
});
d->m_generator->start(1000 / 9);
这里的图像源为桌面截图,间隔 111ms,送给九个视图进行显示。
-
现在我们来实现第二个:多图像源视图。
在上面,我们已经成功实现了一个图像源的多视图展示,现在更进一步,提供多个图像源。
Qml 部分关键代码如下:
Page {
id: page2
width: 860
height: 660
Connections {
target: view
onSourceNeedUpdate: {
/**
* 1. source为图像源索引
* 2. ###用于分隔
* 3. Date.now()用于更新
*/
sourceRepeater.itemAt(source).source = "image://MultiSource/" + source + "###" + Date.now();
}
}
Grid {
rows: 2
columns: 2
anchors.fill: parent
Repeater {
id: sourceRepeater
model: 4
Image {
mipmap: true
width: page2.width / 2
height: page2.height / 2
fillMode: Image.PreserveAspectFit
property bool running: true
Image {
width: 80
height: 80
anchors.centerIn: parent
opacity: 0.7
mipmap: true
source: parent.running ? "" : "qrc:/play.png"
}
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.top: parent.top
anchors.topMargin: 12
font.bold: true
font.pointSize: 30
color: "red"
text: index
}
Rectangle {
anchors.fill: parent
border.width: 2
border.color: "#89f2f5"
color: "transparent"
}
MouseArea {
anchors.fill: parent
onClicked: {
if (parent.running) {
view.pauseSource(index);
parent.running = false;
} else {
view.resumeSource(index);
parent.running = true;
}
}
}
}
}
}
}
这里基本类似,只是多了暂停 / 继续而已。
C++ 部分关键代码:
/**
* @note 多图像源
* @source[0-4] gif[0-4]
*/
for (int i = 0; i < 4; i++) {
d->m_sources[i] = new QMovie(":/" + QString::number(i) + ".gif", "GIF", this);
qDebug() << d->m_sources[i]->fileName() << d->m_sources[i]->isValid();
connect(d->m_sources[i], &QMovie::frameChanged, this, [i, this](int) {
d->m_multiSource->updateView(i, d->m_sources[i]->currentImage());
emit sourceNeedUpdate(i);
});
}
如你所见,四个图像源来自四张不同的 GIF 图像,之所以用这种形式,是因为可以让人联想到其他类似的场景:
1、多个监视器( 多个摄像头 )。
2、多个图像源( 多个视频源 )。
而对于这些场景,我觉得是相当有用的。
【结语】
实际上,在 Qml 使用 QImage 确实有些麻烦。
当然,对我来说,因为用得多了,反倒是挺习惯的。
并且也渐渐明白了,Qml 只做前端显示,复杂的工作都由后端的 C++ 来完成。
然后一点题外话要说,注意:
Image Caching:
Images returned by a QQuickImageProvider are automatically cached, similar to any image loaded by the QML engine. When an image with a "image://" prefix is loaded from cache, requestImage() and requestPixmap() will not be called for the relevant image provider. If an image should always be fetched from the image provider, and should not be cached at all, set the cache property to false for the relevant Image, BorderImage or AnimatedImage object.
翻译:图像缓存
由 QQuickImageProvider 返回的图像将自动缓存,类似于 QML 引擎加载的任何图像。 当从缓存中加载带有 "image://" 前缀的图像时,相关图像提供者将不会调用 requestImage() 和 requestPixmap() 。 如果应该始终从图像提供者获取图像,并且根本不应该对其进行缓存,则将相关 Image,BorderImage 或 AnimatedImage 对象的 cache 属性设置为 false。
然而,将 cache 设置为 false 并没有卵用,仍然需要使用 Date.now() 来请求一个不同的图像,才能造成刷新的效果( 也许这是一个 Bug),但不管怎么说,这个坑必须小心。
最后,附上项目链接(多多star呀..⭐_⭐):
CSDN的:Qml中实现多视图,多图像源(QImage/QPixmap)_QPixmap-C++文档类资源-CSDN下载