Sur cette page

Exemple d'intégration de fenêtres

Démonstration de l'intégration d'éléments d'interface utilisateur non Qt dans des applications Qt.

Qt offre une large gamme de contrôles d'interface utilisateur pour les applications basées sur Qt Widget et Qt Quick, mais il peut parfois être souhaitable d'utiliser des contrôles provenant d'autres boîtes à outils d'interface utilisateur, telles que la boîte à outils d'interface utilisateur native de la plate-forme.

Pour intégrer ces contrôles, nous nous appuyons sur l'abstraction QWindow de Qt, en créant une représentation QWindow du contrôle de l'interface utilisateur native, qui peut ensuite être intégrée dans l'interface utilisateur de Qt. Une fenêtre créée de cette manière est connue dans Qt comme une fenêtre étrangère, car elle représente un contrôle créé par une boîte à outils d'interface utilisateur étrangère (à Qt).

Création d'une fenêtre étrangère

Pour créer la représentation QWindow, nous utilisons QWindow::fromWinId(), en passant une référence à une poignée de fenêtre native, représentée par le type opaque WId.

Chaque plateforme définit à quel type natif correspond le type opaque WId.

Plate-formeType WId
macOSNSView*
WindowsHWND
X11xcb_window_t
iOSUIView*
AndroidVue d'ensemble
WebAssemblyemscripten::val*

Le résultat est un QWindow qui représente la poignée de la fenêtre native.

Remarque : Qt XML ne prend pas la propriété (exclusive) de la poignée de la fenêtre native lors de la création d'une fenêtre étrangère, de sorte que l'application est responsable de maintenir la fenêtre native en vie pendant la durée de vie de la fenêtre étrangère QWindow.

Avant de pouvoir créer une fenêtre QWindow à l'aide de QWindow::fromWinId(), nous avons besoin d'une poignée de fenêtre native. Dans cet exemple, nous intégrerons un contrôle de calendrier mensuel, car la plupart des plates-formes l'ont dans leur boîte à outils d'interface utilisateur native, ou sont facilement disponibles. Les détails de la création du calendrier sur chaque plateforme sont présentés dans les extraits de code ci-dessous.

Pour s'assurer que la poignée native reste vivante, mais qu'elle est également nettoyée correctement à la sortie de l'application, nous maintenons une liste de fonctions de nettoyage que nous exécutons avant de retourner sur main().

En plus de créer la poignée de fenêtre native et de la transformer en QWindow, nous définissons également une taille minimale pour la fenêtre QWindow, en fonction de ce que la boîte à outils native peut nous dire sur la taille minimale préférée du contrôle de calendrier. Cela permet à Qt de présenter correctement la fenêtre étrangère intégrée.

#include <AppKit/NSDatePicker.h>
#include <AppKit/NSLayoutConstraint.h>

QWindow *createCalendarWindow()
{
    auto *datePicker = [NSDatePicker new];
    cleanupFunctions.push_back([=]{ [datePicker release]; });

    datePicker.datePickerStyle = NSDatePickerStyleClockAndCalendar;
    datePicker.datePickerElements = NSDatePickerElementFlagYearMonthDay;
    datePicker.drawsBackground = YES;
    datePicker.dateValue = [NSDate now];

    auto *calendarWindow = QWindow::fromWinId(WId(datePicker));
    calendarWindow->setMinimumSize(QSizeF::fromCGSize(datePicker.fittingSize).toSize());

    return calendarWindow;
}

#include <windows.h>
#include <commctrl.h>

