w99hyun
프로필프로젝트포스트연락처
목록
w99hyun

본 사이트는 개인 포트폴리오 게시용으로 제작되었습니다.

ⓒ 2026. w99hyun(Woohyun Cho) All rights reserved.


QT - QML ListView (with. C++ Items)

원본 글 보기

2025년 12월 19일


QT QML의 ListView에서는 QML 내에서 자체적으로 생성한 모델을 출력할 수도 있지만, C++에서 생성한 List Model을 불러와 동적으로 생성하는 방법도 존재한다. 프로그램을 제작하다보면 보통 전자보다는 후자를 많이 사용하게 될 것이다.

Create List Item

C++에서 데이터 모델을 생성하고 QML에 표시하는 방법을 알아보겠다. 우선, QML의 ListView에서 출력할 C++ 데이터 모델이 필요하다. 이 모델은 ListView에 연동할 모델이므로, QAbstractListModel을 상속받아야 한다.

여기서는 예를들어 QML에서 timestamp, message 두 가지를 표시해보려고 한다.

struct ItemEntry
{
    QString timestamp;
    QString message;
};


class EntryModel : public QAbstractListModel
{
private:
    QList<ItemEntry> m_entries;
public:
    enum Roles {
        TimestampRole = Qt::UserRole + 1,
        MessageRole,
    };
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;

}

QAbstractListModel을 상속받은 class를 생성하고, m_entries에 데이터들이 저장된다. rowCount(), data()은 QAbstractListModel의 가상함수이므로 반드시 override해야하며, QML에서 사용하려면 enum과 함께 roleNames()도 override해주는 것이 좋다. 편의성의 이유이며, 자세한 내용은 아래에서 확인할 수 있다.

rowCount()

int EntryModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);// 함수 인자를 쓰지 않을 때 경고 무시를 위한 구문
    return m_entries.count();
}

m_entries의 개수를 반환해서 ListView에서 구현해야 할 Item 개수를 인식시켜주도록 한다.

data()

QVariant EntryModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || index.row() >= m_entries.size())
        return QVariant();

const ItemEntry& entry = m_entries.at(index.row());

switch (role) {
  case TimestampRole:
      return entry.timestamp;
  case MessageRole:
      return entry.message;
  default:
      return QVariant();
}

}

m_entries의 행에 맞는 데이터를 가져와서 role에 맞는 데이터를 반환한다.

roleNames()

QHash<int, QByteArray> EntryModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[TimestampRole] = "timestamp";
    roles[MessageRole] = "message";
    return roles;
}
  • QHash : 키-값 쌍을 저장하는 해시 테이블이다.
    enum Roles {
      TimestampRole = Qt::UserRole + 1,
      MessageRole,
    };
    Roles.TimestampRole → 256 Roles.MessageRole → 257 를 의미하므로, 역할 번호를 문자열 이름에 매핑하는 과정이다.
QQmlEngine* engine = m_qmlView->engine();
    if (engine) {
        engine->rootContext()->setContextProperty("itemModel", m_logModel);
    }

QML에서 itemModel 키워드로 C++ 객체에 접근할 수 있도록 등록한다.

ListView {
    id: ListView
    anchors.fill: parent
    model: typeof itemModel
    clip: true
    spacing: 0

delegate: Rectangle {
    width: parent.width
    height: 25

    Row {
        anchors.left: parent.left
        anchors.leftMargin: 12
        anchors.verticalCenter: parent.verticalCenter
        spacing: 8

        Text {
            text: "[" + (model.timestamp || "") + "]"
            color: "#D9D9D9"
            font.pixelSize: 11
        }

        Text {
            text: model.message || ""
            color: "#D9D9D9"
            font.pixelSize: 11
        }
    }
}

}

QML에서 각 아이템에 대한 Rectangle이다. ListView 내부에서 rowCount()를 호출해서 표시할 항목 수를 확인한 뒤, rowCount()의 반환값만큼 delegate 항목이 생성된다. m_entries 개수에 따라 delegate 항목의 개수가 결정되는 것이다.

QML 내부에서는 C++ 로직에서 등록을 해줬기 때문에, model.timestamp, model.message와 같이 해당 m_entries index에 맞는 값을 가져올 수 있다.

예를 들어 model.timestamp는 내부적으로 data(index, TimestampRole)를 호출한다. 그 후, roleNames()에서 매핑값을 확인해서 해당 role의 값을 반환한다.

⚠️ 만약, C++에서 roleNames()를 구현하지 않았다면 QML에서 model.timestamp로는 접근이 불가능하고 model[256]과 같은 방식으로만 접근이 가능하다. model을 QML에 등록할 때 QT가 내부적으로 roleNames()를 호출해서 매핑 테이블을 생성하는 것이다.


Update List Item

위에서는 C++ 모델을 QML에서 표시하는 방법을 확인했고, 만약 C++ 모델에서 새로운 아이템이 추가된다면 QML에서도 업데이트 될 수 있도록 업데이트 로직이 필요하다.

void EntryModel::addItem(const QString& message, const QString& level) {
    //1. ListView에 "데이터 추가 시작" 알림
    beginInsertRows(QModelIndex(), m_entries.size(), m_entries.size());

//2. 실제 데이터 추가
ItemEntry entry;
entry.timestamp = getCurrentTimestamp();
entry.message = message;
m_entries.append(entry);

//3. ListView에 "데이터 추가 완료" 알림
endInsertRows();

//4. countChanged 시그널 발생 (QML에서 count 속성 업데이트)
emit countChanged();

}

외부에서 아이템을 추가할 때 위의 함수를 호출하는 것이다. 여기서 핵심은 beginInsertRows()와 endInsertRows()이다. 아래에서 동작 순서를 확인할 수 있다.

beginInsertRows() 호출
  ↓
ListView가 새 항목이 추가되려고 한다는 것을 감지
  ↓
endInsertRows() 호출
  ↓
ListView가 rowCount() 다시 호출 → 아이템 증가 확인
  ↓
새로운 delegate 자동 생성 (새로운 아이템 index)
  ↓
새 delegate에서 model.timestamp, model.message 접근
  ↓
화면에 새 아이템 항목 표시

beginInsertRows()

bool beginInsertRows(const QModelIndex &parent, int first, int last)

이 함수의 인자에 대한 설명이다.

(QModelIndex) parent

  • 부모 인덱스를 의미한다.
  • 계층 구조가 없으면 QModelIndex()를 사용한다.

(int) first

  • 삽입할 첫 번째 행의 index이다. 0부터 시작한다.

(int) last

  • 삽입할 마지막 행의 index이다.
  • first <= last여야 하고, 마지막 하나만 추가한다면 first == last이다.

예시의 코드에서는 리스트 끝에 하나를 추가하는 방식이므로 first == last이다.


지금까지 다뤘던 프레임워크에 비해 ListView를 만드는 과정이 꽤나 복잡하고 어려움을 느낄 수 있었다. 다만 기본적으로 솔루션을 제공하는 것이니 익숙해진다면 어렵지 않게 만들 수 있을 것이다.

이전 글

Unity 렌더 파이프라인 관련 소식 (HDRP 업데이트 중단)

2026년 2월 28일

다음 글

QT - C++에서 선언한 Enum을 QML에서 불러오기

2025년 12월 15일