Qml中实现多视图,多图像源(QImage / QPixmap)

【写在前面】

       在 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() 。 如果应该始终从图像提供者获取图像,并且根本不应该对其进行缓存,则将相关 ImageBorderImage AnimatedImage 对象的 cache 属性设置为 false。

       然而,将 cache 设置为 false 并没有卵用,仍然需要使用 Date.now() 来请求一个不同的图像,才能造成刷新的效果( 也许这是一个 Bug),但不管怎么说,这个坑必须小心。

       最后,附上项目链接(多多star呀..⭐_⭐):

       CSDN的:Qml中实现多视图,多图像源(QImage/QPixmap)_QPixmap-C++文档类资源-CSDN下载

       Github的:https://github.com/mengps/QmlExamples

  • 17
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
要在QML实现多页的切换,可以使用`StackView`组件和按钮来实现。`StackView`用于管理多个页面,并提供了一种简单的方式来切换页面。 以下是一个示例代码,展示了如何使用按钮来切换多个页面: ```qml import QtQuick 2.0 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 ApplicationWindow { visible: true width: 400 height: 300 StackView { id: stackView anchors.fill: parent initialItem: page1 Component.onCompleted: { stackView.push(page1); // 初始页面 } } RowLayout { anchors.bottom: parent.bottom spacing: 10 Button { text: "Page 1" onClicked: stackView.push(page1) } Button { text: "Page 2" onClicked: stackView.push(page2) } Button { text: "Page 3" onClicked: stackView.push(page3) } } Component { id: page1 Rectangle { width: stackView.width height: stackView.height color: "lightblue" Text { text: "Page 1" font.pixelSize: 20 anchors.centerIn: parent } } } Component { id: page2 Rectangle { width: stackView.width height: stackView.height color: "lightgreen" Text { text: "Page 2" font.pixelSize: 20 anchors.centerIn: parent } } } Component { id: page3 Rectangle { width: stackView.width height: stackView.height color: "lightpink" Text { text: "Page 3" font.pixelSize: 20 anchors.centerIn: parent } } } } ``` 在上面的例子,我们使用`StackView`作为主要的页面管理器,并在底部使用`RowLayout`来放置按钮。 每个按钮都有一个`onClicked`事件处理程序,当按钮被点击时,我们使用`stackView.push()`函数将相应的页面推入`StackView`。 每个页面都是通过`Component`定义的,其包含一个带有文本的矩形。你可以根据需要自定义页面的内容和样式。 通过点击按钮,你可以在应用程序切换不同的页面。这种方式可以用于创建多页应用程序、导航界面等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦起丶

您的鼓励和支持是我创作最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值