• 통신의 기본요소

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 patternapplication에서 가장 범용적으로 사용되는 architectural pattern이다. pattern이 적절히 적용되면 user interfacebusiness 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++ 코드로 작성된 데이터를 기반으로 화면에 출력하고 새로운 주소 정보를 추가 삭제하는 것에 대해 진행해보자.

AddressBook.zip
0.00MB

+ Recent posts