Qt에 대하여 - 7. 레이아웃 시스템

보통 다이얼로그같은 것을 하나 만들면 그 안에는 여러개의 위젯이 들어갑니다.

무작정 생성만 해두면 다 한군데에 몰려있으니 위치관계를 고려해서 리사이즈도 해주고 위치도 옮겨주고 줄간격도 맞춰주고...어떻게 깔끔한 모양을 만들었습니다.

그런데 창을 리사이즈했더니 위젯은 고대로 있고 창만 커집니다.

기껏 좌표 계산해서 가운데로 가져다 놨더니 창을 키우니 더이상 가운데가 아니게 되고, 반대로 창을 작게 했더니 다 짤려 나오고...

그럼 리사이즈 할때마다 다시 계산을 다 해서 배열해야 할까요?

우리의 강력한 큐티는 이런 경우에도 쉽게 대처할 수 있는 레이아웃 시스템을 가지고 있습니다.

일단 소스코드를 보시죠.

mainwidget.h
[code]
#include <QWidget>

class QLabel;
class QLineEdit;

class MainWidget : public QWidget {
    Q_OBJECT
public:
    MainWidget();
private slots:
    void setName(const QString &name);
private:
    QLabel *printName;
    QLineEdit *nameEdit;
};
[/code]mainwidget.cpp
[code]
#include "mainwidget.h"
#include <QLabel>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QSpacerItem>

MainWidget::MainWidget()
: QWidget() {
    printName = new QLabel();
    nameEdit = new QLineEdit(this);
   
    QHBoxLayout *hbox1 = new QHBoxLayout;
    hbox1->addWidget(new QLabel(trUtf8("이름을 적어주세요:"), this));
    hbox1->addWidget(nameEdit);
   
    QVBoxLayout *vbox = new QVBoxLayout;
    vbox->addLayout(hbox1);

    QHBoxLayout *hbox2 = new QHBoxLayout;
    hbox2->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
    hbox2->addWidget(printName);
    hbox2->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
    vbox->addLayout(hbox2);

    setLayout(vbox);

    connect(nameEdit, SIGNAL(textChanged(const QString&)), this, SLOT(setName(const QString&)));

    setName(QString());
}

void MainWidget::setName(const QString &name) {
    static const QString text = trUtf8("당신의 이름은 %1입니다.");
    if (name.isEmpty())
        printName->setText(text.arg(trUtf8("무명씨")));
    else
        printName->setText(text.arg(name));
    adjustSize();
}
[/code]main.cpp
[code]
#include <QApplication>
#include "mainwidget.h"

int main(int argc, char **argv) {
    QApplication app(argc, argv);
    MainWidget mw;
    mw.show();
    return app.exec();
}
[/code]
whats-your-name

실행화면

이번 예제는 이름을 적으면 적은 이름을 표시해주는 whats-your-name이라는 프로그램입니다.

mainwidget.h는 QLineEdit라는 새로운 위젯이 나온 것 외에는 딱히 설명할 것이 없어보입니다.

간단히 복습해보면 Q_OBJECT는 메타 오브젝트 시스템을 위한 키워드이며, 시그널/슬롯을 이용할려면 선언해줘야하고, slots는 함수를 슬롯으로 이용할 수 있게 만들어주는 키워드입니다.

QLineEdit는 위의 스샷에서 보듯이 무엇가를 입력받기 위한 위젯입니다(오래되서 확실친 않지만 아마 MFC에선 CEdit란 이름의 클래스가 같은 역할을 했던거 같습니다).

이름에 Line이라고 적혀있는 대로, 입력 받을 수 있는건 한줄이며, QLabel등과 동일하게 text()/setText()로 현재 글을 가져오거나 지정할수도 있습니다.

이 위젯은 글자가 입력/수정될때마다 textEdited(const QString&)시그널과 textChanged(const QString&)시그널을 내뱉습니다.

둘의 차이는, textEdited는 사용자가 QLineEdit에 직접 입력한 경우에만 발생되지만, textChanged는 프로그램내에서 setText등을 이용해서 글자를 바꿨을때도 발생한다는 것입니다.

우리는 여기서 textChanged시그널을 이용해서 입력된 이름을 표시할 것입니다.

이제 mainwidget.cpp파일로 넘어갑니다.

