Part I: Basic Qt – Ch02. Creating Dialogs 01 Subclassing QDialog 본문

Programming/C++/Qt

Part I: Basic Qt – Ch02. Creating Dialogs 01 Subclassing QDialog

halatha 2008. 7. 31. 18:07

Subclassing QDialog

Our first example is a Find dialog written entirely in C++. We will implement the dialog as a class in its own right. By doing so, we make it an independent, self-contained component, with its own signals and slots.

우리의 번째 예제는 완전하게 C++ 작성된 Find 다이알로그이다. 우리는 다이알로그를 클래스로서 구현할 것이다. 그렇게 함으로서, 자신의 시그널과 슬롯을 가지고, 우리는 독립적이고, 자체적으로 컴포넌트를 포함하는 것을 만든다.

Figure 2.1. The Find dialog


The source code is spread across two files: finddialog.h and finddialog.cpp. We will start with finddialog.h.

소스 코드는 개의 파일로 나뉜다: finddialog.h finddialog.cpp이다. 우리는 finddialog.h부터 시작할 것이다.

1 #ifndef FINDDIALOG_H

2 #define FINDDIALOG_H

3 #include <QDialog>

4 class QCheckBox;

5 class QLabel;

6 class QLineEdit;

7 class QPushButton;


Lines 1 and 2 (and 27) protect the header file against multiple inclusions.

1 2(그리고 27) 다중 인클루전에서 헤더 파일을 보호한다.

Line 3 includes the definition of QDialog, the base class for dialogs in Qt. QDialog inherits QWidget.

3행은 Qt에서 다이알로그를 위한 기본 클래스인 QDialog 정의를 포함한다. QDialog QWidget 상속한다.

Lines 4 to 7 are forward declarations of the Qt classes that we will use to implement the dialog. A forward declaration tells the C++ compiler that a class exists, without giving all the detail that a class definition (usually located in a header file of its own) provides. We will say more about this shortly.

4~7행은 다이알로그를 구현하기 위해 우리가 사용할 Qt 클래스들을 미리 선언한다. 미리 선언하는 것은 C++ 컴파일러에게 클래스 정의(보통 그것의 헤더 파일에 존재하는) 제공하는 세부 사항을 주지 않고 클래스가 존재하는 것을 말한다. 우리는 이것에 대해 짧게 말할 것이다.

Next, we define FindDialog as a subclass of QDialog:

다음으로, 우리는 QDialog 서브 클래스로서 FindDialog 정의한다:

8 class FindDialog : public QDialog

9 {

10 Q_OBJECT

11 public:

12 FindDialog(QWidget *parent = 0);


The Q_OBJECT macro at the beginning of the class definition is necessary for all classes that define signals or slots.

클래스 정의의 시작 부분에 있는 Q_OBJECT 매크로는 시그널이나 슬롯을 정의하는 모든 클래스에 필요하다.

The FindDialog constructor is typical of Qt widget classes. The parent parameter specifies the parent widget. The default is a null pointer, meaning that the dialog has no parent.

FindDialog 생성자는 전형적인 Qt 위젯 클래스이다. parent 인자는 부모 위젯을 의미한다. 디폴트는 포인터이고, 다이알로그가 부모를 가지지 않는 것을 의미한다.

13 signals:

14 void findNext(const QString &str, Qt::CaseSensitivity cs);

15 void findPrevious(const QString &str, Qt::CaseSensitivity cs);


The signals section declares two signals that the dialog emits when the user clicks the Find button. If the Search backward option is enabled, the dialog emits findPrevious(); otherwise, it emits findNext().

signals 부분은 사용자가 Find 버튼을 클릭했을 다이알로그가 내보내는 개의 시그널을 선언한다. 만약 Search backward 선택사항이 가능하게 되어 있다면, 다이알로그는 findPrevious() 내보낸다; 그렇지 않으면 findNext() 보낸다.

The signals keyword is actually a macro. The C++ preprocessor converts it into standard C++ before the compiler sees it. Qt::CaseSensitivity is an enum type that can take the values Qt::CaseSensitive and Qt::CaseInsensitive.

signals 카워드는 사실 매크로이다. C++ 전처리기는 그것을 컴파일러가 보기 전에 표준 C++ 바꾼다. Qt::CaseSensitivityQt::CaseSensitive At::CaseInsensitive 값을 취하는 열거형이다.

16 private slots:

17 void findClicked();

18 void enableFindButton(const QString &text);

19 private:

20 QLabel *label;

21 QLineEdit *lineEdit;

22 QCheckBox *caseCheckBox;

23 QCheckBox *backwardCheckBox;

24 QPushButton *findButton;

25 QPushButton *closeButton;

26 };

