이전 포스팅에서 ListView를 QML로 보여주고 그 안에서 동적으로 관리하는 예제를 살펴봤습니다.
이번에는 C++에서 AbstractItemModel을 상속받아 Model을 만들고 그 Model을 가지고 데이터를 처리해보겠습니다.
예전에 설명했듯이 List로 데이터를 Display하기 위해서 Model(데이터), View(리스트 그려줌), Delegate(Style)이 필요했습니다.
ListModel {
id:nameModel
ListElement {
name: "park"
age : 20
}
ListElement {
name: "kim"
age : 25
}
ListElement {
name: "lee"
age : 30
}
} // 데이터를 담는 Model
Component {
id: nameDelegate
Item {
width : windows.width
height : dataHeight
Text {
anchors.fill: parent
text: "name : " + name + "\n" + "age : " + age;
verticalAlignment: Text.AlignVCenter
anchors.leftMargin: 10
}
MouseArea {
anchors.fill: parent
onClicked: nameList.currentIndex = index
}
}
} // 데이터를 표현하는 Style
ListView {
id : nameList
width : parent.width
height : listViewheight
model : nameModel
delegate: nameDelegate
highlight: listHighLight
} // 데이터를 보여주는 View
위 예제에는 Model을 qml 내에서 자체적으로 생성하였고 qml의 이벤트를 통해서 append하거나 remove 하였습니다.
Rectangle {
width : parent.width/nCount
height : parent.height
color: "gray"
border.width: 1
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text : "input"
}
MouseArea {
anchors.fill: parent
onClicked: {
nameModel.append({ name: nameInput.text,
age : parseInt(ageInput.text)
})
nameInput.text = ""
ageInput.text = ""
nameInput.focus = false
ageInput.focus = false
}
}
}
Rectangle {
width : parent.width/nCount
height : parent.height
color: "red"
border.width: 1
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text : "delete"
}
MouseArea {
anchors.fill: parent
onClicked: {
if ( nameModel.count > 0 ) {
nameModel.remove(nameList.currentIndex);
nameList.currentIndex = -1
}
}
}
}
이번에는 model 자체를 C++ 객체를 통해 가져와보겠습니다. C++ 쪽 클래스는 QAbstractListModel을 상속받는 Model 클래스와 그 클래스를 가지고 있는 Manager 클래스를 만들겠습니다. qml의 ListView는 이 Manager 함수를 통해 Model을 관리하게 됩니다.
위 설명대로 nameList에서는 model로 ModelMgr을 통해 MDataModel을 받고 있습니다. 그리고 input, delete도 ModelMgr을 통해 Model에 접근하고 있습니다.
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
...
Column {
spacing : columnSpacing
width : parent.width
height : parent.height
ListView {
id : nameList
width : parent.width
height : listViewheight
model : ModelMgr.getModel()
delegate: nameDelegate
highlight: listHighLight
} // 데이터를 보여주는 View
...
Rectangle {
width : parent.width/nCount
height : parent.height
color: "gray"
border.width: 1
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text : "input"
}
MouseArea {
anchors.fill: parent
onClicked: {
ModelMgr.addItem(nameInput.text, parseInt(ageInput.text));
nameInput.text = ""
ageInput.text = ""
nameInput.focus = false
ageInput.focus = false
}
}
}
Rectangle {
width : parent.width/nCount
height : parent.height
color: "red"
border.width: 1
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text : "delete"
}
MouseArea {
anchors.fill: parent
onClicked: {
ModelMgr.removeItem(nameList.currentIndex)
}
}
}
}
}
}
하지만 이대로 실행한다면 아래처럼 에러 메시지가 나오게 됩니다.
main.qml 측에서는 MDataModel 클래스에 대한 정보가 없기 때문입니다. 이를 위해서 qmlregistertype를 통해서 등록시켜줘야 합니다. 추가로 qml에서 ModelMgr의 함수를 호출할 것이기 때문에 setContextProperty를 통해 넘겨줍니다.
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "ModelMgr.h"
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
qmlRegisterType<MDataModel>("DataModel", 1, 0, "MDataModel");
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
ModelMgr dataMgr;
engine.rootContext()->setContextProperty("DataMgr", &dataMgr);
engine.load(url);
return app.exec();
}
qmlRegisterType를 통해 객체를 등록했으면 qml 에서는 import 시켜줄 수 있습니다.
import QtQuick 2.15
import QtQuick.Window 2.15
import DataModel 1.0
Window {
property int listViewheight : ModelMgr.count * dataHeight;
property int dataHeight : 50;
property int inputUIHeight : 30;
property int columnSpacing : 10;
...
}
다음은 ModelMgr 클래스입니다. 이 클래스는 QAbstractListModel을 상속받은 클래스를 변수로 가지고 있고 qml에서 add, remove 시에 Model에 전달해주기 위한 클래스입니다. count를 통해 listView의 크기를 동적으로 조절하기 위해 Q_PROPERTY로 설정하고 add, remove 시 countChanged signal을 발생시키고 있습니다.
#ifndef MODELMGR_H
#define MODELMGR_H
#include <QObject>
#include "mdatamodel.h"
class ModelMgr : public QObject
{
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
explicit ModelMgr(QObject *parent = nullptr);
Q_INVOKABLE MDataModel* getModel() { return &m_Data; }
Q_INVOKABLE void addItem(QString sName, int age);
Q_INVOKABLE void removeItem(int nRow);
int count() { return m_Data.rowCount(); }
signals:
void countChanged();
private:
MDataModel m_Data;
};
#endif // MODELMGR_H
#include "ModelMgr.h"
#include <QDebug>
ModelMgr::ModelMgr(QObject *parent)
: QObject{parent}
{
}
void ModelMgr::addItem(QString sName, int age)
{
m_Data.addItem(sName, age);
emit countChanged();
}
void ModelMgr::removeItem(int nRow)
{
m_Data.removeItem(nRow);
emit countChanged();
}
다음으로 QAbstractListModel을 상속받고 있는 MDataModel 클래스입니다. 이 QAbstractModelItem을 상속받아 사용하기 위해서는 virtual로 되어 있는 세 함수는 모두 override 해주어야 합니다.
#ifndef MDATAMODEL_H
#define MDATAMODEL_H
#include <QAbstractListModel>
typedef struct _info {
QString sName;
int nAge;
_info() {
sName = "";
nAge = 0;
}
} info;
enum enRole {
nName = Qt::UserRole,
nAge
};
class MDataModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit MDataModel(QObject* parent = nullptr);
virtual int rowCount(const QModelIndex & = QModelIndex()) const;
virtual QVariant data(const QModelIndex &index, int nRole) const;
void addItem(QString sName, int age);
void removeItem(int nRow);
protected:
virtual QHash<int, QByteArray> roleNames() const;
private:
QList<info> m_infoList;
};
#endif // MDATAMODEL_H
#include "mdatamodel.h"
#include <QDebug>
MDataModel::MDataModel(QObject *parent) :
QAbstractListModel(parent)
{
}
int MDataModel::rowCount(const QModelIndex &) const
{
return m_infoList.count();
}
QVariant MDataModel::data(const QModelIndex &index, int nRole) const
{
int nRow = index.row();
if (nRow < 0)
return QVariant();
info item = m_infoList.value(nRow);
QString sData = "";
switch (nRole)
{
case nName:
sData = item.sName;
break;
case nAge:
sData = QString::number(item.nAge);
break;
}
return sData;
}
QHash<int, QByteArray> MDataModel::roleNames() const
{
QHash<int, QByteArray> role;
role[nName] = "Data";
role[nAge] = "Age";
return role;
}
void MDataModel::addItem(QString sName, int age)
{
int nRow = rowCount();
beginInsertRows(QModelIndex(), nRow, nRow);
info newInfo;
newInfo.sName = sName;
newInfo.nAge = age;
m_infoList.append(newInfo);
endInsertRows();
}
void MDataModel::removeItem(int nRow)
{
beginRemoveRows(QModelIndex(), nRow, nRow);
m_infoList.removeAt(nRow);
endRemoveRows();
}
이전 포스팅과 add, remove시에 결과는 같지만 qml 상에서 로직을 처리한 것을 c++로 QAbstractModelItem을 이용해서 처리하였습니다. 아래는 main.qml의 전체 코드입니다.
import QtQuick 2.15
import QtQuick.Window 2.15
import DataModel 1.0
Window {
property int listViewheight : ModelMgr.count * dataHeight;
property int dataHeight : 50;
property int inputUIHeight : 30;
property int columnSpacing : 10;
property int nCount : 6;
width: 300
height: listViewheight + inputUIHeight + columnSpacing;
visible: true
id:windows
Component {
id: nameDelegate
Item {
width : windows.width
height : dataHeight
Text {
anchors.fill: parent
text: "name : " + model.Data + "\nage : " + model.Age;
verticalAlignment: Text.AlignVCenter
anchors.leftMargin: 10
}
MouseArea {
anchors.fill: parent
onClicked: nameList.currentIndex = index
}
}
} // 데이터를 표현하는 Style
Component {
id:listHighLight
Rectangle{
color : "blue"
}
} // Highlight
Column {
spacing : columnSpacing
width : parent.width
height : parent.height
ListView {
id : nameList
width : parent.width
height : listViewheight
model : ModelMgr.getModel()
delegate: nameDelegate
highlight: listHighLight
} // 데이터를 보여주는 View
Row {
width : parent.width
height: inputUIHeight
Rectangle {
border.width: 1
width : parent.width/nCount
height : parent.height
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text : "name"
}
}
Rectangle {
width : parent.width/nCount
height : parent.height
border.color: "black"
border.width: 1
TextInput {
id:nameInput
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
onFocusChanged: {
focus? parent.color = "deepskyblue" : parent.color = "white"
}
}
}
Rectangle {
border.width: 1
width : parent.width/nCount
height : parent.height
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text : "age"
}
}
Rectangle {
width : parent.width/nCount
height : parent.height
border.color: "black"
border.width: 1
TextInput {
id:ageInput
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
onFocusChanged: {
focus? parent.color = "deepskyblue" : parent.color = "white"
}
}
}
Rectangle {
width : parent.width/nCount
height : parent.height
color: "gray"
border.width: 1
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text : "input"
}
MouseArea {
anchors.fill: parent
onClicked: {
ModelMgr.addItem(nameInput.text, parseInt(ageInput.text));
nameInput.text = ""
ageInput.text = ""
nameInput.focus = false
ageInput.focus = false
}
}
}
Rectangle {
width : parent.width/nCount
height : parent.height
color: "red"
border.width: 1
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text : "delete"
}
MouseArea {
anchors.fill: parent
onClicked: {
ModelMgr.removeItem(nameList.currentIndex)
}
}
}
}
}
}
'Programming > Qt' 카테고리의 다른 글
[Qt] QML 정리 포스팅 모음 (2) | 2023.04.20 |
---|---|
[Qt] QML(8) - SoundEffect, ComboBox ListModel 적용 (0) | 2023.04.20 |
[Qt] QML(6) - Loader, Binding, Connection (0) | 2023.04.18 |
[Qt] QML(5) - TimeTimer 프로그램 만들기 (0) | 2023.04.17 |
[Qt] QML(4) - QML/C++ 통합하기(Q_PROPERTY, Q_INVOKABLE, SIGNAL) (0) | 2023.04.13 |