안드로이드 스튜디오 프로젝트에서 QtAbstractItemModel 사용하기

Qt Quick 안드로이드용 API 예제는 안드로이드 스튜디오 프로젝트로 제공됩니다. 프로젝트 폴더는 Qt 설치 위치에서 찾을 수 있습니다.

예를 들어 기본 Windows 설치 경로 아래에서 찾을 수 있습니다:

C:\Qt\Examples\Qt-/1\platforms\android

개요

이 예제는 두 개의 프로젝트, 즉 안드로이드 스튜디오 프로젝트(qtabstractitemmodel_java)와 QML 프로젝트(qtabstractitemmodel)로 구성되어 있습니다. QML 프로젝트를 Android 프로젝트로 가져올 수 있습니다.

이 예제는 Java와 QML 간에 복잡한 데이터 유형을 처리하는 방법을 보여줍니다. QtAbstractItemModelQtModelIndex Java API 클래스를 사용하는 방법을 보여줍니다. QML에서는 TableView 항목으로 데이터 사용법을 보여줍니다. Java에서는 행과 열에 대한 중첩된 ArrayList 항목의 모델을 사용하여 데이터 사용법을 보여줍니다. QML 작동 방식에 대한 자세한 내용은 다음을 참조하세요. Qt Qml.

예제 실행하기

이 예제를 실행하려면 안드로이드용 표준 Qt 설치 위에 안드로이드 스튜디오와 안드로이드 스튜디오용 Qt 도구가 필요합니다. 안드로이드 스튜디오에서 qtabstractitemmodel_java를 열고 안드로이드 스튜디오용 Qt 도구의 지침에 따라 qtabstractitemmodel.

QML 프로젝트

QML 프로젝트 측면에서 이 예제에서는 Rectangle 을 루트 객체로 사용합니다. dataModel 속성 변수는 Java 측에서 생성 및 전달된 데이터 모델을 보유합니다.

