이전 연재에서 QML에서 ListModel을 작성하여 목록으로 표시하였다. 이제 C++로 ListModel을 만들어 연동해보자.
QT에서는 관련하여 QAbstractListMode, QAbstractTableModel 을 제공하는데 우리는 ListView에 연동할 리스트를 관리하는 모델임으로 QAbstractListModel을 상속받아서 작성한다. 먼저 QAbstractListModel 은 QAbstractItemModel을 상속받고 있는데 이것이 Table, Tree 를 위한 모델을 포함하여 부모클래스가 된다. 이 클래스에서 주요 가상함수들이 리스트 모델을 구성하는데 있어 중요 함수가 된다.
3가지 함수를 재정의하는 것이 중요하다.
rowCount 함수를 재정의하면 리스트의 전체 개수를 리턴하여 UI에서 행이 표시될 것이다. data 함수를 재정의하면 그에 따라 리스트 데이터가 출력된다. 입력 인자인 index, role이 각각 row 행 번호와 column 을 가리키다. 만약 GUI의 List를 통해 입력을 받고 싶다면 setData 를 재정의하면 된다. 마지막으로 roleNames를 재정의 해야 QML에서 모델에 각 행에서 컬럼 데이터를 얻을 수 있다. AddressBookListModel 클래스를 생성하여 위 함수를 재정의 해 보자.
위에서 이야기한 몇개 함수만 재정의 하였다. 더불어 데이터의 각 row 정보를 담당하는 구조체도 정의하였다. enum 값으로 정의한 ColumnNames의 역할도 구현부에서 추가 설명할 것인데 이름을 보아도 알 수 있듯이 model에서 접근할 수 있는 컬럼명이 된다. 하단의 QList<ADDRES_BOOK_ITEM>은 포인터 형태로서 ViewModel을 통해 넘겨받아 QML 연동시켜 준다. 구현부는 아래와 같다.
클래스의 생성자에서 enum 값에서 정의된 순서대로 column 명을 정의하였다. 그리고 roleNames에서 그 리스트를 리턴하였다. rowCount 함수에서는 포인터로 가지고 있는 실제 리스트 데이터의 갯수를 리턴하고 있다.
가장 중요한 data 함수에서는 인자로 넘겨받은 index 정보를 바탕으로 실제 리스트 데이터에서 해당 인덱스의 정보를 가져와서 roleName에 정의된 순서대로 값을 리턴하고 있다. 예상 하겠지만 QML에서는 이 함수를 각 행마다 또 컬럼마다 반복 호출하여 화면에 출력하게 된다.
이 리스트 모델을 ViewModel 에서 멤버로 가지고 있으면서 리스트모델에 데이터를 채워넣어보자. 코드는 아래와 같다.
위에 함수를 보면 getListModel() 함수를 통해 ListModel의 포인터를 리턴하고 있다.
QML 코드를 보면 ViewModel에 정의된 함수를 호출하여 model 과 연결시켜 주고 있다. 프로그램을 실행하면 전과 종일한 리스트를 확인할 수 있지만 데이터는 QML에서 코딩된 것이 아니라 ViewModel에 정의한 데이터를 통해서 표시되고 있는 것임으로 txt파일이나 json, xml, 혹은 db를 통해 얻은 무한대의 데이터를 화면에 표시할 수 있을 것이다.
마무리 하며 다음 연재는?
QML위주로 작업하다가 QAbstractListModel을 상속받아 리스트 모델을 만드는 과정이 꾀나 복잡하게 느껴졌을지도 모르겠다. 하지만 단지 몇개의 함수만 재정의 하면 놀랍게도 간단히 화면에 C++ 기반의 데이터를 출력할 수 있다. 다음 시간에는 저 위에 버튼 Add, Remove, Edit (텍스트 복사하느라 전부 add인데 무시!) 버튼을 눌러 리스트를 추가, 삭제, 편집하는 것을 해보겠다. 추가, 편집을 위해 새로운 대화상자를 뛰우는 법, 그리고 출력된 리스트를 갱신하는 법을 배우게 될 것이다.
QML에서 Click과 같은 이벤트를 C++로 전달하여 함수를 호출하여 로직을 수행하고 로직이 수행된 결과는 QML 로 전달하여 화면을 갱신한다. QML과 통신을 담당하는 클래스들은 QOject 를 상속받아야한다. meta class를 생성하여 상호 연결을 해주기 위험인데 지금은 그저 전능한 QT 매커니즘이 무언가 연결해 주는구나 정도로만 이해하자. 우리는 QT를 이용하여 GUI를 만들려는 것이지 GUI 프레임웍 자체를 만들려는 것이 아니다.
자 이 클래스는 다음 두 키워드를 통해 QML과 통신하게 된다. QML의 Property 의 데이터를 주고 받는 용도로 Q_PROPERTY() 를 정의하고 QML에서 C++ 함수를 호출하기 위해 Q_INVOKABLE 로 함수를 선언한다. 예제를 설명하기 위해 주소록을 조금 변경해 보자. 사용자가 한 행을 선택하면 리스트뷰 상단에 몇번째 행을 선택했습니다라는 메시지를 출력해 보겠다. 물론 이정도는 QML 자체적으로 처리가 가능하지만 몇번째 행을 선택 했습니다라는 메시지가 아닌 매우 복잡한 연산을 하고 그 결과를 표시한다고 상상해 보자.
하드코딩된 라벨의 텍스트를 C++로 연동시켜보겠다. 먼저 클래스를 생성해 보자. 클래스 이름은 AddressBookViewModel 이라고 하겠다. 왜 이런식으로 이름 지었는지는 좀더 설명하면 이해가 갈 것이다. 클래스를 생성할때 QObject를 Base Class로 선언해두면 좀 더 편하게 코딩할 수 있다.
자 먼저 사용자 마우스를 클릭할 때마다 발생할 함수를 선언에 보자. QML에서 호출하는 함수임으로 selectRow 라고 하고 입력 인자로 선택행 행의 인덱스를 넘겨 받자. qml에서 호출되는 함수 이름은 소문자로 시작되어야 warning 표시를 나지 않는다. 대문자로 시작할 경우 계속 warnig이 나서 상당히 거슬리니 소문자로 함수를 시작하길 바란다.
자 이제 QML에서 click시마다 이 함수가 작동될 수 있도록 처리해 보자. QML에서 C++ 클래스를 인식하는 가장 간단한 방식은 전역으로 객체를 선언하는 것이다. 거의 모든 QT 책들이나 예제에서 이 방식으로 설명하고 있는데 main 함수 안에서 클래스를 인스턴스화 하고 qml에서 사용할 수 있도록 context property로 선언하는것이다.
이렇게 정의하면 QML에서는 위해서 정의한 Component인것처럼 접근할 수 있다. qml에서 마우스 클릭시 함수를 호출할 수 있다.
디버깅 하는 방법도 배울겸 올바르게 호출되고 있는지 디버깅을 해보자. 브레이크 포인트를 잡으려는 위치에 코드 행 넘버가 표시되는 곳 왼쪽을 클릭한다. 붉은 색 원이 표시되면 브레이브 포인트가 설정된 것이다.
그리고 프로그램을 실행할 때 디버그 모드로 실행한다. 왼쪽 아래 버튼 중 벌레 모양이 포함된(굉장이 직관적이다 디버그!) 버튼을 클릭한다.
좀 시간이 지나고 나면 표시된 화면에서 두번째 행을 클릭하면 아래와 같이 브레이크 포인트에 프로그램이 정지 되고 오른쪽 위 local 창에서 로컬 변수인 iPos 값에 1이 표시됨을 확인할 수 있다.
두번째로는 선택된 행의 인덱스 값 즉 m_selectedPos를 Q_PROPERTY를 통해 QML로 전달해 보자. QML 에서 보듯이 property 란 변수처럼 사용된다. 그것을 c++의 함수와 연계하기 위해 아래와 같은 형식으로 작성한다. msg의 표시는 c++의 데이터를 읽기만 하고 QML에서의 입력이 c++로 넘어가지 않으므로 READ 와 NOTIFY 만 선언한다. 만약 QML에서 입력한 값을 C++로 전달하고 한다면 WRITE도 정의하면 된다. 가장 앞쪽의 QString displayMsg가 QML 에서 인식하는 자료형과 Property 이름이 된다.
QML 코드를 보면 좀 더 이해가 쉬울 것이다. Label의 텍스트를 model의 displayMsg값으로 표시하고 있다. QT는 QML에서 modeld의 값을 넘겨 받으려할때 Q_PROPERY에서 정의된 getDisplayMsg 함수를 호출하여 값을 넘겨받게 해준다.
위 코드를 다시한번 살펴 보면 NOTIFY displayMsgChanged 라고 선언 되어 있고 signals: 아래에 같은 이름의 함수가 선언되어 있다. 이것이 마법처럼 QML과의 인터렉션을 가능하게 해 주는데 C++ 코드를 다시 살펴 보자. 아까와 달리 emit 이란 키워드가 추가되고 displayMsgChanged() 를 호출하고 있다. c++ 코드에 해당 함수의 구현부는 필요치 않다.
이것이 QT에서 제공하는 signal/slot 매커니즘으로 변경 사항을 QML에 알려주는 것이다. QML 화면이 처음 실행될때 displayMsg의 값을 가져와서 화면에 표시할 것이다. 하지만 마우스로 행을 클릭하여 인덱스 번호가 변경될 때마다 QML의 자신의 표시를 갱신해야 한다는 것을 자동으로 알지 못한다. 이때 emit을 통해 갱신을 요청하는 것이다. 자 마우스를 클릭할 때마다 숫자가 바뀌는 것을 확인할 수 있다. emit 라인을 삭제하면 c++안에서 위치값은 갱신 되어도 화면은 갱신되지 않는다.
이것으로 기본적인 C++과 통신에 대한 기본을 익혔다. 몇가지 추가적인 통신 방식도 필요하지만 과부하 걸리지 않도록 이후 과정에서 자연스럽게 설명해 보겠다.
AddressModel에서 대해서 이야기 해보고 싶다. 매우 간단하지만 샘플 수준에서 사용할 수 있어도 실무적으로는 사용하기는 어려워 보인다. 상업용 프로그램에서 화면에 수십, 수백개가 있다고 생각해 보자. 어떤 화면에 표시된 데이터들은 매우 큰 메모리를 차지할 수 있다. 하지만 사용자는 한번에 한 화면만 보게 될 것이고 메뉴등을 이용하여 다른 화면으로 변경할 것이다. AddressModel을 전역 객체로 선언해서 사용하려면 모든 화면에 필요한 객체들을 다 메모리에 올려두고 시작되어야 한다. 물론 위 방식으로 해도 불필요한 메모리를 사용하지 않도록 할 수 있지만 전역으로 선언하는 구조는 OOP적으로 좋다고 말하기 어렵다.
자 이제부터 구현하려는 방식은 QT관련 책에서도 유사한 방식을 보지 못했지만 나름 더 옳은 방식이라고 생각하는 것이니 집중해서 보길 바란다. 그것을 위해 가장 유명하며 GUI를 위해 범용적으로 사용되는 패턴에 대해서 설명해야 한다.
MVVM Pattern
MVP, MVVM, MP 등 여러 파생 pattern의 원형이 되는 MVC pattern은 application에서 가장 범용적으로 사용되는 architectural pattern이다. 이 pattern이 적절히 적용되면 user interface와 business logic 이 분리되어 상호간에 영향없이 고치는 것이 가능해진다.
-Controller는 모델에 명령을 보냄으로써 모델의 상태를 변경할 수 있다. (예: 워드 프로세서에서 문서를 편집하는 것) 또, 컨트롤러가 관련된 뷰에 명령을 보냄으로써 모델의 표시 방법을 바꿀 수 있다. (문서를 스크롤하는 것)
-Model은 모델의 상태에 변화가 있을 때 컨트롤러와 뷰에 이를 통보한다. 이와 같은 통보를 통해서 뷰는 최신의 결과를 보여줄 수 있고, 컨트롤러는 모델의 변화에 따른 적용 가능한 명령을 추가, 제거, 수정할 수 있다. 어떤 MVC 구현에서는 통보 대신 뷰나 컨트롤러가 직접 모델의 상태를 읽어 오기도 한다.
- View는 사용자가 볼 결과물을 생성하기 위해 모델로부터 정보를 얻어 온다.
이 패턴으로 부터 발전한 MVVM 패턴은 Model-View-ViewModel 로서 ViewModel이 Model과 Vieww사이에서 역할을 수행한다. ViewModel은 View관점에서는 Model이며 Model 관점에서는 View 처럼 행동한다. 즉 View와 Binding하여 Model에 변경이 생겼을 경우 View를 업데트하며 View에 변경이 생겼을 경우 Model에 전달하는 것이다. 이 패턴을 사용함으로서 가장 큰 장점은 View와 Model이 서로 전혀 알지 못함으로 구조적인 독립성을 가져 유연한 설계가 가능해진다. 하지만 Framework상에서 View와 ViewModel을 binding 해줄 수 있는 메커니즘이 있어야만 쉽게 적용 가능하다.
QML에서 MVVM을 적용
QT/QML은 signal/slot 매커니즘을 통해 binding 매커니즘을 잘 활용할 수 있도록 지원한다. MVVM 취지에 맞게 각 화면별 ViewModel을 C++로 작성하고 View와 ViewModel을 하나의 Pair로 관리해 보겠다. 더불어 main.cpp에 각 화면에 필요한 ViewModel을 생성한뒤 context 객체로서 추가하지 않고 View가 생성될때 ViewModel도 같이 생성하여 View와 연결하는 방식을 사용하겠다.
이미 작성한 AddressBokkViewModel 을 그대로 사용할 수 있다. 문제는 어떻게 하면 view가 생성될때 자동으로 AddressBookViewModel도 생성할 수 있는가? QT에서는 qml안에서 component중 하나로써 c++ 클래스를 사용할 수 있도록 등록하는 기능을 제공한다. main.cpp 에서 context 객체로 등록하던 부부을 삭제하고 아래와 같이 등록해 보자.
그다음 QML 에서 AddressBookViewModel을 Component로 선언한다. 이것을 사용하기 위해 import ViewModel 1.0 을 미리 선언해 두어야 한다.
그 다음 addressModel 로 선언했던 부분들을 myModel로 변경하면 적용이 완료된다.
이 방식의 장점은 사용자 보고자 하는 view가 메모리에 올라올때 필요한 ViewModel을 생성할 수 있고 view가 메모리에서 사라지면 viewmodel도 메모리에서 해제가 된다. 좀더 확인을 위해 메뉴를 추가하여 두개의 화면을 이동할 수 있도록 확장해 보자. 두개의 뷰를 전환하기 위해 보통 Menu와 Loader 를 사용한다.
main.qml을 ApplicationWindow를 사용하여 변경하였다. ApplicationWindow는 Menu 를 구성 요소로 가지고 있어 편리하게 추가할 수있다. 코드를 보면 유추할 수 있듯이 메뉴는 두개의 qml(AddressBookView, AboutMeView) 사이를 이동할 수 있도록 해준다. Loader는 처음 실행 시 AddressBookView를 로딩하여 화면에 보여준다.
AddressBookView는 이전에 main.qml에 있던 것을 그대로 옮긴것이니 달라진 시작 부분만 캡춰하겠다. 코드중에 onCompleted, onDesturction 부분은 이 QML이 로딩되고 해제될때 출력창에 메시지를 남겨 생성과 소멸에 대하여 확인하기 위함이다.
별것없는 AboutMeViw.qml은 아래와 같다.
생성과 소멸 시점을 파악하기 위해 cpp 코드에서도 아래와 같이 로그를 추가하였다.
최초로 프로그램으 시작하면 아래와 같이 로그를 확인할 수 있다. 즉 ViewModel이 먼저 메모리 생성되고 그 다음 qml이생성이 된다. 이제 메뉴를 이용하여 about창으로 가고 다시 addressbook 창으로 돌아가 보자.
로그를 보면 처음 세줄이 메뉴에서 AboutMe를 선택한 것이다. AddressBookView 가 메모리 에서 해제되고 AboutMeView 가 생성된 뒤 AddressBookViewModel에 메모리에서 된다.그다음 두줄은 다시 메뉴에서 AddressBook을 선택한 것이다.
정리하자면 Component로써 정의한 ViewModel을 View가 생성되기 이전에 생성되고 View가 소멸된 이후에 소멸되다. 따라서 View가 화면에 보여지며 작동할 때는 ViewModel은 반드시 있다고 보증할 수 있어 View의 Interaction에 대해 ViewModel이 적절히 대응할 수 있다.
마무리하며 다음 연재는?
이번 시간을 통해 C++과 QML이 상호 연동하고 MVVM 패턴을 어떤식으로 적용할 지 확인해 보았다. 다음 시간에는 이전시간에 구성한 ListView를 C++ 코드로 작성된 데이터를 기반으로 화면에 출력하고 새로운 주소 정보를 추가 삭제하는 것에 대해 진행해보자.
자 전시간에 이어서 이제 주소록을 위해 ListView를 구성해 보자. 상업용 프로그램에서 리스트뷰는 일련의 정형화된 데이터를 표의 형태로 표시해 주기때문에 아마도 버튼류나 입력을 제외하고 가장 많이 사용될 것이다. 리스트뷰를 설명하기 위해서는 Model / View 에 대해서 알아야 하나 일단 먼저 화면을 구성후 설명 하겠다.
ListView 구성요소
ListView 는 엑셀에서 보든이 표의 타이틀에 해당하는 header 와 일정한 형식의 데이터들이 컬럼으로 구분되어 반복되도록 표현하는 데이터를 표현하는 delegate 라는 속성을 가지고 있다.
위 코드를 실행하면 아래 화며이 나온다. header 영역이 녹색으로 표시됨을 알 수 있다. 어 근데 왜 데이터 영역은 파란색으로 보이지 않지? 데이터를 넣지 않았으니 당연하지 하면 개발자로서의 논리적인 감각이 있으니 정진하시라.
ListModel
자 이제 뭐라도 데이터를 넣어서 표시해보자. 실무에서는 이부분을 C++로 데이터를 만들어 넣지만 지금 거기까지 가기에는 아직 갈길이 멀다. C++로 데이터를 만들어 연동 전에 디자인이 잘 되었는지 확인하기 위해 QML 안에서 데이터를 생성해 보자. 아까 View / Model의 관계를 설명해야 한다고 했는데 아주 일부분만 먼저 말해야 겠다.
List형식으로 데이터를 보여주기 위해 QML에서는 ListModel 이라는 component를 제공한다. 일단 주소록의 데이터를 대략 아래와 같이 정의해 보자. qml안에 임의의 위치에서 아래와 같이 정의해보자. 3개의 주소록 정보를 담았다.
그리고 ListView 에서 model이라는 프로퍼티를 추가하고 ListModel의 id를 입력 후 프로그램을 실행해 본다.
짜짠! Header영역과 3개의 데이터를 의미는 3줄의 데이터 영역이 표시되었다.
현재의 구현으로는 ListView는 데이터가 3개라는 것만 알지 어떻게 표현해야 할지는 알지 못한다.
header & delegate 세부 디자인
header와 delegate를 위한 디자인을 ListViwe안에서 작성해도 되지만 Component 타입으로 별도 디자인한 후 id로 정의할 수 있으니 이번엔 그렇게 고쳐보자. 아래 코드는 데이터를 위해 Component를 디자인한 것이다.
위 24행에 정의한 listWidth는 별도의 사용자 정의 프로퍼티는 정의한 ListView의 넓이를 가리킨다. 이처럼 미리 정의된 property만 사용하는 것이 아니라 임의의 property를 추가할 수 있다. property를 정의하는 형식은 아래를 같다.
데이터행에서 ListModel에서 정의한 name, age, phoneNumber 를 mode. 을 붙여서 표현하면 ListView가 model 프로퍼티에 연결된 ListModel의 순환하면서 ListElement의 key와 매칭되는 속성을 찾은뒤 값을 읽어 표시한다.
이제 Header도 동일하게 디자인 해주자. 각 컬럼의 폭은 데이터와 동일한것이 좋으니 delegate 디자인을 카피해 와서 text만 헤더에 맞게 작성하고 실행해 보자.
이제 ListView 에서 header, delegate를 각각의 id을 선언한 후 실행 해보자. ListView 의 코드가 좀더 간결해졌다.
자. 뭔가 디자인이 구리긴 하지만 Header와 List Data가 출력되는 형식을 갖추었다. 디자인 감각은 없지만 최소한의 구분선이 없으로 뭔가 허전하다. delegate를 디자인 할때 Rectagle로 한번 더 선언한 뒤 margin과 border color를 지정해 보자. anchors.magins 에 값을 넣으면 4방향의 margin 값이 적용되고 상하만 margin을 주고 싶을 경우 topMargin, bottomMaring 에 값을 넣으면 된다.
그리고 표시되는 라벨 텍스트를 정렬하고, 배경 색상도 지정하면 좀더 괜찮아 보일 것이다. 헤더 영역은 darkgray, 데이터 영역은 "transparet"로 선언하겠다..
실행해 보면 아래와 같다. 처음의 밑밑한 디자인 보자는 좀 나아 보이지만 여전히 상업용으로 쓰기에는 구린 디자인이다. 하지만 그런 부분은 디자이너에게 맡긴다고 생각하고 좀더 기능을 완성해 보자.
User Interaction
프로그램을 실행했을때 뭔가 그럴싸해 보이지만 데이터행을 선택하거나 마우스가 움직여도 반응이 없다. 마우스가 데이터행을 이동할때마다 뭔가 색상이 변경되어 반응하도록 해보자. delegate의 안쪽 Rectang 에서 MouseArea를 선언하고 마우스 움직임에 따른 색상을 지정한다. onEntered, onExited는 각각 마우스가 영역 안에 진입할때와 빠져 나올때 발생하는 이벤트들이다. hoverEnabled : true 도 잊지 말고 설정해두어야 한다. 기본값이 false여서 영역안에서 마우스의 움직임에 대해서 event가 발생하지 않게된다. 아마 필요없는경우 불필요한 부하는 줄이기 위한 방안인듯 싶다.
자 이제 마우스의 움직임에 따라 lightblue 색상으로 데이터행들이 변경됬다. 이제 사용자 그중 한 행을 클릭했을 때 blue 색상으로 좀더 진하게 선택을 표시하고 선택된 데이터 행은 마우스가 이동해도 blue 색상이 유지되도록 해보자.
새로운 property 인 highlight를 정의해야 한다.
그리고 ListView에 highlight를 추가하여 Component 를 연결한다. 그리고 실행보면 예상처럼 작동되지 않는다. highlight 는 ListView가 가지고 있는 currentIndex 의 값이 변경될때 작동된다. 이제 MouseArea 에 Click이벤트를 추가하여 클릭시 인덱스를 변경해보자. 여기서 원래 c++개발자여서 느끼는 상당히 직관적이지 않은 부분이 있다. currentIndex 는 ListView가지고 있던 속성이라고 이해되지만 index는 어디서 나온것인가? delegate을위해 정의한 rectangle, row, mousearea어디를 봐도 index 속성이 찾을 수 없다. javascrpt 가 실시간으로 실행되면서 ListView의 row가 정의되면 그때 연결되는 변수명인듯 하다. 어찌보면 유연함 이겠지만 이해하면 학습하기는 상단히 까다롭낟. 어쨋건 delegate 안에서 index는 ListView에서 마우스가 위치한 행을 가리켜 준다.
의심스러워서 인덱스가 잘 선택되는지 로그도 남겨보았다. 자 이제 원하는대로 되었는지 실행해보자. 첫행이 블루로 보인다. 하지만 다른 행을 클릭할 경우 블루색상이 잠시 나타났다가 깜빡임을 볼 수 있을 것이다. 내부 메커니즘을 추적할 순 없지만 예상컨데 마우스 클릭에 의해 highlight 색상이 반영되었다가 onEntered 이벤트가 재작동되면서 lightBlue로 변경되는 것 같다. 테스트를 위한 위코드에서 onEntered/onExited를 잠시 주석처리 후 실행해보자.
예상대로 마우스 클릭 시마다 highlight 색상으로 잘 변경됨을 확인하였다. 자 이대로 끝내야 할까? 상업적 수준의 리스트뷰라면 마우스가 이동하면서 자연스럽에 hovering 되는 색상도 나오고 선택했을때 선택 색상도 나와야 할것이다. 좀더 코드를 고쳐보자. 즉 마우스가 행을 진입하거나 빠져나올때 선택된 행인지 판단하여 색상을 다르게 지정하는 것이다. 그리고 진입, 진출이 아니라 클릭했을때도 기존 색상을 투명하게 처리하여 highlight 속성이 작동되도록 처리한다.
자 이제 실행해보면 선택한 행은 blue로 마우스가 행을 이동하면 선택되지 않은 행들은 lightGray에서 lightBlue로 User Interactive하게 변경된다.
Scrolling
그럼 ListView의 기본적인 디자인은 끝난는가? 아니다. 지금은 리스트가 3개만 가지고 있다. 실제 프로그램들은 수백, 수천개를 가지고 있으니 테스트를 위한 ListModel의 갯수를 10개로 늘리고 창높이를 조절해 보자. 이렇게 한뒤 실행해도 ListView에서 기본 제공하는 기능으로 마우스 휠로 데이터 행을 이동해볼 수는 있다.
이제 스크롤바가 보이도록 추가하자. ListView는 자체적인 가로와 세로 스크롤바를 위한 속성을 제공한다. ListView 안에 적당한 영역에서 아래와 같이 ScrollBar.vertical 에서 Scrollbar를 선언해 주자. 스크롤바가 보이는 정책도 지정해 줄수 있도 AlwayOn를 항상 보이는 것이고 주석 처리된 AsNeeded는 스크롤바가 필요할 경우에만 보여준다.
자 이제 실행해서 최종적인 모양을 확인해보자, 스크롤링도 되고, 하이라이트, 호버링에 의한 인터랙션까지 모두 작동한다.
디자인만 예쁘게 하면 상업용으로 쓸수 있을까? 아니다 사용자는 아마도 컬럼의 넓이를 조절하고 싶을 수도 있다. 힌트를 주자면 지금까지 해온것처럼 header 디자인 영역에 label 만 있는게 아니라 일정한 선을 세로로 긋도록 하고 그 선을 마우스로 드래깅 할때 header의 column 넓이와 데이터의 comumn 넓이를 조정해 주면된다. 여기까지 잘 따라 왔으면 한번 도전해 보자. 거기까지 되면 상업용으로 쓸수 있을까? 역시 아니다. 사용자는 아마도 헤더의 특정 컬럼을 클릭하면 오름차순이나 내림차순으로 변경하고 싶을지도 모른다. 우린 그저 가장 기본이 되는 유저 액션까지 처리한 것에 불과하지만 이제 GUI를 넘어서는 작업을 할 준비는 되었다.
마무리 하며 다음 연재는?
ListView를 구성하기 위한 필수 속성 header, delegate, model, scrollbar등에 대하여 학습니다. 여긴 없지만 header외에 footer라는 것도 있어 하단의 summary 같은 것이 필요할 경우 정의할 수도 있다고 하니 사이트에서 문서로 공부 바란다. 이번 연재부터 각 연재마다 최종 작업물은 파일로 첨부하겠다.
계속되는 GUI 작업이 지겨웠을 수 있으니 다음시간에는 재미있는 C++ 코딩을 시작해보자 실제 DB나 파일로부터 읽어들였다고 가정한 수백개의 C++ 로 작성된 주소록 데이터를 어떻게 QML에서 불러들여 표시할 수 있는지 또 반대로 GUI에서 추가, 삭제, 편집한 데이타들이 C++의 자료 구조로 저장하는 방법에 대해 익혀보자.
그다지 보는 사람이 많지는 않지만 누군가를 위해서 작성한다기 보다는 나 자신의 정리를 위해서 다시 작성을 시작해 본다. 연재번호 001 - 006까지는 반드시 알아야 할 필요가 있긴 하지만 사실 재미라곤 1도 없는 지루한 과정이다. 모름지기 응용 프로그래머라면(혹은 꿈꾸고 있다면) 뭔가 화면이 나오고 사용자와의 인터랙션이 있는 프로그램을 만들어야 하지 않겠는가!
지금투넌 기본적인 지식을 먼저 설명하지 않고 바로 프로그래을 개발하면서 필요한 기본 사항들을 설명해 나가겠다.
앞으로 수회에 걸쳐 만들려고 하는 것은 QML기반의 간단한 인명 정보관리 프로그램이다.
리스트의 상단에는 추가, 삭제, 편집 버튼이 있고 그 버튼을 눌러 사람의 이름, 나이, 주소, 성별등을 추가, 삭제, 편집할 수 있다. 이 간단한 UI를 통해 리스트를 구성하는 법과 C++로 만든 자료구조를 QML과 연동하는 법, QML안에서 다른 QML을 뛰우는 법 등을 익히게 될 것이다. 단순하지만 결국 모든 업무용 응용 프로그래은 이 범주를 크게 벗어나지 않을 것이다.
또 프로그램을 다 만든 후 연재를 위한 내용을 쓰는 것이 아니라 구현하며 동시에 작성하기 때문에 페이지가 시간에 따라 계속 재 수정되서 업데이트되는 괴랄함도 볼 지 모르겠다. 아무튼 고!
UI 구성
연재가 한동안 끊기면서 QT5에서 QT6로 개발환경을 재구축 했다. 본 연재를 혹 따라하고 싶다면 2022년 1월 현재 6.2.2 버전을 설치하라 특히 설치 시 QT Compatibility 모듈을 선택하는 것을 잊지 말길 바란다. 설치는 각자 알아서 하시고 기본 UI를 구성해 보자.
QT Creator 6 에서 새로운 프로젝트는 QT5에 비해 단촐해졌다. 아래 메뉴를 통해 새로운 프로젝트를 생성하자.
프로젝트명은 AddressBook 으로 넘기면 아래와 같은 위자드 화면이 나오고 한단계씩 설정하며 넘어갈 수 있다.
Build System 은 새롭게 도입된 cmake가 아닌 예전 qmake를 선택한다(사실 아직 cmake 를 선택해서 만들어 보지 않았다.^^). Details에서 Minimum Required QT Version 은 6.2를 선택한다. 특별히 다국어를 지정하지는 않겠다. Kits는 당근 6.2.2 를 선택한다. 모든 단계가 완료되면 아래와 같은 화면이 생성될 것이다.
QML? Qt Quick? QML Based Programming? Qt Quick 은 Qt Quick 은 QML(Qt Modeling Language)이라는 인터프리터 언어를 사용한다. 위 코드를 보면 알 수 있듯이 QML 은 HTML / XML 구조와 흡사하고 상하 계층 구조로 되어 있다. 그 계층은 대괄호 구분된다. Window 라는 Component 를 이용하여 창을 생성한다. Window의 크기 설정을 위해 해당 property에 값을 입력한다. QML의 값 입력 방식은 콜론을 사용한다. QML 기반으로 프로그래밍하면 디자인과 로직을 분리하여 재사용성이 극대화된 개발을 가능해진다.
디자인된 QML 화면은 아래와 같이 main 함수를 통해 c++과 연계된다.
모든 c/c++프로그램을 main 함수가 entry point 이다. 여기서 GUI출력을 위한 Application entry 포인트인 QGuiApplication이 선언되어 있다. 이것이 UI를 위한 프레임원을 위한 전역 객체 되시겠다. QML 코드를 해석해서 화면에 표시하기 위한 QQmlApplicationEngine이 선언되어 있다. 그리고 engine이 main.qml을 로딩하고 app 이 실행되면 main.qml이 화면에 표시된다. 다시 화면 디자인으로 돌아가자.
여러분은 마치 키보드나 콘트롤을 드래깅하여 화면이 디자인 되는 것을 보고 싶다면 현재 화면 왼쪽으니 Edit탭을 Design 탭으로 변경해야 한다. edit화면에서 qml 코드를 보여주면 design tab도 활성화 되지만 괴상하게도 Qt Creator 6부터는 Design 탭이 기본적으로 비활성화 되어 있다. 활성화 하기 위해서 메뉴의 help -> about plugin 에서 QmlDesigner를 선택해 주어야 한다.
자 이제 Design tab도 활성화 되었겠다. 디자인을 시작해 보자. main.qml이 활성화 된 상태에서 design 탭을 선택하면 아래와 같은 화면이 나온다. 화면 구성이 좀 다르다면 원하는 대로 변경해 주면 이후 계속 적용된다. 아래 상단의 Form Editor 가 미리보기 화면이고 하단의 Text Editor가 qml 코드이다. 외쪽을 콤포넌트 박스로서 원하는 GUI 콤포넌트를 추가할 수 있다. 그리고 오른쪽은 각성 배치나 정렬등을 설정하는 사이드 바이다.
아래를 보면 Form Editor에서 폼이 너무 커서 한 화면에 안보일 것이다. Form Editor 바로 아래에 보여있는 툴바중 오른쪽에서 세번째 버튼을 클릭하면 현화면에 다 보이도록 폼을 리사이징 해준다.
자 화면을 2단으로 상하 분할 하여 상단에는 버튼들(추가, 삭제, 편집)을 나열하고 하단에는 리스트를 구성하자. 버튼을 추가하기 위해 왼쪽 콤포넌트 뷰를 보면 뭔가 이상한 것을 느낄 것이다.
QML Type
버튼이 없다. 세상에 버튼이 없다. 비슷한 거라곤 꼴랑 rectangle 하나밖에 없다. 거기서부터 만들라는 것인가?
맞다 사실 그걸 이용해서 버튼을 만들면 된다. QML에서 GUI를 구현하기 위해 Object로 Type을 제공한다. 자주 사용되는 타입과 의미는 아래와 같다.
Type Name
Description
Rectangle
사각형 영역으로 아이템을 표시한다. 아래 코드는 화면의 왼쪽 상단을 기준으로 50, 50 px 만큼 떨어진 가로 세로 100x100 px을 녹색 백그라운드를 가지고 4모서리의 반경이 6으로 부드러운 사각형을 말한다.
Retangle은 그 안에 중첩된 다른 타입을 선언할 수 있다. 이때 내부에 중첩된 사각형의 x, y 좌표는 포함하고 있는 사각형의 왼쪽 상단이 기준이 된다.
Image
jpg, png, bmp와 같은 이미지를 표시한다. 위의 rectangle을 마치 액자처럼 프레임하여 이미지를 표시할 수 있다. 아래 코드는 위에서 선언한 사각형 안에서 6,6만큼 떨어진 부분에 이미지를 그려준다. Rectangle { width: 100 height: 100 radious: 6 color: "green" Image { x: 6 y: 6 source: "./images/sample.jpg" }
Text
문자열을 표시해주는 매우 간단한 Type 이다.
Text{ text: "hi"; }
Window
여러가지 콘트롤들이 출력될 수 있는 Window Type 이다.
Item
사용자 정의 아이템으로서 사용자가 원하는 특별한 형태의 콘트롤을 만들고자 할 경우 qml파일의 최상위 Type이 된다.
Anchors
Type 안에 정의하여 자신의 위치와 크기를 다른 Type (보통 상위 타입) 기준으로 배치할 수 있도록 해준다. 아래 코드를 보면 id : sub의 사각형이 id: master의 사각형 안에서 한 가운데에서 배치하고 할 경우 anchors를 이용하여 간단하게 한 가운데에 배치할 수 있다. anchors에서 제공하는 fill, left, right, top, bottom, margins 등을 이용하여 한쪽에 붙도록 배치하거나 조금 띄어서 배치하는 것도 가능하다.
사용자 정의 아이템이나 Sub View들은 보통 별도의 파일로 분리한다. 이 분리된 아이템을 한 화면에 표시하는 여러 방식이 있는데 그중 한가지 방식으로 Loader를 이용할 수 있다. 아래 코드가 어떤 Window.qml안에 있다면 그 창이 생성되면서 OtherView에 정의된 화면이 조합되어 표시할 수 있다. 물론 메뉴같은 것을 이용해 Loader의 Source 속성에 메뉴마다 다른 qml을 연결해주면 Sub View 메뉴에 따라 변경하여 화면에 표시할 수 있다.
Loader { source: "OtherView.qml" }
MouseArea
window나 rectangle 같은 타입 안에서 정의하여 마우스 이벤트를 처리한다. 아래 코드는 사각형을 구성하고 그 안에 text를 표시하고 마치 버튼처럼 클릭할 경우 텍스트의 내용이 변경된다. 이런 응용을 통해 간단하게 버튼을 구현할 수 있다. 코드에서 유의할 점은 마우스가 클릭했을 때 발생하는 이벤트가 있고 그 안에서는 마치 javascript 처럼 값 대입을 위해 QML과는 달리 "="를 사용한다는 것이다. 그리고 마우스가 클랙되기 위한 영역을 할당 하기 위해 anchors 를 이용하였다.
하지만 자주사용하는 일반적인 콘트롤(button, listview, checkbox, radiobutton, textbox, ....) 등등을 위해 QuickControl을 제고하고 두번의 버전업을 통해 Quick Controls 2를 제공하고 있다. 이 콘트롤은 QML기본 타입이 아니므로 import라는 키워드를 이용하여 추가해야 한다.
Button과 List View를 드래깅하여 대략 아래와 같이 배치해 보자. 버튼의 텍스트는 코드를 보면 text: 라고 써있는 부분 오른쪽에 작성하면 된다.
대략 디자인을 했으면 Build & Run 버튼으로 실행해보자.
축하한다. 첫번째 QML이 만들어졌다. 근데 아무리 디자인 감각이 없는 개발자지만 최소 정렬은 제대로 좀 해야하지 않겠는가? 또한 창을 최대 최소화 하면 하단의 리스트를 그에 맞춰 리사이징 되는 것이 좋을 것이다. 물론 정밀한 마우스 이동 신공으로 버튼을 화면상에 나란히 잘 배치할 수 있지만 Layout을 위한 Element를 이용하는 것이 좋다.
Layout
Description
RowLayout
내부의 Element들을 수평으로 배치하며 크기조절
ColumnLayout
내부의 Element들을 수직으로 배치하며 크기조절
GridLayout
내부의 Element들을 바둑판처럼 배치하며 크기조절
Layout
위 3개중 하나를 지정할 수 있음.
먼저 ColumnLayout을 통해 화면을 상하로 2분할 한뒤 위쪽에 버튼 세개, 아래쪽에 리스트뷰를 배치해보자. QML은 콤포넌트 안에 콤포너트를 넣어 재귀적으로 관리할 수 있으니 ColumnLayout {} 추가해서 모든 버튼과 ListView를 ListView 의 양 괄호 안에 넣자. 이걸 하기 전에 QML에 import QtQuick.Layouts 를 추가하여 Layout을 쓴다고 선언해 주어야 한다.
Layout 을 이용하여 내부 컴포넌트를 크기와 위치를 배치하기 위해 x, y, widht, height 속성은 모두 삭제해주자. 아마 Form Editor에서 모든 UI구성이 세로로 배치되었을 것이다. 우리가 원하는 것은 버튼 3개는 나란히 배치하고 그 아래에 ListView는 배치하는 것이었다. 그리고 버튼을 한줄에 나란히 표시하기 위해 3개의 버튼을 Row Component로 둘러싼다. 그리고 일단 ListView는 삭제하고 Rectagle로 변경하자. ListView 각종 컬럼과 컬럼에 들어갈 내용을 디자인 하기 위해서 매우 복잡하다. 일단 Rectangle로 교체하여 배치 후 추후 별도 디자인 하자.
RowLayout과 Row의 차이는 RowLayout은 위치와 크기를 설정할 수 있고 Row는 단순히 내부 Component의 위치만을 배치할 수 있다. 좀 미묘하긴 한데 일단 학습 삼아 버튼 그룹을 나란히 배치하기 위해 Row를 사용하자.
주의: 어떤 component의 상위가 Layout 타입인 경우 width, height 를 사용하여 크기를 지정하면 안되고 Layout.으로 시작하는 속성을 이용해야 한다.
버튼을 그룹핑하는 row와 리스트뷰 영역 할당을 위한 Retangle의 상위 콤포넌트는 layout 타입이니 Layout.fillHeight, Layout.fillWidth를 이용하고 true 로 설정하면 해당 영약이 창 크기에 따라 자동으로 확대 축소된다. Row는 fillWidth를 true로 함으로써 내부 버튼들이 창 전체 넓이 기준으로 배치할 수 있도록 했고 ListView 는 FillWidth, FillHeight를 true 로 설정하여 전체 영역을 사용하도록 했다. 이때 주의할 점은 버튼을 그룹핑하는 Row에 FillHeight를 지정하지 않았다. 이런 방식으로 ListView 는 버튼 영역을 제외한 크기로 자동 조절된다.
확인을 위해 Rectangle의 경계선을 검은색으로 한 수 Run하여 창 크기를 조절해 보면 리사이징 되는 것을 확인할 수 있다. Rectangle 에 margin을 두면 검은 사각형이 창안쪽으로 들어와 더 확인하기 좋다.
자 여기서 버튼의 넓이를 화면의 크기에 3등분하여 배치하고 싶다면 어떻게 해야할까. 자바스크립의 실시같 인터프리팅의 장점을 잘 활용할 때다. button의 width 속성에서 전체 넓이를 1/3한 값을 넣으면 된다. 전체 넓이를 알기위한 중요한 키워드 parent가 나왔다. parent란 상위 component 가리키는 키워드 이다.
즉 상위 component인 row의 넓이를 가져와 1/3 을 계산하여 button의 width 로 정해겠다는 것이다. 하는 김에 row의 속성인 spacing 에 값을 할당하여 버튼 사이가 입력 값만큼 여유가 생기고 Layout.margins으로 외곽선 부분에 여유 공간을 할당한다.
위 코드에서 단지 넓이를 1/3하게 되면 margin과 spacing때문에 버튼 3개 넓이의 합이 창의 넓이를 넘게 된다. 그래서 양쪽 마진과 3버튼 사이의 두개의 spacing 값을 뺀 넓이에서 1/3을 해 주어야 한다. 아니 버튼 배치하는데 이렇게 복잡하게 한다고? 물론 위치와 크기를 조절할 수 있는 RowLayout을 이용하면 매우 간단히 할 수 있으니 한번 해보시라.
다음 연재는?
버튼을 배치하고 ListView를 넣을 Rectangle 영역을 배치했으니 이제 본격적으로 상업적으로 많이 활용되는 ListView를 디자인 해보자.
잘 알고 있겠지만 Qt는 단순한 크로스 플랫폼을 위한 GUI 프레임웍이 아니다. 크로스 플랫폼에서 개발되는 거의 모든 것을 지원하는 거대한 프레임웍과 라이브러리들의 모음이다. 따라서 그에따른 오버헤드가 따른다. 즉 이미 표준 C++에서 지원하고 있는 자료 구조들의 Qt 버전을 별도로 학습해야 한다는 것이다. 물론 표준 C++의 자료 구조를 사용해도 되지만 가급적 Qt 에서 제공하는 것을 사용하길 바란다. Qt에서 제공하는 모든 클래스들은 QObject를 상속받고 있으며 이로 인해 장점을 누릴 수 있다. 괜히 C#이나 Java가 베이스클래스를 기본적으로 가지고 있는게 아니다.
자료 구조는 문서를 보면 굉장히 많은 함수들이 있고 매우 매우 직관적이기 때문에 특별히 길게 설명하지 않고 이런것들이 있다는 것 위주로 나열하겠지만 향후 실무에서 유용하게 사용하는 함수들에 대해서는 따로 설명하겠다.
QString / QStringList
QString 문자열을 조작을 포함한 기능을 제공하다. 더 좋은 것은 아래처럼 문자열의 encoding 까지도 지원한다.
QStringList는 결국 뒤에서 설명할 QList 를 이용한 QList<QString> 과 거의 유사하다. 하지만 이것에 부가적의 편의 함수들이 추가되었다.
QDateTime,...
MFC에 익숙하다면 CDateTime 클래스 생각하면 쉽다. Modern C++에서 chrono가 들어오면서 많이 편해지긴 했다.
패밀리 클래스로 QDate, QTime 이 있다.
흔히 많이 쓰는 날짜를 문자열로 바꾸는 함수를 사용하여 길게 표시하는 것과 짧게 표시하는 해 보았다.
모두 Qt에서 제공하는 Generic 컨테이너 클래스이다. C++standard에서 제공하는 list, vector, set, map 와 같은 역할을 수행한다. 그럼 그거 쓰지 왜 자꾸 따로 만들었냐 할 수 있겠으나 Qt의 긴 역사와 QObject를 상속받는 구조를 고려할때 납득이 가는 부분이다. 대신 힘들게 C++ 을 공부했다면 쉽게 넘어갈 수 있다. 대신 많은 추가적인 함수들을 제공하고 있으므로 너무 대충 넘기지 말고 사용할때마다 문서를 보면서 이미 있는 기능을 다시 개발하는 우를 범하지 않기를 바란다.
QVariant
자 드디어 못보던 것이 나왔다. 표준 C++에서는 제공하지 않는 것으로 다양한 타입의 Qt 자료 구조를 저장할 수 있다. 좀 생성자를 보면 과하다 싶을 정도로 모든 형태의 데이터 타입을 지원하고 있다. 특이한 것은 Jason과 같은 타입까지도 지원한다.
Signal 은 우리말로 신호를 의미하고 Slot은 무언가를 넣을 수 있는 자리나 틈 같은 것을 의미한다. 즉 신호가 발생하면 slot에 있는 무언가를 시작한다. 디자인 패턴을 이해한다면 "생산자 소비자 패턴"을 수행한다고 생각할수도 있겠다.
보통 하나의 클래스에서 시그널과 슬롯을 구현하면서 설명을 하겠지만 신호가 발생되는 곳과 그것에 대한 처리가 발생하는 곳이 항상 같지는 않으므로 별개의 두개의 클래스를 가지고 설명해보겠다.
Signal이 발생하는 곳을 SignalGenerator라고 하고, Slot을 처리하는 곳을 SlotProcessor라고 하자. 주의할 점은 Signal and Slot 메커니즘이 작동하기 위해서는 반드시 QObject를 상속받아야 한다. 그 외에 매크로도 추가해야 하지만 Qt Creator는 간편한 위저드 기능을 제공한다.
아마 특별한 설정을 하지 않았다면 파일명이 소문자로 표시될 것이다. Qt Creator의 기본값이 소문자여서 그렇다. 아래와 같이 옵션으로 변경할 수 있다.
프로젝트명에서 마우스 우클릭을 하여 Add New 를 실행하면 위와 같은 위저드 화면이 나온다. 여기서 Base class를 QObject를 선택하면 자동으로 필요한 내용이 추가로 체크된다.
생성된 파일을 헤더를 보면 필요한 상속과 매크로까지 미리 작성되어 있음을 확인할 수 있다. 이제 Sinal을 발생시키는 부분을 작성해 보자.
위 소스를 보면 emit란 용어는 처음 볼겠지만 대략 GeneratorTextSignal 함수가 호출되면 내부적으로 textSignal 을 호출함을 알 수 있을 것이다. 사실 위에서 보이는 signals 와 emit은 표준 c++에는 있지 않은 용어다. 즉 qt가 정의한 키워드이며 qt는 c++ 소스 코드에서 해당 키워드를 이용하여 표준 c++에 맞도록 코드를 추가하고 컴파일을 진행한다.
SlotProcessor의 함수를 보면 두 클래스의 의도가 SignalGenerator에서 GeneratorTextSignal을 호출하면 Signal and slot 메커니즘에 따라 SlotProcessor의 onSlotMessage가 호출되기 위한 코드이다. 두 클래스의 인스탄스를 main에서 정의해보자.
자 위 코드를 추가한 후 성급하게 빌드하고 실행해본 사람이 없길 바란다. 변수 sg 에서 GenerateTextSignal을 통해 Signal을 발생시켰지만 sp는 그 발생 사실을 알 수 없어 slot함수를 실행할 수 없게 된다. Qt는 signal / slot 키워드를 통해 추가적인 코드를 만든는 것이지 알아서 두 클래스를 연결해 주는 것은 아니다. 마법은 없다.
Connect 함수를 통해 두 인스탄스를 Signal / Slot을 연결해 주고 나서 실행하면 아래와 같은 텍스트가 출력됨을 Application Out 창을 통해 확인할 수 있다. connect 함수에 F1 을 클릭해서 (더이상 F1을 이야기 하지 않겠다) 설명을 보면 여러가지 버전으로 재정의 되어 있다. 처음에 주석 처리한 방식도 정상적으로 작동된다. 나는 그중에 멤버 함수의 포인터를 이용하여 연결하는 방식을 사용하였다.
자 이제 좀더 자세히 이게 뭐하는 것인지 살펴보자. 이처럼 기능이 작동되는 것을 보면 C++ 개발자라면 함수 포인터를 이용한 Callback 함수랑 뭐가 다른거지 라는 생각이 들겠지만 마지막 파라미터인 ConnectionType을 보면 보다 진보된 형태의 함수 포인터를 관리해준다고 느낄 수 있다.
즉 콜백함수와 같이 즉시 실행되는 것만이 아니라 상황에 따라 유연하게 queue에 넣어 slot 함수의 작동을 제어할 수 있다. 또한 connect함수에 대응되는 disconnect함수도 제공하여 연결을 끊을 수도 있다.
Signal / Slot 은 개념이해는 큰 어려움이 없을 것이다. 오히려 문법이 생소해 잘 외워지지 않는데 Signal에서 선언한 함수의 형태와 Slot 에서 구현된 함수의 형태가 같아야만 하는 것을 기억하면 된다.
QtCreator를 이용하여 새로운 콘솔 프로젝트를 아래와 같이 생성해 보자. 나는 관행적으로 D드라이브에 _dev라는 폴더를 두고 그 아래에 개발 관련 프로젝트를 모아 놓는다. 편한곳에 생성하면 된다.
다음으로 이동하면 처음부터 당황스런 화면이 나온다. 기본값은 qmake 이니 그대로 선택하여 다음으로 가자. qt6부터는 cmake가 기본이라고도 하던데 뭐가 다른지는 그때 고민해 보자.
다음을 눌러보자. 뭔가 더 복잡한게 나왔다. 이것은 Qt가 다국어를 처리하기위 지원하는 파일이다. 실무적으로 개발하기 전까지는 항상 none으로 설정하겠다. 다음.
뭔가 더 복잡한게 나왔다. 키트를 선택하란다. 키트란 뭔가? 그동안 대충 다음으로 넘어갔지만 이번 정리를 위해 좀더 찾아 보았다. 키트란 결국 배포하려는 플랫폼을 의미한다. 나는 여러 버전의 Qt를 설치했으므로 많이 나오지만 여러분은 좀 다르게 나올것이다. 적절히 Qt5 버전대를 선택하라.
None으로 그대로 두고 다음으로 넘어간다. 마찬가지로 실무적으로 이용할 경우 version control은 당연히 하겠지만 이 정리에선 하지 않겠다.
프로젝트 구성
자 드디어 익숙한 소스 코드가 보인다. 개발자라면 무수히 보았을 main.cpp 소스 파일 하나과 뭔지 모를 HelloWorld.pro 파일 하나가 덩그러니 있다.
이 pro 파일이 Visual Stuio에서의 프로젝트 파일 역할을 한다. 하지만 훨씬 직곽적이고 수정하기가 용이하다. 아래를 보면 콘솔 프로젝트 임으로 gui 가 프로젝트에서 배제되어 있음을 알 수 있다. 또한 C_++11 기반으로 개발하고 있으며 main.cpp가 project에 등록되어 있음을 알 수 있다. Qt는 이렇게 +=, -=통해 속성들을 추가, 삭제할 수 있다.
코드 보기
자 이제 본격적으로 소스 코드를 살펴보자.
메인 함수 안에는 아무것도 없고 처음 보는 QCoreApplication 하나가 선언되어 있다. 한번 실행 해보자. 실행은 좌측 하단의 녹색 버튼을 클릭하면 된다.
자 기대했던것이 나왔는가? 기대했던 Hello World 글자는? 아싸 소스 코드를 보면 알겠지만 Hello World를 출력하는 코드가 없다. QCoreApplication은 Qt 기반의 프로그램을 시작하도록 해주는 객체이고 현재 프로그램은 a.exec() 함수 안에 머물러 있다. 자 강제로 종료하고 코드를 입력해보자.
C++방식으로 Hello World를 출력하도록 하고 실행하면 Hello World를 볼 수 있다. 이제 다른 방식은 어떨까. Qt의 기능을 이용할 수 있으면 좋겠는데. 있다. qDebug 를 이용하는 것이다.
Qt 에서 제공하는 qDebug 기능을 이용하면 Qt가 내부적으로 Console에 출력을 시켜준다. 해당 키워드에 대해 좀더 알고 싶으면 qDebug 에 마우스를 대고 F1키를 누르면 친절할 설명이 나온다. qDebug외에서 qInfo, qWarn, qFatal, qError 등이 있다. 나는 잘 쓰지 않지만 이렇게 레벨을 걸고 출력을 하도록 한뒤 일정 레벨만 실제 콘솔에 출력하도록 세분화 할 수는 장점이 있다.
이제[ 코드를 좀더 자세히 보자. 전통적인 c++ 프로그램의 main 함수가 호출되면 콘솔창이 생성되고 키입력과 같은 코드를 넣지 않으면 그대로 프로그램이 종료된다. 프로그램을 실행하면 텍스트가 출력되고 Console 창이 그대로 떠 있다. 어떻게 된 걸까? 비밀은 QCoreApplication이 선언되어 있고 exec 함수를 호출함에 의해 가능해진다. QCoreApplication에 마우스를 클릭하고 F1을 눌러 설명을 보자.
이런 설명을 볼수 있다. 즉 UI가 없이 Qt 애플리케이션의 이벤트 루프를 제공한다라고 되어 있다. 좀더 자세히 보기 위해 More를 클릭해 보자.
QCoreApplication은 이벤트 루프를 유지하고 그 이벤트 루프는 exec 함수를 호출하면서 시작된다. 이벤트 루프를 종료하기 위해선 quit 와 같은 함수를 호출했을때 가능하다라고 되어있지만. console 환경에서는 창을 강제 종료해야만 종료할 수 있다. 설명을 보면 signal / slot 이란 용어가 나온다. Qt 메커니즘의 가장 중요한 개념인 Sinal / Slot에 대해 알아보자.
먼저 템프릿을 첫번째인 Application(Qt)를 클릭하면 두개의 템플릿을 볼 수 있다. 나는 Widget를 다루지 않을 것이므로 간단히 콘솔기반인 두번째를 선택하여 Qt 의 핵심적이 부분에 대해서 설명할 것이다.
두번째를 선택하면 Quick 버전에 따라 창의 구성이 조금씩 달라지긴 하지만 대동 소이하다. 전부 QML을 이용한 것이고 Empty 프로젝트에 추가적인 기능을 보완한것이다. 이 정리에서 기본적인 부분을 위해 Console과 Empty 프로젝트만으로 설명하고 실무적인 부분을 설명할때 다시 추가적인 Template에 대해 설명하겠다.