Auf dieser Seite

Qt Canvas Painter – Beispiel „Compact Health“

Veranschaulicht die Verwendung von „ QCanvasPainter “ in einem „ QWindow “.

Patientenmonitor mit Anzeigen für Puls, SpO₂, Atmung und Temperatur sowie grünen und gelben Kurvenverläufen

Dieses Beispiel veranschaulicht die Verwendung von Qt Canvas Painter in einer reinen „ QWindow “-Anwendung. Die Qt Quick und Qt Widgets Stacks werden nicht verwendet.

Die Anwendung rendert alle Inhalte über „ QRhi “-Backends mit „ QCanvasPainter “. Das Rendering und die Integration von „ QCanvasPainter “ mit „ QRhi “ werden über „ QCanvasRhiPaintDriver “ verwaltet. Das bedeutet, dass Hardwarebeschleunigung genutzt wird, sofern sie für das ausgewählte Backend verfügbar ist.

Hinweis: QRhi ist Teil von Qt GUI Private. Als solches bietet es nur eingeschränkte Kompatibilitätsgarantien. Es gibt keine Garantien hinsichtlich der Quellcode- oder Binärkompatibilität für diese Klassen, was bedeutet, dass die API nur mit der Qt-Version garantiert funktioniert, auf deren Grundlage die Anwendung entwickelt wurde.

Was dieses Beispiel veranschaulicht

  • Das Rendern einer vollständig benutzerdefinierten Benutzeroberfläche in einem „ QWindow “.
  • Erstellen und Verwalten von „ QRhi “, einer Swapchain sowie Depth-Stencil-Ressourcen.
  • Aufzeichnen von Zeichnungsbefehlen über „ QCanvasPainter “ und „ QCanvasRhiPaintDriver “.
  • Ausführen einer kontinuierlichen Animation mit ` QWindow::requestUpdate()`.

Schritt-für-Schritt-Anleitung

Die Anwendung wählt standardmäßig eine plattformabhängige Rendering-API aus. Sie können auch über Befehlszeilenargumente ein bestimmtes Backend angeben:

  • --null
  • --opengl
  • --vulkan
  • --d3d11
  • --d3d12
  • --metal

Sollte die Erstellung des Backends fehlschlagen, versucht das Beispiel, auf OpenGL ES 2 zurückzugreifen, sofern dieses verfügbar ist.

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    app.setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents);

    QRhi::Implementation graphicsApi;
#if defined(Q_OS_WIN)
    graphicsApi = QRhi::D3D11;
#elif QT_CONFIG(metal)
    graphicsApi = QRhi::Metal;
#elif QT_CONFIG(vulkan)
    graphicsApi = QRhi::Vulkan;
#else
    graphicsApi = QRhi::OpenGLES2;
#endif

    QCommandLineParser cmdLineParser;
    cmdLineParser.addHelpOption();
    QCommandLineOption nullOption({ "n", "null" }, QLatin1String("Null"));
    cmdLineParser.addOption(nullOption);
    QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL"));
    cmdLineParser.addOption(glOption);
    QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan"));
    cmdLineParser.addOption(vkOption);
    QCommandLineOption d3d11Option({ "d", "d3d11" }, QLatin1String("Direct3D 11"));
    cmdLineParser.addOption(d3d11Option);
    QCommandLineOption d3d12Option({ "D", "d3d12" }, QLatin1String("Direct3D 12"));
    cmdLineParser.addOption(d3d12Option);
    QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal"));
    cmdLineParser.addOption(mtlOption);

    cmdLineParser.process(app);

   if (cmdLineParser.isSet(nullOption))
        graphicsApi = QRhi::Null;
    if (cmdLineParser.isSet(glOption))
        graphicsApi = QRhi::OpenGLES2;
    if (cmdLineParser.isSet(vkOption))
        graphicsApi = QRhi::Vulkan;
    if (cmdLineParser.isSet(d3d11Option))
        graphicsApi = QRhi::D3D11;
    if (cmdLineParser.isSet(d3d12Option))
        graphicsApi = QRhi::D3D12;
    if (cmdLineParser.isSet(mtlOption))
        graphicsApi = QRhi::Metal;

#if QT_CONFIG(opengl)
    QSurfaceFormat fmt;
    fmt.setDepthBufferSize(24);
    fmt.setStencilBufferSize(8);
#  ifdef Q_OS_MACOS
    fmt.setVersion(4, 1);
    fmt.setProfile(QSurfaceFormat::CoreProfile);
#  endif
    QSurfaceFormat::setDefaultFormat(fmt);
#endif

#if QT_CONFIG(vulkan)
    QVulkanInstance vulkanInstance;
    if (graphicsApi== QRhi::Vulkan) {
        vulkanInstance.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions());
        if (!vulkanInstance.create()) {
#if QT_CONFIG(opengl)
            qWarning("Vulkan is not usable, falling back to OpenGL");
            graphicsApi = QRhi::OpenGLES2;
#else
            qWarning("Vulkan is not usable, falling back to the null backend");
            graphicsApi = QRhi::Null;
#endif
        }
    }
#endif

    MainWindow window(graphicsApi);

#if QT_CONFIG(vulkan)
    if (graphicsApi== QRhi::Vulkan)
        window.setVulkanInstance(&vulkanInstance);
#endif

    window.resize(1920 / 2, 1080 / 2);
    window.show();

    return app.exec();
}