QWindow *createCalendarWindow()
{
    static bool initializedDateControl = []{
        INITCOMMONCONTROLSEX icex;
        icex.dwSize = sizeof(icex);
        icex.dwICC = ICC_DATE_CLASSES;
        return InitCommonControlsEx(&icex);
    }();
    Q_ASSERT(initializedDateControl);

    HWND monthCalendar = CreateWindow(MONTHCAL_CLASSW,
        nullptr, MCS_NOTODAYCIRCLE | MCS_NOTODAY, 0, 0, 0, 0,
        nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
    cleanupFunctions.push_back([=]{ DestroyWindow(monthCalendar); });

    auto *calendarWindow = QWindow::fromWinId(WId(monthCalendar));

    RECT minimumSize;
    MonthCal_GetMinReqRect(monthCalendar, &minimumSize);
    const auto dpr = calendarWindow->devicePixelRatio();
    calendarWindow->setMinimumSize(QSize(
        minimumSize.right / dpr,minimumSize.bottom / dpr));

    return calendarWindow;
}

#include <gtk/gtk.h>
#include <gtk/gtkx.h>

QWindow *createCalendarWindow()
{
    static bool initializedGTK = []{
        qputenv("GDK_BACKEND", "x11");
        return gtk_init_check(nullptr, nullptr);
    }();
    Q_ASSERT(initializedGTK);

    auto *plug = gtk_plug_new(0);
    g_signal_connect(GTK_WIDGET(plug), "delete-event", G_CALLBACK(+[]{
        return true; // Don't destroy on close
    }), nullptr);
    cleanupFunctions.push_back([=]{ gtk_widget_destroy(GTK_WIDGET(plug)); });

    auto *calendar = gtk_calendar_new();
    gtk_container_add(GTK_CONTAINER(plug), GTK_WIDGET(calendar));
    gtk_widget_show_all(plug);

    auto *calendarWindow = QWindow::fromWinId(gtk_plug_get_id(GTK_PLUG(plug)));

    GtkRequisition minimumSize;
    gtk_widget_get_preferred_size(calendar, &minimumSize, NULL);
    calendarWindow->setMinimumSize(QSize(minimumSize.width, minimumSize.height));

    return calendarWindow;
}

#include <UIKit/UIDatePicker.h>

QWindow *createCalendarWindow()
{
    auto *datePicker = [UIDatePicker new];
    cleanupFunctions.push_back([=]{ [datePicker release]; });

    datePicker.datePickerMode = UIDatePickerModeDate;
    datePicker.preferredDatePickerStyle = UIDatePickerStyleInline;
    datePicker.backgroundColor = UIColor.systemBackgroundColor;

    auto *calendarWindow = QWindow::fromWinId(WId(datePicker));
    calendarWindow->setMinimumSize(QSizeF::fromCGSize(datePicker.frame.size).toSize());

    return calendarWindow;
}

Q_DECLARE_JNI_CLASS(CalendarView, "android/widget/CalendarView")
Q_DECLARE_JNI_CLASS(Color, "android/graphics/Color")

QWindow *createCalendarWindow()
{
    using namespace QtJniTypes;
    using namespace QNativeInterface;

    auto *androidApp = qGuiApp->nativeInterface<QAndroidApplication>();
    Q_ASSERT(androidApp);

    auto *calendarView = new CalendarView(androidApp->context());
    cleanupFunctions.push_back([=]{ delete calendarView; });

    // Resolving Android default colors is not trivial, so let's ask Qt
    QColor paletteColor = qGuiApp->palette().color(QPalette::Window);
    int backgroundColor = Color::callStaticMethod<int>("rgb",
        paletteColor.red(), paletteColor.green(), paletteColor.blue());
    calendarView->callMethod<void>("setBackgroundColor", backgroundColor);

    auto *calendarWindow = QWindow::fromWinId(WId(calendarView->object()));
    calendarWindow->setMinimumSize(QSize(200, 220));

    return calendarWindow;
}

#include <emscripten.h>
#include <emscripten/val.h>
using emscripten::val;
using emscripten::EM_VAL;

EM_JS(EM_VAL, createCalendarElement, (), {
    var calendar = document.createElement("calendar-date");
    calendar.innerHTML = "<calendar-month></calendar-month>";
    return Emval.toHandle(calendar);
});

QWindow *createCalendarWindow()
{
    static bool initializedCalendarComponent = []{
        return EM_ASM_INT(
            var script = document.createElement('script');
            script.src = "https://unpkg.com/cally";
            script.type = "module";
            document.head.appendChild(script);
            return true;
        );
    }();
    Q_ASSERT(initializedCalendarComponent);

    val *calendarElement = new val(val::take_ownership(createCalendarElement()));
    cleanupFunctions.push_back([calendarElement]{ delete calendarElement; });

    QWindow *window = QWindow::fromWinId(WId(calendarElement));
    window->setMinimumSize(QSize(250, 300));
    return window;
}

Intégrer une fenêtre étrangère

Maintenant que nous avons une fenêtre étrangère QWindow, nous pouvons l'intégrer dans une interface utilisateur Qt. Plusieurs options s'offrent à nous, comme décrit ci-dessous.

Intégrer dans Qt GUI

Au niveau le plus bas, nous pouvons intégrer la fenêtre étrangère en la répartissant dans une autre QWindow, via QWindow::setParent(). Cette approche laisse au développeur de l'application le soin de gérer le positionnement, le redimensionnement et d'autres aspects de la gestion de la fenêtre enfant intégrée, c'est pourquoi nous déconseillons généralement l'intégration à ce niveau, si cela est possible.

Dans cet exemple, nous commençons par créer une fenêtre conteneur minimale.

class ContainerWindow : public QRasterWindow
{
protected:
    bool event(QEvent *event) override
    {
        if (event->type() == QEvent::ChildWindowAdded) {
            auto *childWindow = static_cast<QChildWindowEvent*>(event)->child();
            childWindow->resize(childWindow->minimumSize());
            setMinimumSize(childWindow->size().grownBy(contentsMargins));
            resize(minimumSize());
        }

        return QRasterWindow::event(event);
    }

    void showEvent(QShowEvent *) override
    {
        findChild<QWindow*>()->setVisible(true);
    }

    void resizeEvent(QResizeEvent *) override
    {
        auto *containedWindow = findChild<QWindow*>();
        containedWindow->setPosition(
            (width() / 2)  - containedWindow->width() / 2,
            (height() / 2) - containedWindow->height() / 2
        );
    }

    void paintEvent(QPaintEvent *) override
    {
        QPainter painter(this);
        painter.fillRect(0, 0, width(), height(), "#00414A");
    }
};

Dans cet exemple, nous créons d'abord une implémentation minimale de fenêtre conteneur, dans laquelle nous pouvons ensuite répartir notre fenêtre étrangère.

ContainerWindow window;
window.setTitle("Qt Gui");

auto *calendarWindow = createCalendarWindow();
calendarWindow->setParent(&window);
Intégration dans Qt Widgets

Pour les applications construites sur la pile d'interface utilisateur Qt Widgets, nous suivons la même approche que pour QWindow::fromWinId(), en créant une représentation QWidget de QWindow, via QWidget::createWindowContainer().

Nous pourrions ensuite répartir le widget dans un autre widget, via QWidget::setParent(), avec les mêmes réserves que pour l'exemple Qt GUI ci-dessus, à savoir qu'il faut gérer manuellement le positionnement, le redimensionnement, etc. Dans cet exemple, nous préférons ajouter le widget contenant la fenêtre à un QVBoxLayout, ce qui nous permet de centrer automatiquement la fenêtre étrangère à l'intérieur du widget de niveau supérieur.

QWidget widget;
widget.setPalette(QColor("#CDB0FF"));
widget.setWindowTitle("Qt Widgets");
widget.setLayout(new QVBoxLayout);
widget.layout()->setContentsMargins(contentsMargins);
widget.layout()->setAlignment(Qt::AlignCenter);

auto *calendarWidget = QWidget::createWindowContainer(createCalendarWindow());
widget.layout()->addWidget(calendarWidget);
Intégration dans Qt Quick

Enfin, pour les applications construites sur la pile d'interface utilisateur Qt Quick, nous utilisons l'élément WindowContainer pour gérer la fenêtre étrangère.

Window {
    id: root
    title: "Qt Quick"
    color: "#2CDE85"

    property alias calendarWindow: calendar.window

    property int contentsMargins: 20

    minimumWidth: calendarWindow.minimumWidth + contentsMargins * 2
    minimumHeight: calendarWindow.minimumHeight + contentsMargins * 2

    WindowContainer {
        id: calendar
        width: window.minimumWidth
        height: window.minimumHeight
        anchors.centerIn: parent
    }
}

Dans cet exemple, la fenêtre étrangère est exposée au moteur QML en tant que propriété de contexte, mais cela pourrait être résolu de différentes manières en fonction des besoins de l'application.

QQmlApplicationEngine engine;
engine.setInitialProperties({{ "calendarWindow", QVariant::fromValue(createCalendarWindow()) }});
engine.loadFromModule("windowembedding", "Main");

Exemple de projet @ 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.