창 임베딩

Qt가 아닌 UI 요소를 Qt 애플리케이션에 임베드하는 방법에 대한 데모입니다.

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

이러한 컨트롤을 통합하기 위해 Qt의 QWindow 추상화를 기반으로 네이티브 UI 컨트롤의 QWindow 표현을 생성하여 Qt UI에 임베드할 수 있습니다. 이렇게 생성된 창은 외부(Qt용) UI 툴킷에서 생성된 컨트롤을 나타내기 때문에 Qt에서는 외부 창이라고 합니다.

외부 창 생성하기

QWindow 표현을 만들려면 QWindow::fromWinId()을 사용하여 불투명 WId 유형으로 표시되는 네이티브 창 핸들에 대한 참조를 전달합니다.

각 플랫폼은 WId 불투명 유형이 어떤 네이티브 유형에 매핑되는지 정의합니다.

플랫폼WId 유형
macOSNSView*
WindowsHWND
X11xcb_window_t
iOSUIView*
Android보기

결과는 기본 창 핸들을 나타내는 QWindow 입니다.

참고: Qt는 외부 창을 생성할 때 네이티브 창 핸들에 대한 (독점적인) 소유권을 가지지 않으므로, 애플리케이션은 외부 QWindow 의 수명 동안 네이티브 창을 유지할 책임이 있습니다.

이제 QWindow::fromWinId()을 사용하여 QWindow 을 만들려면 먼저 네이티브 창 핸들이 필요합니다. 이 예제에서는 대부분의 플랫폼에서 기본 UI 툴킷에 월 캘린더 컨트롤을 포함하거나 쉽게 사용할 수 있으므로 월 캘린더 컨트롤을 포함하겠습니다. 각 플랫폼에서 캘린더를 만드는 방법에 대한 자세한 내용은 아래 코드 스니펫에 나와 있습니다.

네이티브 핸들이 계속 살아있으면서도 애플리케이션 종료 시 적절하게 정리되도록 하기 위해 main() 에서 반환하기 전에 실행하는 정리 함수 목록을 유지합니다.

네이티브 창 핸들을 생성하고 이를 QWindow 로 변환하는 것 외에도 네이티브 툴킷이 캘린더 컨트롤의 선호되는 최소 크기를 알려줄 수 있다는 점을 기반으로 결과물인 QWindow 에 최소 크기를 설정합니다. 이렇게 하면 Qt가 임베디드 외부 창을 적절하게 배치할 수 있습니다.

X11#x11

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

외부 창 삽입하기

이제 외부 창 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()를 통해 QWindowQWidget 표현을 생성합니다.

그런 다음 위젯을 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 {
    title: "Qt Quick"
    color: "#2CDE85"

    required property QtObject calendarWindow;

    property int contentsMargins: 20

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

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

이 예제에서는 외부 창이 컨텍스트 프로퍼티로 QML 엔진에 노출되지만 애플리케이션의 필요에 따라 다른 방식으로 해결할 수 있습니다.

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

예제 프로젝트 @ code.qt.io

© 2025 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.