728x90

이전 포스팅에서 ListView를 QML로 보여주고 그 안에서 동적으로 관리하는 예제를 살펴봤습니다.

 

[Qt] QML(2) - State/Transition, ListView, Property

지난 포스팅에선 간단히 오브젝트와 컨테이너를 이용해서 배치하고 이벤트 처리를 하는 예제를 해봤습니다. [Qt] QML UI 구성하기(1) 지난 포스팅에서 Qt Quick Application 프로젝트를 생성하는 부분까

1d1cblog.tistory.com

이번에는 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)
                    }
                }
            }
        }
    }
}
728x90

+ Recent posts