레이아웃 관리
Qt 레이아웃 시스템은 위젯 내의 자식 위젯들을 자동으로 배열하여 사용 가능한 공간을 효율적으로 활용할 수 있도록 하는 간단하면서도 강력한 방법을 제공합니다.
소개
Qt에는 애플리케이션의 사용자 인터페이스에서 위젯이 어떻게 배치되는지를 정의하는 데 사용되는 일련의 레이아웃 관리 클래스가 포함되어 있습니다. 이러한 레이아웃은 위젯에 할당된 공간이 변경될 때 위젯의 위치와 크기를 자동으로 조정하여, 위젯이 일관되게 배열되고 사용자 인터페이스 전체가 사용 가능한 상태를 유지하도록 보장합니다.
모든 ` QWidget `의 하위 클래스는 레이아웃을 사용하여 자식 위젯을 관리할 수 있습니다. ` QWidget::setLayout()` 함수는 위젯에 레이아웃을 적용합니다. 이러한 방식으로 위젯에 레이아웃이 설정되면, 레이아웃은 다음 작업을 담당합니다:
- 자식 위젯의 배치
- 창에 대한 적절한 기본 크기 설정
- 창에 대한 적절한 최소 크기
- 크기 조정 처리
- 내용 변경 시 자동 업데이트:
- 자식 위젯의 글꼴 크기, 텍스트 또는 기타 콘텐츠
- 자식 위젯 숨기기 또는 표시하기
- 자식 위젯 제거
Qt의 레이아웃 클래스
Qt의 레이아웃 클래스는 수동으로 작성하는 C++ 코드를 위해 설계되었으며, 간편성을 위해 측정값을 픽셀 단위로 지정할 수 있도록 되어 있어 이해하고 사용하기 쉽습니다. Qt Widgets Designer 를 사용하여 생성된 폼에 대해 생성된 코드 역시 레이아웃 클래스를 사용합니다. Qt Widgets Designer 는 사용자 인터페이스 개발에 일반적으로 수반되는 컴파일, 링크 및 실행 과정을 생략해 주므로, 폼 디자인을 실험해 볼 때 유용하게 활용할 수 있습니다.
자식 위젯을 수평 또는 수직으로 정렬합니다 | |
버튼 위젯 그룹을 구성하는 컨테이너 | |
입력 위젯과 관련 레이블로 구성된 폼을 관리합니다 | |
QGraphicsAnchorLayout 내 두 항목 간의 앵커를 나타냅니다 | |
Graphics View에서 위젯들을 서로 고정할 수 있는 레이아웃 | |
위젯을 격자 형태로 배치합니다 | |
제목이 있는 그룹 박스 프레임 | |
위젯을 수평으로 정렬합니다 | |
지오메트리 매니저의 기본 클래스 | |
QLayout이 조작하는 추상 항목 | |
가로 및 세로 크기 조정 정책을 설명하는 레이아웃 속성 | |
레이아웃 내의 빈 공간 | |
한 번에 하나의 위젯만 표시되는 위젯 스택 | |
한 번에 하나의 위젯만 표시되는 위젯 스택 | |
위젯을 수직으로 정렬합니다 | |
위젯을 나타내는 레이아웃 항목 |
가로, 세로, 그리드 및 양식 레이아웃
위젯에 적절한 레이아웃을 적용하는 가장 쉬운 방법은 내장된 레이아웃 관리자인 ` QHBoxLayout`, ` QVBoxLayout`, ` QGridLayout` 및 ` QFormLayout`을 사용하는 것입니다. 이 클래스들은 ` QLayout`을 상속받으며, ` `은 ` QObject `(` QWidget`이 아님)에서 파생됩니다. 이 클래스들은 일련의 위젯에 대한 기하학적 관리를 담당합니다. 더 복잡한 레이아웃을 만들려면 레이아웃 관리자를 서로 중첩하여 사용할 수 있습니다.
- QHBoxLayout 는 위젯을 왼쪽에서 오른쪽으로(또는 오른쪽에서 왼쪽으로 읽는 언어의 경우 오른쪽에서 왼쪽으로) 수평 행으로 배치합니다.

- QVBoxLayout 는 위젯을 위에서 아래로 수직 열 형태로 배치합니다.

- QGridLayout 는 위젯을 2차원 그리드 형태로 배치합니다. 위젯은 여러 셀을 차지할 수 있습니다.

- QFormLayout 는 위젯을 ‘라벨-필드’ 형식의 2열 레이아웃으로 배치합니다.