MainWindow ist eine Unterklasse von ` PainterWindow`, das wiederum eine Unterklasse von ` QWindow` ist. ` PainterWindow ` implementiert ein Fenster, das Inhalte anzeigt, die über ` QRhi` – die Grafikabstraktion von Qt – gerendert werden. Es initialisiert eine ` QRhi `-Instanz, wenn das Fenster angezeigt wird, verwaltet eine Swapchain sowie einen Depth-Stencil-Puffer, verarbeitet Größenänderungsereignisse und ruft eine virtuelle Funktion ` paint() ` auf, die von ` MainWindow` implementiert wird.

Zum besseren Verständnis der Implementierung von ` PainterWindow ` empfiehlt es sich, das RHI-Fenster-Beispiel anzusehen, da es im Wesentlichen der Klasse ` RhiWindow ` in diesem Beispiel sehr ähnlich ist.

Ein für „ QCanvasPainter “ spezifischer Schritt besteht darin, eine „ QCanvasPainterFactory “-Instanz abzurufen, nachdem „ QRhi “ initialisiert wurde:

if (!m_factory) {
    m_factory = new QCanvasPainterFactory;
    m_factory->create(m_rhi.get());
}

Das Rendern eines neuen Frames erfolgt in der Funktion „ render() “, die aufgerufen wird, wenn das Fenster angezeigt oder in der Größe angepasst wird sowie als Reaktion auf Aktualisierungsanforderungen, die über „ requestUpdate()“ geplant wurden.

Anders als im RHI-Fenster-Beispiel werden wir keine Vertex- und Uniform-Buffer oder Grafik-Pipelines über die QRhi -API erstellen und Draw-Calls direkt auslösen. Stattdessen wird QCanvasRhiPaintDriver verwendet, um einen Render-Durchlauf mit der von QCanvasPainter generierten Darstellung aufzuzeichnen.

void PainterWindow::render()
{
    if (!m_factory|| !m_factory->isValid()|| !m_rhi|| !m_sc)
        return;

    if (!m_hasSwapChain|| m_notExposed)
        return;

   if (m_sc->currentPixelSize() != m_sc->surfacePixelSize()|| m_newlyExposed) {
        resizeSwapChain();
       if (!m_hasSwapChain)
            return;
        m_newlyExposed = false;
    }

    QRhi::FrameOpResult r = m_rhi->beginFrame(m_sc.get());
    if (r== QRhi::FrameOpSwapChainOutOfDate) {
        resizeSwapChain();
        if (!m_hasSwapChain)
            return;
        r = m_rhi->beginFrame(m_sc.get());
    }
    if (r != QRhi::FrameOpSuccess) {
        qDebug("beginFrame failed with %d, retry", r);
        requestUpdate();
        return;
    }

    QRhiCommandBuffer*cb = m_sc->currentFrameCommandBuffer();
    QRhiRenderTarget*rt = m_sc->currentFrameRenderTarget();

    QCanvasRhiPaintDriver*pd = m_factory->paintDriver();
    QCanvasPainter*painter = m_factory->painter();

    pd->resetForNewFrame();
    pd->beginPaint(cb, rt, m_fillColor, size(), float(devicePixelRatio()));
    paint(painter);
    pd->endPaint();

    m_rhi->endFrame(m_sc.get());
}

Die Implementierung von ` paint() ` in `MainWindow` nutzt die ` QCanvasPainter `-API und muss sich nicht direkt um die Details der ` QRhi ` auf niedrigerer Ebene kümmern.

Zunächst lädt und registriert es Bilder, falls dies noch nicht geschehen ist:

void MainWindow::paint(QCanvasPainter *p)
{
    if (!m_initialized) {
        auto flags = QCanvasPainter::ImageFlag::GenerateMipmaps;
        m_b1ImageLight = p->addImage(QImage(":/images/icon_random_light.png"), flags);
        m_b1ImageDark = p->addImage(QImage(":/images/icon_random_dark.png"), flags);
        m_b2ImageLight = p->addImage(QImage(":/images/icon_theme_light.png"), flags);
        m_b2ImageDark = p->addImage(QImage(":/images/icon_theme_dark.png"), flags);
        m_b3ImageLight = p->addImage(QImage(":/images/icon_settings_light.png"), flags);
        m_b3ImageDark = p->addImage(QImage(":/images/icon_settings_dark.png"), flags);
        m_sImageLight = p->addImage(QImage(":/images/icon_run_light.png"), flags);
        m_sImageDark = p->addImage(QImage(":/images/icon_run_dark.png"), flags);
        m_initialized = true;
    }

Darauf folgt das eigentliche Zeichnen, das das Ausfüllen und Umranden von Pfaden, das Rendern von Text und das Zeichnen von Bildern umfasst. Zum Beispiel:

// Highlight pressed button
if (m_selectedButton) {
    p->beginPath();
    p->roundRect(m_views[m_selectedButton].rect, viewRadius);
    p->setLineWidth(2.0f * m_px);
    p->setStrokeStyle(m_theme.highlight());
    p->stroke();
}

QCanvasPainter Zeichenbefehle bereiten die Geometrie, den Status und die „ QRhi “ für die interne Zeichenarbeit vor. Der Befehlsstrom wird geleert, wenn „ endPaint()“ in „ PainterWindow::render() “ aufgerufen wird. Dort wird ein Renderdurchlauf auf dem zugehörigen „ QRhiCommandBuffer “ aufgezeichnet.

Vor der Rückgabe ruft ` MainWindow::paint() ` die Funktion ` requestUpdate()` auf, eine Funktion von ` QWindow `, die einen neuen Frame anfordert, der mit der Bildwiederholfrequenz des Displays synchronisiert ist. Dadurch wird der Inhalt für die Animation kontinuierlich aktualisiert.

Einzelheiten zur Implementierung finden Sie im vollständigen Beispielquellcode.

Beispielprojekt @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.