Rectangle {
    id: mainRectangle

    property AbstractItemModel dataModel

TableView 는 데이터 모델을 표시합니다.

    TableView {
        id: tableView

        model: mainRectangle.dataModel

        anchors {fill: parent; margins: 20}
        columnSpacing: 4
        rowSpacing: 6
        boundsBehavior: TableView.OvershootBounds
        clip: true

        ScrollBar.vertical: ScrollBar {
           policy: ScrollBar.AsNeeded
        }
        ScrollBar.horizontal: ScrollBar{
           policy: ScrollBar.AsNeeded
        }

delegate 속성에서 모델의 각 셀 항목은 TextEdit 을 포함하는 Rectangle 으로 정의됩니다. TextEdit 의 텍스트 속성은 주어진 역할 및 인덱스에 따라 값을 반환하는 QAbstractItemModel::data()를 사용하여 설정됩니다.

QML에서 이러한 메서드를 호출한다는 것은 실행이 Qt qtMainLoopThread 스레드 컨텍스트에서 이루어짐을 의미합니다.

        delegate: Rectangle {
            implicitWidth: (tableView.height > tableView.width) ? tableView.width / 10 : tableView.height / 5
            implicitHeight: implicitWidth

            required property var model

            color: "#2CDE85"
            border {color: "#00414A"; width: 2}

            TextEdit {
                // Calls MyDataModel::data to get data based on the roles.
                // Called in Qt qtMainLoopThread thread context.
                //
                // After editing is finished, call MyDataModel::setData()
                // to update the value of selected cell.
                onEditingFinished: parent.model.edit = text

                text: parent.model.display
                font {pixelSize: 26; bold: true}
                padding: 5
                anchors.fill: parent
                wrapMode: TextEdit.Wrap
                horizontalAlignment: TextEdit.AlignHCenter
                verticalAlignment: TextEdit.AlignVCenter
            }
        }

TextEdit 필드를 편집하는 경우 onEditingFinished() 핸들러는 모델의 edit 역할 값을 편집된 텍스트로 설정합니다. 그러면 QAbstractItemModel::setData() 메서드가 호출되어 셀의 편집된 텍스트가 모델의 해당 인덱스로 업데이트됩니다.

자세한 내용은 QAbstractItemModel 을 참조하세요.

안드로이드 스튜디오 프로젝트

Android Studio 프로젝트(qtabstractitemmodel_java)에는 하나의 활동 클래스 MainActivityMyDataModel 클래스가 포함되어 있습니다.

데이터 모델

데이터 모델인 MyDataModelQtAbstractItemModel 클래스를 확장합니다. QtAbstractItemModelQAbstractItemModel 의 래퍼입니다.

MyDataModel 클래스의 메서드는 QML과 안드로이드 양쪽에서 호출되므로 실행은 스레드 컨텍스트인 Qt qtMainLoopThread와 안드로이드 메인 스레드 컨텍스트에서 모두 발생합니다. MyDataModel 클래스의 메서드에서 멤버 변수에 접근할 때는 동기화를 보장해야 합니다.

먼저 이 예제에서는 간단한 행과 열 모의 데이터 세트로 모델을 초기화합니다. 이 생성자 메서드는 안드로이드 메인 스레드 컨텍스트에서 호출된다는 점에 유의하세요.

/*
* Initializes the two-dimensional array list with following content:
* [] [] [] [] 1A 1B 1C 1D
* [] [] [] [] 2A 2B 2C 2D
* [] [] [] [] 3A 3B 3C 3D
* [] [] [] [] 4A 4B 4C 4D
* Threading: called in Android main thread context.
*/
public MyDataModel() {

이 예제에서는 다른 용도로 QtAbstractItemModel 메서드를 재정의합니다. columnCount() 및 rowCount() 메서드는 모델에서 각각의 개수를 반환합니다. 각 rowCount()의 실행은 스레드 컨텍스트, Qt qtMainLoopThread와 안드로이드 메인 스레드 컨텍스트 모두에서 발생합니다.

/*
* Returns the count of columns.
* Threading: called in Android main thread context.
* Threading: called in Qt qtMainLoopThread thread context.
*/
@Override
synchronized public int columnCount(QtModelIndex qtModelIndex) {
    return m_columns;
}

/*
* Returns the count of rows.
* Threading: called in Android main thread context.
* Threading: called in Qt qtMainLoopThread thread context.
*/
@Override
synchronized public int rowCount(QtModelIndex qtModelIndex) {
    return m_dataList.size();
}

data() 메서드는 역할과 인덱스에 기반한 모델 데이터를 Java에서 QML로 제공합니다. roleNames() 메서드는 숫자 역할 값과 해당 이름에 일치하는 해시를 문자열로 반환하며, QML에서는 이 역할 이름을 사용하여 모델에서 해당 데이터를 가져옵니다. index() 메서드는 새 모델 인덱스를 반환합니다. parent() 메서드는 인덱스의 부모를 반환해야 합니다. 하지만 이 예제에서는 부모 인덱스가 없는 데이터에 초점을 맞추기 때문에 메서드를 재정의하고 빈 QtModelIndex()를 반환합니다. 이 메서드는 QML에서 호출되므로 실행은 Qt qtMainLoopThread 스레드 컨텍스트에서 이루어집니다.

/*
* Returns the data to QML based on the roleNames
* Threading: called in Qt qtMainLoopThread thread context.
*/
@Override
synchronized public Object data(QtModelIndex qtModelIndex, int role) {
    if (role == ROLE_DISPLAY) {
        Cell elementForEdit = m_dataList.get(qtModelIndex.row()).get(qtModelIndex.column());
        return elementForEdit.getValue();
    }
    Log.w(TAG, "data(): unrecognized role: " + role);
    return null;
}

/*
* Defines what string i.e. role in QML side gets the data from Java side.
* Threading: called in Qt qtMainLoopThread thread context.
*/
@Override
synchronized public HashMap<Integer, String> roleNames() {
    HashMap<Integer, String> roles = new HashMap<>();
    roles.put(ROLE_DISPLAY, "display");
    roles.put(ROLE_EDIT, "edit");
    return roles;
}

/*
* Returns a new index model.
* Threading: called in Qt qtMainLoopThread thread context.
*/
@Override
synchronized public QtModelIndex index(int row, int column, QtModelIndex parent) {
    return createIndex(row, column, 0);
}

/*
* Returns a parent model.
* Threading: not used called in this example.
*/
@Override
synchronized public QtModelIndex parent(QtModelIndex qtModelIndex) {
    return new QtModelIndex();
}

이 예제에서는 애플리케이션의 QML 측에서 index 에서 모델의 데이터가 설정될 때 호출되는 QAbstractItemModel::setData() 메서드를 재정의합니다.

/*
* Gets called when model data is edited from QML side.
* Sets the role data for the item at index to value,
* if given index is valid and if data in given index truly changed.
*/
@Override
synchronized public boolean setData(QtModelIndex index, Object value, int role) {
    Cell cellAtIndex = m_dataList.get(index.row()).get(index.column());
    String cellValueAtIndex = cellAtIndex.getValue();
    if (!index.isValid() || role != ROLE_EDIT
            || Objects.equals(cellValueAtIndex, value.toString())) {
        return false;
    }
    cellAtIndex.setValue(value.toString());
    // Send dataChanged() when data was successfully set.
    dataChanged(index, index, new int[]{role});
    return true;
}

이 예제에서는 모델 측에서 행과 열을 추가하고 제거하는 MainActivity UI 상호 작용을 위한 메서드를 구현합니다. 시작, 끝, 삽입, 행 제거 메서드를 호출하여 모델 인덱스를 업데이트하는 시작InsertRow()와 같은 메서드를 구현합니다. 이 예제에서는 QtAbstractItemModel 을 사용하므로 모델에 새 행을 삽입할 때마다 beginInsertRows() 및 endInsertRows()를 호출해야 합니다. 제거할 때도 마찬가지입니다. 메서드는 안드로이드 측에서 호출되므로 실행은 안드로이드 메인 스레드 컨텍스트에서 이루어집니다.

/*
* Adds a row.
* Threading: called in Android main thread context.
*/
synchronized public void addRow() {
    if (m_columns > 0 && m_dataList.size() < MAX_ROWS_AND_COLUMNS) {
        beginInsertRows(new QtModelIndex(), m_dataList.size(), m_dataList.size());
        m_dataList.add(generateNewRow());
        endInsertRows();
    }
}

/*
* Removes a row.
* Threading: called in Android main thread context.
*/
synchronized public void removeRow() {
    if (m_dataList.size() > 1) {
        beginRemoveRows(new QtModelIndex(), m_dataList.size() - 1, m_dataList.size() - 1);
        m_dataList.remove(m_dataList.size() - 1);
        endRemoveRows();
    }
}

이 예제에서는 모델 측에서 열을 추가하고 제거하는 MainActivity UI 상호 작용을 위한 메서드를 구현합니다. 시작, 끝, 삽입 및 제거 열을 호출하여 beginRemoveColumn()과 같이 모델 인덱스를 업데이트합니다. 행 추가 및 제거 메서드와 동일한 컨텍스트 인식이 적용됩니다.

/*
* Adds a column.
* Threading: called in Android main thread context.
*/
synchronized public void addColumn() {
    if (!m_dataList.isEmpty() && m_columns < MAX_ROWS_AND_COLUMNS) {
        beginInsertColumns(new QtModelIndex(), m_columns, m_columns);
        generateNewColumn();
        m_columns += 1;
        endInsertColumns();
    }
}

/*
* Removes a column.
* Threading: called in Android main thread context.
*/
synchronized public void removeColumn() {
    if (m_columns > 1) {
        int columnToRemove = m_columns - 1;
        beginRemoveColumns(new QtModelIndex(), columnToRemove, columnToRemove);
        for (int row = 0; row < m_dataList.size(); row++)
            m_dataList.get(row).remove(columnToRemove);
        m_columns -= 1;
        endRemoveColumns();
    }
}

주요 활동

MainActivityQtQmlStatusChangeListener 인터페이스를 구현하여 QML이 로드될 때 상태 업데이트를 가져옵니다. 또한 메인 안드로이드 활동이기도 합니다.

이 예제에서는 데이터 모델을 생성하고 초기화합니다. QtQuickView도 참조하십시오.

private final MyDataModel m_model = new MyDataModel();

이 예제에서는 사용자가 UI를 통해 모델과 상호 작용할 수 있도록 UI 버튼과 해당 리스너를 설정합니다.

/*
* Returns the count of columns.
* Threading: called in Android main thread context.
* Threading: called in Qt qtMainLoopThread thread context.
*/
@Override
synchronized public int columnCount(QtModelIndex qtModelIndex) {
    return m_columns;
}

/*
* Returns the count of rows.
* Threading: called in Android main thread context.
* Threading: called in Qt qtMainLoopThread thread context.
*/
@Override
synchronized public int rowCount(QtModelIndex qtModelIndex) {
    return m_dataList.size();
}

이 예제는 QML 콘텐츠 로드를 시작합니다. 로드는 ready 상태가 업데이트될 때까지 백그라운드에서 이루어집니다.

/*
* Returns the data to QML based on the roleNames
* Threading: called in Qt qtMainLoopThread thread context.
*/
@Override
synchronized public Object data(QtModelIndex qtModelIndex, int role) {
    if (role == ROLE_DISPLAY) {
        Cell elementForEdit = m_dataList.get(qtModelIndex.row()).get(qtModelIndex.column());
        return elementForEdit.getValue();
    }
    Log.w(TAG, "data(): unrecognized role: " + role);
    return null;
}

/*
* Defines what string i.e. role in QML side gets the data from Java side.
* Threading: called in Qt qtMainLoopThread thread context.
*/
@Override
synchronized public HashMap<Integer, String> roleNames() {
    HashMap<Integer, String> roles = new HashMap<>();
    roles.put(ROLE_DISPLAY, "display");
    roles.put(ROLE_EDIT, "edit");
    return roles;
}

/*
* Returns a new index model.
* Threading: called in Qt qtMainLoopThread thread context.
*/
@Override
synchronized public QtModelIndex index(int row, int column, QtModelIndex parent) {
    return createIndex(row, column, 0);
}

/*
* Returns a parent model.
* Threading: not used called in this example.
*/
@Override
synchronized public QtModelIndex parent(QtModelIndex qtModelIndex) {
    return new QtModelIndex();
}

이 예제에서는 QML 콘텐츠가 로드되고 상태가 준비되면 데이터 모델을 설정합니다.

/*
* Gets called when model data is edited from QML side.
* Sets the role data for the item at index to value,
* if given index is valid and if data in given index truly changed.
*/
@Override
synchronized public boolean setData(QtModelIndex index, Object value, int role) {
    Cell cellAtIndex = m_dataList.get(index.row()).get(index.column());
    String cellValueAtIndex = cellAtIndex.getValue();
    if (!index.isValid() || role != ROLE_EDIT
            || Objects.equals(cellValueAtIndex, value.toString())) {
        return false;
    }
    cellAtIndex.setValue(value.toString());
    // Send dataChanged() when data was successfully set.
    dataChanged(index, index, new int[]{role});
    return true;
}

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.