27 #endif


In the class's private section, we declare two slots. To implement the slots, we will need to access most of the dialog's child widgets, so we keep pointers to them as well. The slots keyword is, like signals, a macro that expands into a construct that the C++ compiler can digest.

클래스의 프라이빗 영역에서, 우리는 개의 슬롯을 선언한다. 슬롯들을 구현하기 위해, 우리는 다이알로그의 자식 위젯들의 대부분을 접근할 필요가 있을 것이고, 그래서 우리는 그것들에 대한 포인터도 유지한다. slots 키워드는 signals 같이 C++ 컴파일러가 사용할 있게 하는 구조로 확장하는 매크로이다.

For the private variables, we used forward declarations of their classes. This was possible because they are all pointers and we don't access them in the header file, so the compiler doesn't need the full class definitions. We could have included the relevant header files (<QCheckBox>, <QLabel>, etc.), but using forward declarations when it is possible makes compiling somewhat faster.

프라이빗 변수들을 위해, 우리는 클래스의 미리 선언하는 것을 사용했다. 이것은 그것들이 모두 포인터이기 때문이고 우리는 헤더 파일에서 그것들을 접근하지 않기 때문에 가능하다, 그래서 컴파일러는 전체 클래스 정의가 필요없다. 우리는 관련된 헤더 파일들을 포함시킬 수도 있었지만(<QCheckBox>, <QLabel>), 가능할 미리 선언을 하는 것을 이용하는 것은 컴파일을 빠르게 한다.

We will now look at finddialog.cpp, which contains the implementation of the FindDialog class.

이제 우리는 finddialog.cpp 것이다, 그것은 FindDialog 클래스의 구현을 포함하는 것이다.

1 #include <QtGui>

2 #include "finddialog.h"


First, we include <QtGui>, a header file that contains the definition of Qt's GUI classes. Qt consists of several modules, each of which lives in its own library. The most important modules are QtCore, QtGui, QtNetwork, QtOpenGL, QtSql, QtSvg, and QtXml. The <QtGui> header file contains the definition of all the classes that are part of the QtCore and QtGui modules. Including this header saves us the bother of including every class individually.

우선, 우리는 <QtGui>, Qt GUI 클래스들의 정의를 포함하는 헤더 파일을 포함한다. Qt 개의 모듈로 구성이 되고, 각각은 각각의 라이브러리에서 동작한다. 가장 중요한 모듈은 QtCore, QtGui, QtNetwork, QtOpenGL, QtSql, QtSvg, QtXml이다. <QtGui> 헤더 파일은 QtCore QtGui 모듈의 부분인 모든 클래스들의 정의를 포함한다. 헤더를 포함하는 것은 우리가 모든 클래스를 개별적으로 포함하는 귀찮은 일을 덜어준다.

In filedialog.h, instead of including <QDialog> and using forward declarations for QCheckBox, QLabel, QLineEdit, and QPushButton, we could simply have included <QtGui>. However, it is generally bad style to include such a big header file from another header file, especially in larger applications.

filedialog.h에서는, <QDialog> 포함하고 QCheckBox, QLabel, QLineEdit, QPushButton 미리 선언하는 것을 이용하는 대신에, 우리는 단지 간단하게 <QtGui> 포함시켰다. 그러나, 그렇게 헤더 파일을 다른 헤더 파일로부터 포함하는 것은 나쁜 스타일이다, 특히 어플리케이션에서는.

3 FindDialog::FindDialog(QWidget *parent)

4 : QDialog(parent)

