本页内容

窗口托管示例

演示如何在 Qt 应用程序中托管非 Qt UI 控件。

Qt 为基于 Qt Widgets 和Qt Quick 的应用程序提供了广泛的 UI 控件,但有时可能需要使用其他 UI 工具包中的控件,例如平台的原生 UI 工具包。

为了集成这些控件,我们基于 Qt 中的QWindow 抽象,创建一个原生 UI 控件的QWindow 表示形式,然后将其托管在 Qt UI 中。以这种方式创建的窗口在 Qt 中被称为“外部窗口”,因为它代表了一个由(对 Qt 而言)外部 UI 工具包创建的控件。

创建外部窗口

要创建QWindow 表示,我们使用QWindow::fromWinId(),并传入一个由不透明类型WId表示的原生窗口句柄引用。

每个平台都会定义不透明类型 WId 映射到的本机类型。

平台WId 类型
macOSNSView*
WindowsHWND
X11xcb_window_t
iOSUIView*
Android查看
WebAssemblyemscripten::val*

结果是一个QWindow ,它表示本机窗口句柄。

注意:Qt 在创建外部窗口时不会对本机窗口句柄拥有(独占)所有权,因此应用程序有责任在外部QWindow 的整个生命周期内保持本机窗口处于活动状态。

现在,在使用QWindow::fromWinId() 创建QWindow 之前,我们需要一个本机窗口句柄。在本示例中,我们将托管一个月历控件,因为大多数平台在本机 UI 工具包中都提供了该控件,或者可以轻松获得。以下代码片段展示了在各个平台上创建日历的具体方法。

为了确保原生句柄保持有效,同时在应用程序退出时能被正确清理,我们维护了一个清理函数列表,并在从 `main()` 返回之前执行这些函数。

除了创建原生窗口句柄并将其转换为QWindow 外,我们还会根据原生工具包报告的日历控件首选最小尺寸,为生成的QWindow 设置最小尺寸。这使得

Qt 能正确布局托管的外部窗口。

托管 macOS 日历的 Qt GUI、Qt Widgets 和 Qt Quick 窗口

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

用于托管 Windows 日历的 Qt GUI、Qt Widgets 和 Qt Quick 窗口

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

托管 GTK 日历的 Qt GUI、Qt Widgets 和 Qt Quick 窗口

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

托管 iOS 日历的 Qt GUI、Qt Widgets 和 Qt Quick 容器

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

托管 Android 日历的 Qt GUI、Qt Widgets 和 Qt Quick 容器

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

在 WebAssembly 日历上托管日历的 Qt GUI、Qt Widgets 和 Qt Quick 窗口

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

托管外部窗口

既然我们已经有了一个外部QWindow ,就可以将其嵌入到Qt用户界面中。这里有几种方案,如下所述。

在以下环境中托管Qt GUI

在最低层级,我们可以调用QWindow::setParent(),将外部窗口重新关联到另一个QWindow 上,从而将其托管。这种方法将托管子窗口的定位、调整大小及其他管理方面的处理交由应用程序开发者负责,因此我们通常建议

在该层级进行集成。

在此示例中,我们首先创建一个最简单的容器窗口实现。

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

然后,我们可以将外部窗口重新分配给该容器窗口。

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

auto *calendarWindow = createCalendarWindow();
calendarWindow->setParent(&window);
在Qt Widgets

对于基于Qt Widgets UI 堆栈构建的应用程序,我们采用与QWindow::fromWinId() 相同的方法,通过QWidget::createWindowContainer() 创建QWindow 的QWidget 表示形式。

随后,我们可以通过QWidget::setParent()将该控件重新归入另一个控件之下,但需注意与上文Qt GUI 示例相同的注意事项,即必须手动管理定位、调整大小等操作。在本示例中,我们更倾向于将窗口容器控件添加到QVBoxLayout 中,这样可以自动将外部窗口居中显示在顶级控件内。

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

最后,对于基于Qt Quick UI 堆栈构建的应用程序,我们使用WindowContainer 项来管理外部窗口。

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

在此示例中,外部窗口作为 initial 属性暴露给 QML 引擎,但根据应用程序的需求,这可以通过不同的方式来实现。

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

示例项目 @ 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.