해더파일중에 QVBoxLayout, QHBoxLayout, QSpacerItem 등 처음 보는 클래스들이 보일텐데요, 이건 생성자안에서 생성할때 설명하겠습니다.

printName은 "당신의 이름은 ~입니다."라고 표시하기 위한 라벨이고, nameEdit가 이름을 입력받기위한 에디트입니다.

13번째 줄을 보면 QHBoxLayout *hbox1를 할당하고 있습니다.

이름에서 알수 있듯이 오늘의 주인공인 레이아웃 클래스중 하나입니다.

QHBoxLayout의 H는 Horizontal의 H입니다.

즉 수평으로 위젯들을 배치할때 유용하게 쓸수 있는 레이아웃 클래스입니다.

다음줄을 보시면 addWidget이란 멤버함수를 호출하고 있습니다.

이 함수는 레이아웃에 widget을 추가해줍니다.

QHBoxLayout클래스에 추가된 widget은 추가된 순서대로 배열됩니다.

addWidget의 안을 보면 new QLabel(trUtf8("이름을 적어주세요:"), this) 이렇게 바로 동적할당한 포인터를 넘겨주고 있습니다.

이건 두줄로 풀어쓰면

QLabel *label = new QLabel(trUtf8("이름을 적어주세요:"), this);
hbox1->addWidget(label);

과 동등한데, 여기서 label은 딱히 다음에 쓸일이 없기 때문에, 그냥 변수를 선언하지 않고 바로 포인터를 넘겨준 것입니다(전회에 설명한대로, 동적할당하더라도 this의 자식객체로 들어가기 때문에 나중에 자동으로 해제되므로 이런식으로 짜는게 가능합니다).

그리고 또 다음줄에 addWidget(nameEdit)가 호출되고 있습니다.

따라서 "이름을 적어주세요:"라벨이 제일 왼쪽에 들어가고, 그오른쪽에 nameEdit가 들어갑니다.

만약 여기에 새로운 위젯을 추가한다면 이번엔 nameEdit의 오른쪽에 추가되겠죠?

17번째 줄을 보면 이번엔 QVBoxLayout *vbox를 할당하고 있습니다.

눈치빠른 분이라면 V가 vertical의 이니셜이란 것을 눈치채셨을 듯합니다.

QHBoxLayout과 기본적인 사용법은 똑같으나, 위젯을 배치하는 방향이 옆으로(왼쪽에서 오른쪽으로)가 아니라 세로로(위에서 아래로)배치하는 레이아웃 클래스가 QVBoxLayout입니다.

다음줄을 보면 addLayout이란 함수로 hbox1를 추가하고 있습니다.

addLayout은 기본적인 동작방식은 addWidget과 거의 같지만, 그 대상이 위젯이 아니라 다른 레이아웃 클래스라는 점만 다릅니다.

위젯들을 포함한 레이아웃이 통째로 삽입됩니다(결과는 스샷을 보는 쪽이 알기쉽습니다).

20번째 줄을 보면 hbox2라는  새로운 QHBoxLayout을 할당하고 있습니다.

사실 더이상 hbox1은 쓸일이 없기때문에, 새로운 변수를 선언할 필요없이 hbox1에 새로 할당해도 되지만, 편의상 구분하기 위해 새로운 변수를 선언하였습니다.

hbox2에서는 addItem이라는 또다른 함수가 호출되고 있습니다.

'item'이라는 것은 레이아웃에 들어갈수 있는 아이템(마땅히 다른 말이 떠오르지 않네요)입니다.

이렇게 레이아웃에 추가될수 있는 것은 위젯, 다른 레이아웃, 그리고 아이템 이렇게 세가지입니다.

그리고 아이템의 경우는 클래스가 딱 두개밖에 없습니다.

하나는 여기서 만들고 있는 QSpacerItem, 다른 하나는 QWidgetItem입니다.

사실 addWidget함수는 내부적으로 QWidgetItem을 생성하여, addItem함수를 이용하도록 구현되어있습니다.

하지만 addWidget이나 기타 위젯으로 바로 접근 가능한 함수들을 제공하고 있으므로, 사실성 QWidgetItem을 직접 보게될일은 거의없고, 결국 우리가 보게될 아이템은 QSpacerItem 한개뿐입니다.

QSpacerItem은 이름그대로 공간을 때우기위한 아이템입니다.