5 {

6 label = new QLabel(tr("Find &what:"));

7 lineEdit = new QLineEdit;

8 label->setBuddy(lineEdit);

9 caseCheckBox = new QCheckBox(tr("Match &case"));

10 backwardCheckBox = new QCheckBox(tr("Search &backward"));

11 findButton = new QPushButton(tr("&Find"));

12 findButton->setDefault(true);

13 findButton->setEnabled(false);

14 closeButton = new QPushButton(tr("Close"));


On line 4, we pass on the parent parameter to the base class constructor. Then we create the child widgets. The tr() function calls around the string literals mark them for translation to other languages. The function is declared in QObject and every subclass that contains the Q_OBJECT macro. It's a good habit to surround user-visible strings with TR(), even if you don't have immediate plans for translating your applications to other languages. Translating Qt applications is covered in Chapter 17.

4행에서, 우리는 기초 클래스 생성자에게 parent 인자를 전달한다. 다음에 우리는 자식 위젯들을 만든다. tr() 함수는 다른 언어로 번역하기 위해 문자열 리터럴 표시를 순회한다. 함수는 QObject Q_OBJECT 포함하는 모든 서브클래스에 선언되어 있다. 사용자에게 보여주는 문자열을 TR() 사용해 감싸는 것은 좋은 습관이다, 비록 당신의 어플리케이션을 바로 다른 언어로 번역하기 위한 계획이 없더라도. Qt 어플리케이션을 번역하는 것은 17장에서 다룬다.

In the string literals, we use ampersands ('&') to indicate shortcut keys. For example, line 11 creates a Find button, which the user can activate by pressing Alt+F on platforms that support shortcut keys. Ampersands can also be used to control focus: On line 6 we create a label with a shortcut key (Alt+W), and on line 8 we set the label's buddy to be the line editor. A buddy is a widget that accepts the focus when the label's shortcut key is pressed. So when the user presses Alt+W (the label's shortcut), the focus goes to the line editor (the label's buddy).

문자열 리터럴에서, 우리는 앰퍼샌드를 이용해 단축키를 지정한다. 예를 들어, 11행에서 Find 버튼을 만드는데, 사용자는 단축 키를 지원하는 플랫폼에서 Alt + F 눌러 활성화시킬 있다. 앰퍼샌드는 포커스를 조정하기 위해 사용될 수도 있다: 6행에서 우리는 단축키(Alt + W) 함께 레이블을 만들고, 8행에서 레이블의 버디로 라인 에디터를 지정한다. 버디는 레이블의 단축키가 눌릴 포커스를 얻는 위젯이다. 그래서 사용자가 Alt + W(레이블 단축키) 누를 , 포커스가 라인 에디터로 간다(레이블의 버디).

On line 12, we make the Find button the dialog's default button by calling setDefault(true). The default button is the button that is pressed when the user hits Enter. On line 13, we disable the Find button. When a widget is disabled, it is usually shown grayed out and will not respond to user interaction.

12행에서, 우리는 Find 버튼을 setDefault(true) 호출해 다이알로그의 기본 버튼으로 만든다. 기본 버튼은 사용자가 엔터를 입력할 눌리는 버튼이다. 13행에서, 우리는 Find 버튼을 사용하지 못하게 만든다. 위젯이 사용이 불가능하게 되면, 보통 회색으로 보이고 사용자 입력에 반응하지 않는다.

15 connect(lineEdit, SIGNAL(textChanged(const QString &)),

16 this, SLOT(enableFindButton(const QString &)));

17 connect(findButton, SIGNAL(clicked()),

18 this, SLOT(findClicked()));

19 connect(closeButton, SIGNAL(clicked()),

20 this, SLOT(close()));


The private slot enableFindButton(const QString &) is called whenever the text in the line editor changes. The private slot findClicked() is called when the user clicks the Find button. The dialog closes itself when the user clicks Close. The close() slot is inherited from QWidget, and its default behavior is to hide the widget from view (without deleting it). We will look at the code for the enableFindButton() and findClicked() slots later on.

프라이빗 슬롯 enableFindButton(const QString &) 라인 에디터의 텍스트가 변화될 때마다 호출된다. 프라이빗 슬롯 findClicked() 사용자가 Find 버튼을 클릭할 호출된다. 다이알로그는 사용자가 Close 클릭하면 종료한다. close() 슬롯은 QWidget에서 상속되고 기본 동작은 뷰에서 위젯을 숨기는 것이다(그것을 지우지는 않는다). 우리는 enableFindButton() findClicked() 슬롯에 대한 코드를 나중에 것이다.

Since QObject is one of FindDialog's ancestors, we can omit the QObject:: prefix in front of the connect() calls.

QObject FindDialog 조상 하나이기 때문에, 우리는 QObject::라는 접두어를 connect() 호출 앞에서 생략할 있다.

21 QHBoxLayout *topLeftLayout = new QHBoxLayout;

22 topLeftLayout->addWidget(label);

23 topLeftLayout->addWidget(lineEdit);

24 QVBoxLayout *leftLayout = new QVBoxLayout;

25 leftLayout->addLayout(topLeftLayout);

26 leftLayout->addWidget(caseCheckBox);

27 leftLayout->addWidget(backwardCheckBox);

28 QVBoxLayout *rightLayout = new QVBoxLayout;

29 rightLayout->addWidget(findButton);

30 rightLayout->addWidget(closeButton);

31 rightLayout->addStretch();

32 QHBoxLayout *mainLayout = new QHBoxLayout;

33 mainLayout->addLayout(leftLayout);

34 mainLayout->addLayout(rightLayout);

35 setLayout(mainLayout);


Next, we lay out the child widgets using layout managers. Layouts can contain both widgets and other layouts. By nesting QHBoxLayouts, QVBoxLayouts, and QGridLayouts in various combinations, it is possible to build very sophisticated dialogs.

다음은, 우리가 레이아웃 관리자를 이용해 자식 위젯들을 배치하는 것이다. 레이아웃은 위젯과 다른 레이아웃 포함할 있다. QHBoxLayout, QVBoxLayout, QGridLayout 다양하게 포함하는 것으로, 매우 복잡한 다이알로그를 만드는 것이 가능하다.

For the Find dialog, we use two QHBoxLayouts and two QVBoxLayouts, as shown in Figure 2.2. The outer layout is the main layout; it is installed on the FindDialog on line 35 and is responsible for the dialog's entire area. The other three layouts are sub-layouts. The little "spring" at the bottom right of Figure 2.2 is a spacer item (or "stretch"). It uses up the empty space below the Find and Close buttons, ensuring that these buttons occupy the top of their layout.

Find 다이알로그를 위해, 그림 2.2에서처럼 우리는 개의 QHBoxLayout 개의 QVBoxLayout 사용할 것이다. 바깥 쪽의 레이아웃은 레이아웃이다; 그것은 35행에서 FindDialog 설치되고 다이알로그의 전체 영역을 책임진다. 다른 개의 레이아웃은 레이아웃이다. 그림 2.2 바닥 오른쪽의 작은 "용수철 모양" 공간을 확보하기 위한 것들이다(또는 "확장"). 그것은 Find Close 버튼들 밑의 공간을 채우는데 사용하고, 버튼들이 레이아웃의 위쪽을 차지하는 것을 확실하게 한다.

Figure 2.2. The Find dialog's layouts

[View full size image]


One subtle aspect of the layout manager classes is that they are not widgets. Instead, they inherit QLayout, which in turn inherits QObject. In the figure, widgets are represented by solid outlines and layouts are represented by dashed outlines to highlight the difference between them. In a running application, layouts are invisible.

레이아웃 관리자 클래스의 하나의 미묘한 면은 그것들이 위젯이 아니라는 것이다. 대신, 그것들은 QLayout 상속하는데, 그것은 대신 QObject 상속한다. 그림에서, 서로간의 차이를 강조하기 위해 위젯들은 실선으로 표현되고 레이아웃들은 점선으로 표시된다. 동작중인 어플리케이션에서, 레이아웃들은 보이지 않는다.

When the sub-layouts are added to the parent layout (lines 25, 33, and 34), the sub-layouts are automatically reparented. Then, when the main layout is installed on the dialog (line 35), it becomes a child of the dialog, and all the widgets in the layouts are reparented to become children of the dialog. The resulting parentchild hierarchy is depicted in Figure 2.3.

레이아웃들이 부모 레이아웃에 더해질 (25, 33, 34), 레이아웃들은 자동으로 reparent된다. 다음에, 레이아웃이 다이알로그에 설치되고(35), 다이알로그의 자식의 되고, 레이아웃에 있는 모든 위젯들은 다이알로그의 자식이 되기 위해 reparent된다. 결과적인 부모 자식 계층은 그림 2.3에서처럼 나타난다.

Figure 2.3. The Find dialog's parentchild relationships


36 setWindowTitle(tr("Find"));

37 setFixedHeight(sizeHint().height());

38 }


