이 페이지에서

창 호스팅 예제

Qt 애플리케이션에서 Qt가 아닌 UI 요소를 호스팅하는 방법을 보여주는 예제입니다.

Qt는 Qt Widgets 및 Qt Quick 기반 애플리케이션 모두를 위해 광범위한 UI 컨트롤을 제공하지만, 때로는 플랫폼의 네이티브 UI 툴킷과 같은 다른 UI 툴킷의 컨트롤을 사용하는 것이 바람직할 수도 있습니다.

이러한 컨트롤을 통합하기 위해, Qt의 ` QWindow ` 추상화를 기반으로 네이티브 UI 컨트롤의 ` QWindow ` 표현을 생성한 다음, 이를 Qt UI 내에 호스팅합니다. 이러한 방식으로 생성된 창은 (Qt에 대해) 외부 UI 툴킷으로 생성된 컨트롤을 나타내므로, Qt에서는 ‘외부 창( foreign window)’으로 알려져 있습니다.

외부 창 생성

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 UI에 호스팅할 수 있습니다. 아래에서 설명하듯이, 여기에는 몇 가지 옵션이 있습니다.

다음 위치에 호스팅하기 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 예제와 마찬가지로 위치 지정, 크기 조정 등을 수동으로 관리해야 한다는 점에 유의해야 합니다. 이 예제에서는 window 컨테이너 위젯을 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 property)으로 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.