레이아웃 관리
Qt 레이아웃 시스템은 위젯 내에서 자식 위젯을 자동으로 정렬하여 사용 가능한 공간을 잘 활용할 수 있도록 하는 간단하고 강력한 방법을 제공합니다.
소개
Qt에는 어플리케이션의 사용자 인터페이스에서 위젯을 배치하는 방법을 설명하는 데 사용되는 레이아웃 관리 클래스 세트가 포함되어 있습니다. 이러한 레이아웃은 사용 가능한 공간이 변경되면 위젯의 위치와 크기를 자동으로 조정하여 일관되게 배치되고 사용자 인터페이스 전체가 계속 사용할 수 있도록 합니다.
모든 QWidget 하위 클래스는 레이아웃을 사용하여 하위 클래스를 관리할 수 있습니다. QWidget::setLayout () 함수는 위젯에 레이아웃을 적용합니다. 이러한 방식으로 위젯에 레이아웃이 설정되면 다음과 같은 작업을 담당합니다:
- 자식 위젯의 위치 지정
- 창의 합리적인 기본 크기
- 창의 합리적인 최소 크기
- 크기 조정 처리
- 콘텐츠 변경 시 자동 업데이트
- 자식 위젯의 글꼴 크기, 텍스트 또는 기타 콘텐츠
- 자식 위젯 숨기기 또는 표시하기
- 자식 위젯 제거
Qt의 레이아웃 클래스
Qt의 레이아웃 클래스는 손으로 작성한 C++ 코드를 위해 설계된 것으로, 픽셀 단위로 치수를 지정할 수 있어 이해하기 쉽고 사용하기 쉽습니다. Qt Widgets Designer 을 사용하여 생성된 폼용 코드도 레이아웃 클래스를 사용합니다. Qt Widgets Designer 은 사용자 인터페이스 개발에 일반적으로 수반되는 컴파일, 링크, 실행 사이클을 피할 수 있으므로 폼 디자인을 실험할 때 유용합니다.
자식 위젯을 가로 또는 세로로 정렬합니다. | |
버튼 위젯 그룹을 구성하는 컨테이너 | |
입력 위젯의 형태와 관련 레이블을 관리합니다. | |
QGraphicsAnchorLayout에서 두 항목 사이의 앵커를 나타냅니다. | |
그래픽 보기에서 위젯을 함께 앵커링할 수 있는 레이아웃 | |
그리드에 위젯을 배치합니다. | |
제목이 있는 그룹 상자 프레임 | |
위젯을 가로로 정렬 | |
지오메트리 관리자의 베이스 클래스 | |
QLayout이 조작하는 추상 항목 | |
가로 및 세로 크기 조정 정책을 설명하는 레이아웃 속성 | |
레이아웃의 빈 공간 | |
한 번에 하나의 위젯만 표시되는 위젯 스택 | |
한 번에 하나의 위젯만 표시되는 위젯 스택 | |
위젯을 세로로 정렬 | |
위젯을 나타내는 레이아웃 항목 |
가로, 세로, 그리드 및 양식 레이아웃
위젯에 좋은 레이아웃을 부여하는 가장 쉬운 방법은 기본 제공 레이아웃 관리자를 사용하는 것입니다: QHBoxLayout, QVBoxLayout, QGridLayout, QFormLayout 입니다. 이러한 클래스는 QLayout 에서 상속되며, 는 다시 QObject ( QWidget 가 아님)에서 파생됩니다. 이들은 위젯 세트의 지오메트리 관리를 처리합니다. 더 복잡한 레이아웃을 만들려면 레이아웃 관리자를 서로 중첩할 수 있습니다.
- QHBoxLayout 은 위젯을 왼쪽에서 오른쪽(오른쪽에서 왼쪽 언어의 경우 오른쪽에서 왼쪽)으로 가로로 배치합니다.
- QVBoxLayout 은 위젯을 위에서 아래로 세로 열에 배치합니다.
- QGridLayout 은 위젯을 2차원 그리드에 배치합니다. 위젯은 여러 셀을 차지할 수 있습니다.
- QFormLayout 은 위젯을 2열 설명형 레이블 필드 스타일로 배치합니다.
코드에서 위젯 배치하기
다음 코드는 위의 첫 번째 스크린샷에 표시된 것처럼 다섯 개의 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()에 따라 공간을 할당받습니다.
- 위젯에 0보다 큰 값으로 스트레치 팩터가 설정되어 있는 경우 해당 위젯은 스트레치 팩터에 비례하여 공간이 할당됩니다(아래 설명 참조).
- 위젯 중 스트레치 계수가 0으로 설정된 위젯이 있는 경우 다른 위젯이 공간을 원하지 않는 경우에만 더 많은 공간을 확보하게 됩니다. 이 중 Expanding 크기 정책을 가진 위젯에 공간이 먼저 할당됩니다.
- 최소 크기(또는 최소 크기가 지정되지 않은 경우 최소 크기 힌트)보다 적은 공간이 할당된 모든 위젯에는 필요한 최소 크기가 할당됩니다. (위젯에는 최소 크기 또는 최소 크기 힌트가 없을 수 있으며, 이 경우 스트레치 인자가 크기를 결정하는 요소입니다.)
- 최대 크기보다 더 많은 공간이 할당된 위젯에는 필요한 최대 크기의 공간이 할당됩니다. (위젯은 최대 크기를 가질 필요가 없으며 이 경우 스트레치 계수가 크기를 결정하는 요소입니다.)
스트레치 팩터
위젯은 일반적으로 스트레치 팩터 설정 없이 생성됩니다. 레이아웃에 배치될 때 위젯은 QWidget::sizePolicy() 또는 최소 크기 힌트 중 더 큰 값에 따라 공간을 할당받습니다. 스트레치 팩터는 위젯이 서로 비례하여 할당되는 공간의 양을 변경하는 데 사용됩니다.
스트레치 인자가 설정되지 않은 QHBoxLayout 을 사용하여 세 개의 위젯을 배치한 경우 다음과 같은 레이아웃이 표시됩니다:
각 위젯에 스트레치 인자를 적용하면 다음과 같이 위젯이 비례하여 배치됩니다(단, 최소 크기 힌트보다 작지는 않음).
레이아웃의 사용자 정의 위젯
자신만의 위젯 클래스를 만들 때는 레이아웃 속성도 전달해야 합니다. 위젯이 Qt의 레이아웃 중 하나를 사용하는 경우, 이것은 이미 처리되어 있습니다. 위젯에 자식 위젯이 없거나 수동 레이아웃을 사용하는 경우, 다음 메커니즘 중 일부 또는 전부를 사용하여 위젯의 동작을 변경할 수 있습니다:
- QWidget::sizeHint()를 다시 구현하여 위젯의 기본 크기를 반환합니다.
- QWidget::minimumSizeHint()를 다시 구현하여 위젯이 가질 수 있는 가장 작은 크기를 반환합니다.
- QWidget::setSizePolicy()를 호출하여 위젯의 공간 요구 사항을 지정합니다.
크기 힌트, 최소 크기 힌트 또는 크기 정책이 변경될 때마다 QWidget::updateGeometry()를 호출합니다. 그러면 레이아웃이 다시 계산됩니다. QWidget::updateGeometry ()를 여러 번 연속으로 호출하면 레이아웃이 한 번만 다시 계산됩니다.
위젯의 기본 높이가 실제 너비에 따라 달라지는 경우(예: 자동 단어 나누기가 있는 레이블) 위젯의 size policy 에 height-for-width 플래그를 설정하고 QWidget::heightForWidth()를 다시 구현하세요.
QWidget::heightForWidth()를 구현하더라도 적당한 sizeHint()를 제공하는 것이 좋습니다.
이러한 함수를 구현할 때 자세한 지침은 Qt 분기별 문서 높이와 너비 바꾸기를 참조하세요.
레이아웃 문제
라벨 위젯에 서식 있는 텍스트를 사용하면 부모 위젯의 레이아웃에 몇 가지 문제가 발생할 수 있습니다. 레이블이 단어로 줄 바꿈된 경우 Qt의 레이아웃 관리자가 서식 있는 텍스트를 처리하는 방식 때문에 문제가 발생합니다.
어떤 경우에는 부모 레이아웃이 QLayout::FreeResize 모드로 전환되어 작은 크기의 창에 맞게 콘텐츠의 레이아웃을 조정하지 않거나 사용자가 창을 너무 작게 만들어서 사용할 수 없게 되는 경우도 있습니다. 문제가 있는 위젯을 서브클래싱하고 적절한 sizeHint() 및 minimumSizeHint() 함수를 구현하면 이 문제를 해결할 수 있습니다.
어떤 경우에는 레이아웃이 위젯에 추가될 때 관련이 있습니다. QDockWidget 또는 QScrollArea ( QDockWidget::setWidget() 및 QScrollArea::setWidget() 사용)의 위젯을 설정할 때 레이아웃이 이미 위젯에 설정되어 있어야 합니다. 그렇지 않은 경우 위젯이 표시되지 않습니다.
수동 레이아웃
세상에 하나뿐인 특별한 레이아웃을 만드는 경우 위에 설명한 대로 사용자 정의 위젯을 만들 수도 있습니다. QWidget::resizeEvent ()를 다시 구현하여 필요한 크기 분포를 계산하고 각 하위 항목에 setGeometry()를 호출합니다.
위젯은 레이아웃을 다시 계산해야 할 때 QEvent::LayoutRequest 유형의 이벤트를 받습니다. QWidget::event ()를 다시 구현하여 QEvent::LayoutRequest 이벤트를 처리합니다.
사용자 정의 레이아웃 관리자 작성 방법
수동 레이아웃의 대안은 QLayout 을 서브클래싱하여 자체 레이아웃 관리자를 작성하는 것입니다. 플로우 레이아웃 예제는 이를 수행하는 방법을 보여줍니다.
여기서는 예제를 자세히 설명합니다. 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 생성자에서 QLayout::add()에 의해 사용됩니다. 레이아웃에 매개 변수가 필요한 고급 배치 옵션이 있는 경우 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에서 컴포지션 모드가 어떻게 작동하는지 보여줍니다. | |
메뉴 예제는 메인 창 애플리케이션에서 메뉴를 사용하는 방법을 보여줍니다. | |
단순 트리 모델 예제는 Qt의 표준 뷰 클래스와 함께 계층적 모델을 사용하는 방법을 보여줍니다. |
© 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.