예를 들어, 다이얼로그에서는 자주 확인 버튼이 맨아래 오른쪽에 위치한걸 볼 수 있습니다.

이러한 것은 QHBoxLayout에 QSpacerItem과 버튼을 차례로 추가해주는 방법으로 구현가능합니다.

이렇게 하면 다이얼로그가 리사이즈되도 버튼은 그대로 두고 QSpacerItem이 알아서 늘어났다 줄어들었다 하면서 공간을 채워주는 것이죠.

생성자는

QSpacerItem(int w, int h, QSizePolicy::Policy hPolicy = QSizePolicy::Minimum,QSizePolicy::Policy vPolicy = QSizePolicy::Minimum)

이런 모습인데요,  w와 h는 sizeHint를 제공하고, hPolicy와 vPolicy는 sizePolicy를 제공합니다.
저의 경우는 0, 0, Expanding, Minimum 이라고 적었는데요, 이렇게 하면 기본적인 크기는 너비 높이 0으로 하되, 리사이즈될때는 옆으로는 늘어나도 되지만, 세로로는 늘어나지 않는 QSpacerItem을 생성한 셈입니다.

21-23라인을 보시면, 두개의 QSpacerItem을 생성하여 그 중간에 printName을 끼워넣는 방식으로 레이아웃이 만들어지고 있습니다.

이렇게 하면, 양쪽에 늘어나는 QSpacer가 놓여있기 때문에, printName은 창크기가 바껴도 항상 한가운데에 위치하게됩니다(사실, 좀더 쉬운 구현방법은 그냥 아이템없이 printName이 직접 늘어나게 한다음, printName의 글자를 중앙정렬시키는 건데요, 일부러 아이템을 소개하기위해 이렇게 했습니다).

이렇게 만들어진 hbox2를 다시 vbox에 추가합니다.

최종적으로 만들어진 레이아웃의 모양을 시각화하면 다음과 같습니다.
시각화된 레이아웃

시각화된 레이아웃

위의 빨간 사각형이 hbox1, 아래의 빨간 사각형이 hbox2, 그리고 아래에 양쪽으로 그려져있는 파란 스프링이 두개의 QSapcerItem입니다.

그리고 빨간 사각형 두개의 주변으로 vbox가 있는 셈이지요.

이렇게 레이아웃이 완성되면 이 레이아웃을 위젯에 세팅해주기만 하면되고, setLayout함수가 그역할을 합니다(26번째 줄).

이때 주의할 것은 이미 한번 레이아웃이 세팅된 위젯에는, 그 레이아웃을 delete하기전엔 새로운 레이아웃을 세팅할 수 없다는 것입니다.

이정도면 레이아웃에 대해서 알아야 할건 거의다 설명했는데, 한가지 설명안한것이 소유권(부모-자식관계)입니다.

여기서는 레이아웃 클래스들은 전부 부모를 지정하지 않고(널로 지정하고) 생성하였습니다.

하지만 한 레이아웃이 다른 레이아웃으로 들어가거나, 어떤 위젯의 레이아웃으로 지정되면(setLayout), 그 레이아웃이나 위젯이 부모가 됩니다.

최종적으로 이 예제에서는 vbox는 hbox1과 hbox2의 부모가, this(MainWidget)은 vbox의 부모가 되어 있는 것입니다.

또, addItem으로 추가된 아이템의 소유권(아이템클래스같이 QObject를 상속받지 않는데, 자동으로 삭제되는 경우는 자식-부모라고 할 수 없어서 소유권이란 말도 자주 씁니다)은 레이아웃이 가지고 있으므로, 레이아웃이 해제될때 자동으로 추가된 아이템들도 해제됩니다.

단 addWidget으로 추가된 위젯의 경우는 소유권(부모)이 바뀌지 않습니다.

다만, setLayout으로 레이아웃을 지정하면, 레이아웃의 위젯들은 자동으로 setLayout을 호출한 위젯의 자식이 됩니다.

따라서 이 예제에서는 printName등을 할당할때, this를 넘겨주지 않더라도 최종적으로는 this의 자식이 될것입니다.

만약 레이아웃 클래스(QHBoxLayout등)을 생성할때 부모 위젯을 넘겨주면 어떻게 될까요?

