En esta página

Ejemplo de incrustación de ventanas

Una demostración de cómo incrustar elementos de interfaz de usuario que no son de Qt en aplicaciones Qt.

Qt ofrece una amplia gama de controles UI para aplicaciones basadas en Qt Widgets y Qt Quick, pero a veces puede ser deseable utilizar controles de otros toolkits UI, como el toolkit UI nativo de la plataforma.

Para integrar estos controles, nos basamos en la abstracción QWindow de Qt, creando una representación QWindow del control de interfaz de usuario nativo, que puede incrustarse en la interfaz de usuario de Qt. Una ventana creada de esta manera se conoce en Qt como ventana externa, ya que representa un control creado por un conjunto de herramientas de interfaz de usuario externo (a Qt).

Creación de una ventana externa

Para crear la representación QWindow utilizamos QWindow::fromWinId(), pasando una referencia a un manejador de ventana nativo, representado por el tipo opaco WId.

Cada plataforma define a qué tipo nativo se asigna el tipo opaco WId.

PlataformaTipo WId
macOSNSView*
WindowsHWND
X11xcb_window_t
iOSUIView*
AndroidVer
WebAssemblyemscripten::val*

El resultado es un QWindow que representa el "handle" de la ventana nativa.

Nota: Qt no toma posesión (exclusiva) del handle de la ventana nativa cuando se crea una ventana externa, por lo que la aplicación es responsable de mantener viva la ventana nativa durante el tiempo de vida de la ventana externa QWindow.

Ahora, antes de que podamos crear una QWindow usando QWindow::fromWinId() necesitamos un manejador de ventana nativo. En este ejemplo, incrustaremos un control de calendario mensual, ya que la mayoría de las plataformas lo tienen en sus kits de herramientas de interfaz de usuario nativa, o de otra manera fácilmente disponible. Los detalles de cómo crear el calendario en cada plataforma se muestran en los siguientes fragmentos de código.

Para asegurar que el "handle" nativo se mantiene vivo, pero también se limpia adecuadamente al salir de la aplicación, mantenemos una lista de funciones de limpieza que ejecutamos antes de volver de main().

Además de crear el manejador de ventana nativo y convertirlo en un QWindow, también establecemos un tamaño mínimo en el QWindow resultante, basándonos en que el toolkit nativo puede decirnos el tamaño mínimo preferido del control de calendario. Esto permite a Qt diseñar adecuadamente la ventana externa incrustada.

#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;
}

Incrustar una ventana externa

Ahora que tenemos una ventana externa QWindow, podemos incrustarla en una interfaz de usuario Qt. Tenemos varias opciones aquí, como se describe a continuación.

Incrustar en Qt GUI

En el nivel más bajo, podemos incrustar la ventana externa reparentándola en otra QWindow, a través de QWindow::setParent(). Este enfoque deja en manos del desarrollador de la aplicación el posicionamiento, redimensionamiento y otros aspectos de la gestión de la ventana hija incrustada, por lo que generalmente desaconsejamos la integración a este nivel, si es posible.

En este ejemplo, primero creamos una implementación mínima de ventana contenedora.

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");
    }
};

A continuación, podemos reparent nuestra ventana extranjera en.

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

auto *calendarWindow = createCalendarWindow();
calendarWindow->setParent(&window);
Integración Qt Widgets

Para las aplicaciones construidas sobre la pila de interfaz de usuario Qt Widgets seguimos el mismo enfoque que para QWindow::fromWinId(), creando una representación QWidget de QWindow, a través de QWidget::createWindowContainer().

Podríamos entonces reparent el widget en otro widget, a través de QWidget::setParent(), con las mismas advertencias que en el ejemplo anterior de Qt Gui de tener que gestionar el posicionamiento, redimensionamiento, etc. manualmente. En este ejemplo preferimos añadir el widget contenedor de la ventana a QVBoxLayout, lo que nos permite centrar automáticamente la ventana externa dentro del widget de nivel superior.

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);
Incrustar en Qt Quick

Finalmente, para aplicaciones construidas sobre la pila Qt Quick UI, utilizamos el elemento WindowContainer para gestionar la ventana ajena.

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
    }
}

En este ejemplo la ventana ajena se expone al motor QML como una propiedad de contexto, pero esto podría resolverse de diferentes maneras dependiendo de las necesidades de la aplicación.

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

Proyecto de ejemplo @ 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.