Finally, we set the title to be shown in the dialog's title bar and we set the window to have a fixed height, since there aren't any widgets in the dialog that can meaningfully occupy any extra vertical space. The QWidget::sizeHint() function returns a widget's "ideal" size.

마지막으로, 우리는 타이틀이 다이알로그의 타이틀 바에 보이도록 하고 윈도우를 고정된 높이로 조정한다, 왜냐하면 다이알로그에는 어떤 여분의 수직 높이도 의미있게 점유하는 위젯이 없기 때문이다. QWidget::sizeHint() 함수는 위젯의 "이상적인" 크기를 반환한다.

This completes the review of FindDialog's constructor. Since we used new to create the dialog's widgets and layouts, it would seem that we need to write a destructor that calls delete on each of the widgets and layouts we created. But this isn't necessary, since Qt automatically deletes child objects when the parent is destroyed, and the child widgets and layouts are all descendants of the FindDialog.

이것이 FindDialog 생성자를 완전히 살펴본 것이다. 우리는 다이알로그의 위젯과 레이아웃을 생성하기 위해 new 사용했기 때문에, 우리가 생성한 위젯과 레이아웃들 각각에 대해 delete 호출하는 소멸자를 작성할 필요가 있는 것처럼 보인다. 그러나 필요하지 않다, 왜냐하면 Qt 자동으로 부모가 소멸할 자식 개체들을 삭제하기 때문이고, 자식 위젯과 레이아웃들은 모두 FindDialog 후손이기 때문이다.

