C++ 과 Qt/QML을 이용한 개발 - 007: (Quantum Jump-1)주소록 프로그램-1. GUI 기본 구성
- 다시 연재를 시작하며
그다지 보는 사람이 많지는 않지만 누군가를 위해서 작성한다기 보다는 나 자신의 정리를 위해서 다시 작성을 시작해 본다. 연재번호 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으로 부드러운 사각형을 말한다. Rectangle { x: 50 y: 50 width: 100 height: 100 radious: 6 color: "green" } 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 등을 이용하여 한쪽에 붙도록 배치하거나 조금 띄어서 배치하는 것도 가능하다. Rectangle { id: master width: 300 height: 300 Rectangle { id: sub width : 100 height :100 anchors.centerIn: parent } |
Loader | 사용자 정의 아이템이나 Sub View들은 보통 별도의 파일로 분리한다. 이 분리된 아이템을 한 화면에 표시하는 여러 방식이 있는데 그중 한가지 방식으로 Loader를 이용할 수 있다. 아래 코드가 어떤 Window.qml안에 있다면 그 창이 생성되면서 OtherView에 정의된 화면이 조합되어 표시할 수 있다. 물론 메뉴같은 것을 이용해 Loader의 Source 속성에 메뉴마다 다른 qml을 연결해주면 Sub View 메뉴에 따라 변경하여 화면에 표시할 수 있다. Loader { source: "OtherView.qml" } |
MouseArea | window나 rectangle 같은 타입 안에서 정의하여 마우스 이벤트를 처리한다. 아래 코드는 사각형을 구성하고 그 안에 text를 표시하고 마치 버튼처럼 클릭할 경우 텍스트의 내용이 변경된다. 이런 응용을 통해 간단하게 버튼을 구현할 수 있다. 코드에서 유의할 점은 마우스가 클릭했을 때 발생하는 이벤트가 있고 그 안에서는 마치 javascript 처럼 값 대입을 위해 QML과는 달리 "="를 사용한다는 것이다. 그리고 마우스가 클랙되기 위한 영역을 할당 하기 위해 anchors 를 이용하였다. Rectangle { width: 100 height: 100 radious: 6 color: "green" Text { id: buttonText text: "Not Clicked Yet" anchors.centerIn: parent MouseArea { id: mouseArea anchors.fill: parent onClicked: { buttonText.text = "not it's clicked" } } } } |
하지만 자주사용하는 일반적인 콘트롤(button, listview, checkbox, radiobutton, textbox, ....) 등등을 위해 QuickControl을 제고하고 두번의 버전업을 통해 Quick Controls 2를 제공하고 있다. 이 콘트롤은 QML기본 타입이 아니므로 import라는 키워드를 이용하여 추가해야 한다.
위 import QtQuick 코드 아래에다가 import QtQuick.Controls 를 작성해보자. 추가 후 왼쪽 Component 목록을 보면 Quick Controls가 추가된 것을 확인할 수 있다. 콘트롤들은 종류도 다양하니 qt 사이트의 가이드를 참고하여 이해해 보자. Qt Quick Controls Guidelines | Qt Quick Controls 6.2.2
휴 처음부터 날코딩할 뻔 했다. 이제 이것을 이용해서 화면을 구성해보자.
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를 디자인 해보자.