이경우는 자동적으로 부모위젯의 setLayout함수가 호출되어 레이아웃을 세팅합니다...만, 이경우도 이미 세팅된 레이아웃이 있는 경우 무시됩니다.

이제 다시 예제로 돌아갑니다.

connect는 처음 설명한데로 QLineEdit의 textChanged시그널과 MainWidget::setName을 연결하고 있고, 마지막으로 빈이름을 설정하고 있습니다.

이제 setName함수를 보면, static 변수로 printName라벨의 텍스트로 설정할 바탕이될 문자열을 선언하고 있습니다.

그런데 %1이란 이상한 글자가 들어가 있네요.

if문을 보면 name이 비어있는 경우와 비어있지 않은 경울 놔눠서 하고 있는데, QString::arg()라는 함수가 호출되고 있습니다.

이 %1이라는 건, 비유하자면 printf나 sprintf등에서 쓰는 %d 라던가 %s 같은 다른 변수로 치환될 부분을 나타냅니다.

그리고 숫자의 순서대로 치환해갑니다.

예를 들어 QString("%1 + %2 = %3").arg(1).arg(2).arg(3) 와 같이 하면 "1 + 2 = 3"이라는 문자열이 반환되는 거죠.

예제에서는 text.arg(trUtf8("무명씨")) 혹은 text.arg(name)라고 하고 있으므로 name이 비어있으면 %1이 무명씨로, 그렇지 않으면 %1이 name으로 치환될 것입니다.

또한 sprintf처럼 숫자를 치환할때 적당히 0이나 다른 문자를 체워넣거나 하는 것도 가능합니다.

하지만 %치환자와 arg를 이용한 치환에서만 가능한 것이 있습니다.

바로 번역할 때, 어순때문에 치환자의 순서가 바뀌더라도, 치환의 순서는 숫자로 지정되므로 그대로 유지된다는 것입니다.

예를 들어 tr("I do %2 width %3")을 "나는 %3을 가지고 %2를 한다"와 같이 번역해도 치환에는 문제가 없는것이죠.

마지막으로  adjustSize()라는 함수를 호출하고 있는데, 이것이 참 편리한 함수입니다.

이 예제에서 글자가 길어지면 저절로 printName라벨의 크기가 늘어납니다. 그리고 printName은 hbox2에 들어있으므로 hbox2도자동으로 늘어나고, hbox2는 vbox에, vbox는 MainWidget에 들어가있으므로 최종적으로 MainWidget의 크기가 늘어나면서 레이아웃이 유지됩니다.

그런데 일단 늘어난 다음에 다시 글자수를 적게하면 한번 늘어난 크기가 줄어들지 않습니다.

다시 줄어들게 하고는 싶은데, 글자수에 따라서 얼마나 줄여야될지를 계산할려니 또 귀찮아집니다.

이럴때 adjustSize()함수를 호출하면 sizeHint()에 기반하여 적절한 크기를 계산하여 리사이즈 해주므로 너무 크기도, 너무 작지도 않게 적당하게 자동으로 크기가 변경됩니다.

이 레이아웃 시스템, 코드도 그리 어렵지 않습니다만, 예를 들어 학생정보를 입력받기위해서 수많은 라벨과 에디트등이 필요한 상황이라면 이것도 또 코드짜는게 만만치 않을 것입니다.

하지만 우리의 사랑스러운 큐티(여담이지만 트롤테크 내부에서는 Qt를 cute처럼 발음한다고 하네요)는 이런 경우에 쉽게 GUI디자인을 가능케하는 Qt Designer라는 프로그램을 제공하고 있습니다(사실 이미 여러분은 디자이너의 화면을 보았습니다. 저위에 제가 '시각화'라는 이름으로 달아놓은 스샷이 바로 디자이너에서 디자인할때의 모습입니다).

다음시간에는 이 Designer에 대해 알아보도록 하겠습니다.

Posted by xylosper

2008/03/28 05:09 2008/03/28 05:09
, ,
Response
No Trackback , No Comment
RSS :
http://xylosper.net/rss/response/113

Trackback URL : http://xylosper.net/trackback/113


Notices

Archives

Calendar

«   2010/09   »
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    

Site Stats

Total hits:
174156
Today:
24
Yesterday:
88
The images of equations
in this website are CodeCogs - An Open Source Numerical Library