Now we will look at the dialog's slots:

이제 다이알로그의 슬롯들을 살펴보자:

39 void FindDialog::findClicked()

40 {

41 QString text = lineEdit->text();

42 Qt::CaseSensitivity cs =

43 caseCheckBox->isChecked() ? Qt::CaseSensitive

44 : Qt::CaseInsensitive;

45 if (backwardCheckBox->isChecked()) {

46 emit findPrevious(text, cs);

47 } else {

48 emit findNext(text, cs);

49 }

50 }

51 void FindDialog::enableFindButton(const QString &text)

52 {

53 findButton->setEnabled(!text.isEmpty());

54 }


The findClicked() slot is called when the user clicks the Find button. It emits the findPrevious() or the findNext() signal, depending on the Search backward option. The emit keyword is specific to Qt; like other Qt extensions it is converted into standard C++ by the C++ preprocessor.

findClicked() 슬롯은 사용자가 Find 버튼을 클릭할 호출된다. 그것은 findPrevious() findNext() 시그널을 내보낸다, Search backward 옵션의 상태에 따라서. emit 키워드는 Qt 특정한 것이다; 다른 Qt 확장들처럼 C++ 전처리기에 의해 표준 C++ 변형된다.

The enableFindButton() slot is called whenever the user changes the text in the line editor. It enables the button if there is some text in the editor, and disables it otherwise.

enableFindButton() 슬롯은 사용자가 라인 에디터의 텍스트를 변형할 때마다 호출된다. 그것은 에디터에 어떤 텍스트가 있는 경우 버튼을 활성화하고, 그렇지 않으면 비활성화한다.

These two slots complete the dialog. We can now create a main.cpp file to test our FindDialog widget:

개의 슬롯들은 다이알로그를 완전하게 한다. 우리는 이제 우리의 FindDialog 위젯을 시험하기 위해 main.cpp file 만든다.

1 #include <QApplication>

2 #include "finddialog.h"

3 int main(int argc, char *argv[])

4 {

5 QApplication app(argc, argv);

6 FindDialog *dialog = new FindDialog;

7 dialog->show();

8 return app.exec();

9 }