코드를 통한 위젯 배치
다음 코드는 위 첫 번째 스크린샷에 표시된 것처럼 5개의 ` QPushButtons`의 레이아웃을 관리하는 ` QHBoxLayout `을 생성합니다:
QWidget *window = new QWidget;
QPushButton *button1 = new QPushButton("One");
QPushButton *button2 = new QPushButton("Two");
QPushButton *button3 = new QPushButton("Three");
QPushButton *button4 = new QPushButton("Four");
QPushButton *button5 = new QPushButton("Five");
QHBoxLayout *layout = new QHBoxLayout(window);
layout->addWidget(button1);
layout->addWidget(button2);
layout->addWidget(button3);
layout->addWidget(button4);
layout->addWidget(button5);
window->show();QVBoxLayout 의 코드는 레이아웃이 생성되는 줄을 제외하면 동일합니다. QGridLayout 의 코드는 자식 위젯의 행과 열 위치를 지정해야 하기 때문에 약간 다릅니다:
QWidget *window = new QWidget;
QPushButton *button1 = new QPushButton("One");
QPushButton *button2 = new QPushButton("Two");
QPushButton *button3 = new QPushButton("Three");
QPushButton *button4 = new QPushButton("Four");
QPushButton *button5 = new QPushButton("Five");
QGridLayout *layout = new QGridLayout(window);
layout->addWidget(button1, 0, 0);
layout->addWidget(button2, 0, 1);
layout->addWidget(button3, 1, 0, 1, 2);
layout->addWidget(button4, 2, 0);
layout->addWidget(button5, 2, 1);
window->show();세 번째 QPushButton 는 2열을 차지합니다. 이는 QGridLayout::addWidget()의 다섯 번째 인수로 2를 지정함으로써 가능합니다.
QFormLayout 한 행에 두 개의 위젯을 추가하며, 일반적으로 양식 생성을 위해 QLabel 와 QLineEdit 를 함께 사용합니다. 같은 행에 QLabel 와 QLineEdit 를 추가하면 QLineEdit 가 QLabel 의 버디로 설정됩니다. 다음 코드는 QFormLayout 를 사용하여 세 개의 QPushButtons 와 이에 대응하는 QLineEdit 를 한 행에 배치합니다.
QWidget *window = new QWidget;
QPushButton *button1 = new QPushButton("One");
QLineEdit *lineEdit1 = new QLineEdit();
QPushButton *button2 = new QPushButton("Two");
QLineEdit *lineEdit2 = new QLineEdit();
QPushButton *button3 = new QPushButton("Three");
QLineEdit *lineEdit3 = new QLineEdit();
QFormLayout *layout = new QFormLayout(window);
layout->addRow(button1, lineEdit1);
layout->addRow(button2, lineEdit2);
layout->addRow(button3, lineEdit3);
window->show();레이아웃 사용 팁
레이아웃을 사용할 때는 자식 위젯을 생성할 때 부모 위젯을 전달할 필요가 없습니다. 레이아웃은 위젯의 부모를 재설정하는 함수( QWidget::setParent())를 사용하여, 위젯들이 레이아웃이 적용된 위젯의 자식이 되도록 자동으로 처리합니다.
참고: 레이아웃 내의위젯은 레이아웃 자체가 아닌, 레이아웃이 설치된 위젯의 자식입니다. 위젯은 레이아웃이 아닌 다른 위젯만을 부모로 가질 수 있습니다.
레이아웃에서 ` addLayout() `를 사용하여 레이아웃을 중첩할 수 있으며, 이 경우 내부 레이아웃은 삽입된 레이아웃의 자식이 됩니다.
레이아웃에 위젯 추가하기
레이아웃에 위젯을 추가하면 레이아웃 처리는 다음과 같이 진행됩니다:
- 모든 위젯에는 처음에 ` QWidget::sizePolicy()` 및 ` QWidget::sizeHint()`에 따라 일정량의 공간이 할당됩니다.
- 위젯 중 스트레치 계수(stretch factor)가 설정되어 있고 그 값이 0보다 큰 경우, 해당 위젯에는 스트레치 계수에 비례하여 공간이 할당됩니다(아래에서 설명).
- 위젯 중 스트레치 계수가 0으로 설정된 것이 있다면, 다른 위젯이 해당 공간을 차지하지 않는 경우에만 더 많은 공간을 할당받습니다. 이 경우, ‘ Expanding ’ 크기 정책을 가진 위젯에 먼저 공간이 할당됩니다.
- 최소 크기(최소 크기가 지정되지 않은 경우 최소 크기 힌트)보다 적은 공간이 할당된 위젯은 필요한 이 최소 크기의 공간을 할당받습니다. (위젯에 최소 크기나 최소 크기 힌트가 반드시 있어야 하는 것은 아니며, 이 경우 신축 계수가 결정 요인이 됩니다.)
- 최대 크기보다 더 많은 공간을 할당받은 위젯에는 필요한 최대 크기의 공간이 할당됩니다. (위젯에 최대 크기가 지정되어 있지 않은 경우, 신축 계수가 결정 요인이 됩니다.)
확장 계수
위젯은 일반적으로 스트레치 계수가 설정되지 않은 상태로 생성됩니다. 레이아웃에 배치될 때 위젯은 ` QWidget::sizePolicy()` 값이나 최소 크기 힌트 중 더 큰 값에 따라 공간의 일정 비율을 할당받습니다. 스트레치 계수는 위젯 간 공간 할당 비율을 조정하는 데 사용됩니다.
스트레치 팩터가 설정되지 않은 QHBoxLayout 를 사용하여 세 개의 위젯을 배치하면 다음과 같은 레이아웃이 생성됩니다:

각 위젯에 스트레치 계수를 적용하면, 위젯들은 비례적으로 배치되지만(단, 최소 크기 힌트보다 작아지지는 않음), 예를 들어 다음과 같습니다:

레이아웃 내 사용자 정의 위젯
자신만의 위젯 클래스를 만들 때는 해당 클래스의 레이아웃 속성도 명시해야 합니다. 위젯이 Qt Widgets의 레이아웃 중 하나를 사용하는 경우, 이 부분은 이미 처리되어 있습니다. 위젯에 자식 위젯이 없거나 수동 레이아웃을 사용하는 경우, 다음 메커니즘 중 하나 또는 모두를 사용하여 위젯의 동작을 변경할 수 있습니다:
- QWidget::sizeHint() 메서드를 재구현하여 위젯의 선호 크기를 반환합니다.
- QWidget::minimumSizeHint()을 재구현하여 위젯이 가질 수 있는 최소 크기를 반환합니다.
- QWidget::setSizePolicy()를 호출하여 위젯이 필요로 하는 공간을 지정합니다.
크기 힌트, 최소 크기 힌트 또는 크기 정책이 변경될 때마다 ` QWidget::updateGeometry()`를 호출하십시오. 이렇게 하면 레이아웃이 재계산됩니다. ` QWidget::updateGeometry()`를 연속해서 여러 번 호출하더라도 레이아웃 재계산은 한 번만 수행됩니다.
위젯의 선호 높이가 실제 너비에 따라 달라지는 경우(예: 자동 단어 분할 기능이 있는 레이블), 위젯의 ` size policy `에서 ` height-for-width ` 플래그를 설정하고 ` QWidget::heightForWidth()`를 재구현하십시오.
QWidget::heightForWidth()을 구현하더라도, 적절한 sizeHint()를 제공하는 것이 좋습니다.
이러한 함수를 구현할 때 필요한 추가 지침은 Qt Quarterly의 “Trading Height for Width” 기사를 참조하십시오.
레이아웃 문제
라벨 위젯에서 서식 있는 텍스트를 사용하면 상위 위젯의 레이아웃에 몇 가지 문제가 발생할 수 있습니다. 이러한 문제는 라벨에서 줄 바꿈이 발생할 때 Qt의 레이아웃 관리자가 서식 있는 텍스트를 처리하는 방식 때문에 발생합니다.
특정 경우, 부모 레이아웃이 QLayout::FreeResize 모드로 설정되는데, 이는 작은 크기의 창에 내용물이 들어맞도록 레이아웃을 조정하지 않거나, 심지어 사용자가 창을 사용할 수 없을 정도로 너무 작게 만드는 것을 방지하기도 합니다. 이러한 문제는 문제가 되는 위젯을 서브클래싱하고, 적절한 ` sizeHint()` 및 ` minimumSizeHint()` 함수를 구현함으로써 해결할 수 있습니다.
어떤 경우에는 레이아웃이 위젯에 추가되는 시점이 중요합니다. QDockWidget 또는 QScrollArea 의 위젯을 설정할 때( QDockWidget::setWidget() 및 QScrollArea::setWidget()을 사용하여), 해당 위젯에는 이미 레이아웃이 설정되어 있어야 합니다. 그렇지 않으면 위젯이 표시되지 않습니다.
수동 레이아웃
독창적인 특수 레이아웃을 만드는 경우, 위에서 설명한 대로 사용자 정의 위젯을 만들 수도 있습니다. QWidget::resizeEvent()을 재구현하여 필요한 크기 분배를 계산하고, 각 자식 요소에 대해 setGeometry()을 호출하십시오.
레이아웃을 재계산해야 할 때 위젯은 QEvent::LayoutRequest 유형의 이벤트를 수신합니다. QEvent::LayoutRequest 이벤트를 처리하려면 QWidget::event()을 재구현하십시오.
사용자 정의 레이아웃 관리자 작성 방법
수동 레이아웃의 대안으로, ` QLayout`을 상속하여 자체 레이아웃 관리자를 작성할 수 있습니다. ‘Flow Layout’ 예제에서 이를 수행하는 방법을 확인할 수 있습니다.
여기서는 이에 대한 예제를 자세히 살펴보겠습니다. ` CardLayout ` 클래스는 같은 이름의 Java 레이아웃 매니저에서 영감을 받았습니다. 이 클래스는 항목(위젯 또는 중첩된 레이아웃)을 서로 겹쳐 배치하며, 각 항목은 ` QLayout::spacing()`를 통해 오프셋을 적용합니다.
자신만의 레이아웃 클래스를 작성하려면 다음을 정의해야 합니다:
- 레이아웃이 처리하는 항목을 저장할 데이터 구조. 각 항목은 ` QLayoutItem`입니다. 이 예제에서는 ` QList `를 사용할 것입니다.
- addItem(), 레이아웃에 항목을 추가하는 방법.
- setGeometry(), 레이아웃에 항목을 추가하는 방법.
- sizeHint(), 레이아웃의 권장 크기.
- itemAt(), 레이아웃을 순회하는 방법.
- takeAt(), 레이아웃에서 항목을 제거하는 방법.
대부분의 경우, ` minimumSize()`도 구현하게 됩니다.
헤더 파일 (card.h)
#ifndef CARD_H
#define CARD_H
#include <QtWidgets>
#include <QList>
class CardLayout : public QLayout
{
public:
CardLayout(int spacing): QLayout()
{ setSpacing(spacing); }
CardLayout(int spacing, QWidget *parent): QLayout(parent)
{ setSpacing(spacing); }
~CardLayout();
void addItem(QLayoutItem *item) override;
QSize sizeHint() const override;
QSize minimumSize() const override;
int count() const override;
QLayoutItem *itemAt(int) const override;
QLayoutItem *takeAt(int) override;
void setGeometry(const QRect &rect) override;
private:
QList<QLayoutItem *> m_items;
};
#endif구현 파일 (card.cpp)
//#include "card.h"먼저, 리스트에 포함된 항목의 개수를 가져오기 위해 count() 를 정의합니다.
int CardLayout::count() const
{
// QList::size() returns the number of QLayoutItems in m_items
return m_items.size();
}그런 다음 레이아웃을 순회하는 두 개의 함수, 즉 ` itemAt() `와 ` takeAt()`를 정의합니다. 이 함수들은 레이아웃 시스템 내부에서 위젯 삭제를 처리하는 데 사용되며, 애플리케이션 프로그래머도 사용할 수 있습니다.
itemAt() 지정된 인덱스의 항목을 반환합니다. ` takeAt() `는 지정된 인덱스의 항목을 제거하고 이를 반환합니다. 이 경우 리스트 인덱스를 레이아웃 인덱스로 사용합니다. 더 복잡한 데이터 구조를 다루는 다른 경우에는 항목들에 대한 선형 순서를 정의하는 데 더 많은 노력이 필요할 수 있습니다.
QLayoutItem *CardLayout::itemAt(int idx) const
{
// QList::value() performs index checking, and returns nullptr if we are
// outside the valid range
return m_items.value(idx);
}
QLayoutItem *CardLayout::takeAt(int idx)
{
// QList::take does not do index checking
return idx >= 0 && idx < m_items.size() ? m_items.takeAt(idx) : 0;
}addItem() 레이아웃 항목에 대한 기본 배치 전략을 구현합니다. 이 함수는 반드시 구현해야 합니다. 이 함수는 QLayout::add()와, 레이아웃을 부모로 받는 QLayout 생성자에서 사용됩니다. 레이아웃에 매개변수가 필요한 고급 배치 옵션이 있는 경우, QGridLayout::addItem(), QGridLayout::addWidget(), QGridLayout::addLayout()의 행 및 열 스패닝 오버로드와 같은 추가 액세스 함수를 제공해야 합니다.
void CardLayout::addItem(QLayoutItem *item)
{
m_items.append(item);
}레이아웃은 추가된 항목에 대한 책임을 인수합니다. QLayoutItem 는 QObject 를 상속받지 않으므로, 항목을 수동으로 삭제해야 합니다. 소멸자에서 takeAt() 를 사용하여 목록에서 각 항목을 제거한 다음, 해당 항목을 삭제합니다.
CardLayout::~CardLayout()
{
QLayoutItem *item;
while ((item = takeAt(0)))
delete item;
}setGeometry() 함수는 실제로 레이아웃을 수행합니다. 인수로 전달된 사각형에는 margin() 가 포함되지 않습니다. 필요한 경우, 항목 간의 간격으로 spacing() 를 사용하십시오.
void CardLayout::setGeometry(const QRect &r)
{
QLayout::setGeometry(r);
if (m_items.size() == 0)
return;
int w = r.width() - (m_items.count() - 1) * spacing();
int h = r.height() - (m_items.count() - 1) * spacing();
int i = 0;
while (i < m_items.size()) {
QLayoutItem *o = m_items.at(i);
QRect geom(r.x() + i * spacing(), r.y() + i * spacing(), w, h);
o->setGeometry(geom);
++i;
}
}sizeHint() 와 minimumSize() 는 일반적으로 구현 방식이 매우 유사합니다. 두 함수 모두에서 반환되는 크기는 spacing() 를 포함해야 하지만, margin() 는 포함하지 않아야 합니다.
QSize CardLayout::sizeHint() const
{
QSize s(0, 0);
int n = m_items.count();
if (n > 0)
s = QSize(100, 70); //start with a nice default size
int i = 0;
while (i < n) {
QLayoutItem *o = m_items.at(i);
s = s.expandedTo(o->sizeHint());
++i;
}
return s + n * QSize(spacing(), spacing());
}
QSize CardLayout::minimumSize() const
{
QSize s(0, 0);
int n = m_items.count();
int i = 0;
while (i < n) {
QLayoutItem *o = m_items.at(i);
s = s.expandedTo(o->minimumSize());
++i;
}
return s + n * QSize(spacing(), spacing());
}추가 참고 사항
- 이 사용자 정의 레이아웃은 너비에 대한 높이를 처리하지 않습니다.
- QLayoutItem::isEmpty()은 무시됩니다. 즉, 레이아웃은 숨겨진 위젯을 보이는 위젯으로 취급합니다.
- 복잡한 레이아웃의 경우, 계산된 값을 캐싱하면 속도를 크게 높일 수 있습니다. 이 경우, QLayoutItem::invalidate()를 구현하여 캐시된 데이터가 변경되었음을 표시하십시오.
- QLayoutItem::sizeHint() 등의 호출은 비용이 많이 들 수 있습니다. 따라서 나중에 같은 함수 내에서 해당 값이 다시 필요한 경우, 해당 값을 지역 변수에 저장해야 합니다.
- 동일한 함수 내에서 같은 항목에 대해 QLayoutItem::setGeometry()를 두 번 호출해서는 안 됩니다. 항목에 자식 위젯이 여러 개 있는 경우, 레이아웃 관리자가 매번 전체 레이아웃을 수행해야 하므로 이 호출은 매우 비용이 많이 들 수 있습니다. 대신, 기하 정보를 계산한 후 설정해야 합니다. (이는 레이아웃에만 적용되는 것이 아닙니다. 예를 들어, 자체적인 resizeEvent()를 구현하는 경우에도 동일한 방식을 따라야 합니다.)
레이아웃 예제
많은 ` Qt Widgets ` 예제에서는 이미 레이아웃을 사용하고 있지만, 다양한 레이아웃을 보여주기 위한 별도의 예제도 여러 개 존재합니다.
이 예제는 신호와 슬롯을 사용하여 계산기 위젯의 기능을 구현하는 방법과, QGridLayout을 사용하여 자식 위젯을 그리드 형태로 배치하는 방법을 보여줍니다. | |
캘린더 위젯 예제는 QCalendarWidget의 사용법을 보여줍니다. | |
다양한 창 크기에 맞게 위젯을 배치하는 방법을 보여줍니다. | |
QPainter에서 합성 모드가 어떻게 작동하는지 보여줍니다. | |
메뉴 예제는 메인 윈도우 애플리케이션에서 메뉴를 사용하는 방법을 보여줍니다. | |
Simple Tree Model 예제는 Qt의 표준 뷰 클래스와 함께 계층적 모델을 사용하는 방법을 보여줍니다. |
© 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.