To compile the program, run qmake as usual. Since the FindDialog class definition contains the Q_OBJECT macro, the makefile generated by qmake will include special rules to run moc, Qt's meta-object compiler. (Qt's meta-object system is covered in the next section.)

프로그램을 컴파일하기 위해, 보통때처럼 qmake 실행하라. FindDialog 클래스 정의가 Q_OBJECT 매크로를 포함하기 때문에, qmake 의해 생성된 메이크 파일은 Qt 메타 오브젝트 컴파일러인, moc 동작하기 위한 특별한 규칙을 포함할 것이다(Qt 메타 오브젝트 시스템은 다음 부분에서 다뤄진다).

For moc to work correctly, we must put the class definition in a header file, separate from the implementation file. The code generated by moc includes this header file and adds some C++ magic of its own.

moc 올바르게 동작하기 위해, 우리는 클래스 정의를, 구현 파일과는 분리된, 헤더 파일에 넣어야만 한다. moc 의해 생성된 코드는 헤더 파일을 포함하고 자체의 어떤 C++ 기법을 더한다.

Classes that use the Q_OBJECT macro must have moc run on them. This isn't a problem because qmake automatically adds the necessary rules to the makefile. But if you forget to regenerate your makefile using qmake and moc isn't run, the linker will complain that some functions are declared but not implemented. The messages can be fairly obscure. GCC produces warnings like this one:

Q_OBJECT 매크로를 사용하는 클래스들은 moc 동작시켜야만 한다. 이것은 qmake 자동으로 필요한 규칙들을 makefile 더하기 때문에 문제가 아니다. 그러나 당신이 qmake 이용해서 메이크 파일을 재생성하는 것을 잊고, moc 동작하지 않으면, 링커는 어떤 함수들은 선언만 되고 구현이 되지 않았다고 불평할 것이다. 메시지들은 매우 불명확하다. GCC 다음과 같은 경고를 만들어낼 것이다:

finddialog.o: In function 'FindDialog::tr(char const*, char const*)':

/usr/lib/qt/src/corelib/global/qglobal.h:1430: undefined reference to

'FindDialog::staticMetaObject'


Visual C++'s output starts like this:

비주얼 C++ 출력은 이렇게 시작한다:

finddialog.obj : error LNK2001: unresolved external symbol

"public:~virtual int __thiscall MyClass::qt_metacall(enum QMetaObject

::Call,int,void * *)"


If this ever happens to you, run qmake again to update the makefile, then rebuild the application.

이런 것이 당신에게 발생한다면, 메이크 파일을 새로 쓰기 위해 qmake 동작시키고, 어플리케이션을 다시 만들어라.

Now run the program. If shortcut keys are shown on your platform, verify that the shortcut keys Alt+W, Alt+C, Alt+B, and Alt+F trigger the correct behavior. Press Tab to navigate through the widgets with the keyboard. The default tab order is the order in which the widgets were created. This can be changed using QWidget::setTabOrder().

이제 프로그램을 동작하라. 만약 당신의 플랫폼에서 단축키가 보인다면, 단축키 Alt + W, Alt + C, Alt + B, Alt + F 올바른 동작을 하는지 확인하라. 키보드와 함께 위젯을 차례로 순회하려면 탭을 눌러라. 기본 순서는 위젯들이 생성된 순서에 따른다. 이것은 QWidget::setTabOrder(0 이용해 바뀔 있다.

Providing a sensible tab order and keyboard shortcuts ensures that users who don't want to (or cannot) use a mouse are able to make full use of the application. Full keyboard control is also appreciated by fast typists.

민감한 순서와 키보드 단축키를 제공하는 것은 마우스를 사용하지 않는(또는 못하는) 사용자들이 어플리케이션을 완전히 사용할 있게 한다. 완전한 키보드 제어는 또한 빠른 타자를 치는 사람들이 좋아한다.

In Chapter 3, we will use the Find dialog inside a real application, and we will connect the findPrevious() and findNext() signals to some slots.

3장에서, 우리는 실제 어플리케이션에서 Find 다이알로그를 이용할 것이고, findPrevious() findNext() 시그널을 어떤 슬롯에 연결할 것이다.

Comments