Last active
November 19, 2023 04:20
-
-
Save mitchcurtis/72c745f05b4292660168b1c0cd1ce4d9 to your computer and use it in GitHub Desktop.
Qt Quick ListView displaying a QAbstractListModel-derived model with custom roles
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cmake_minimum_required(VERSION 3.16) | |
project(model VERSION 0.1 LANGUAGES CXX) | |
set(CMAKE_CXX_STANDARD_REQUIRED ON) | |
find_package(Qt6 6.5 REQUIRED COMPONENTS Quick) | |
qt_standard_project_setup(REQUIRES 6.5) | |
qt_add_executable(appmodel | |
main.cpp | |
) | |
qt_add_qml_module(appmodel | |
URI App | |
VERSION 1.0 | |
QML_FILES | |
Main.qml | |
SOURCES | |
usermodel.h | |
usermodel.cpp | |
) | |
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. | |
# If you are developing for iOS or macOS you should consider setting an | |
# explicit, fixed bundle identifier manually though. | |
set_target_properties(appmodel PROPERTIES | |
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appmodel | |
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} | |
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} | |
MACOSX_BUNDLE TRUE | |
WIN32_EXECUTABLE TRUE | |
) | |
target_link_libraries(appmodel | |
PRIVATE Qt6::Quick | |
) | |
include(GNUInstallDirs) | |
install(TARGETS appmodel | |
BUNDLE DESTINATION . | |
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} | |
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <QGuiApplication> | |
#include <QQmlApplicationEngine> | |
int main(int argc, char *argv[]) | |
{ | |
QGuiApplication app(argc, argv); | |
QQmlApplicationEngine engine; | |
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, | |
&app, []() { QCoreApplication::exit(-1); }, | |
Qt::QueuedConnection); | |
engine.loadFromModule("App", "Main"); | |
return app.exec(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import QtQuick | |
import QtQuick.Controls.Material | |
import QtQuick.Layouts | |
import App | |
ApplicationWindow { | |
width: 640 | |
height: 480 | |
visible: true | |
Shortcut { | |
sequence: "Ctrl+Q" | |
onActivated: Qt.quit() | |
} | |
ColumnLayout { | |
anchors.fill: parent | |
ListView { | |
id: listView | |
currentIndex: 9 | |
clip: true | |
Layout.fillWidth: true | |
Layout.fillHeight: true | |
model: UserModel {} | |
delegate: Rectangle { | |
id: delegateItem | |
width: listView.width | |
implicitHeight: rowLayout.implicitHeight | |
required property string name | |
required property int age | |
RowLayout { | |
id: rowLayout | |
anchors.fill: parent | |
TextField { | |
text: delegateItem.name | |
onEditingFinished: delegateItem.name = text | |
Layout.fillWidth: true | |
} | |
SpinBox { | |
value: delegateItem.age | |
onValueModified: delegateItem.age = value | |
} | |
} | |
} | |
} | |
RowLayout { | |
Button { | |
text: qsTr("Append") | |
onClicked: listView.model.append() | |
} | |
GroupBox { | |
RowLayout { | |
anchors.fill: parent | |
SpinBox { | |
id: indexSpinBox | |
from: 0 | |
to: listView.count | |
} | |
Button { | |
text: qsTr("Insert") | |
onClicked: listView.model.insert(indexSpinBox.value) | |
} | |
} | |
} | |
Button { | |
text: qsTr("Remove first") | |
enabled: listView.count > 0 | |
onClicked: listView.model.removeFirst() | |
} | |
Button { | |
text: qsTr("Remove last") | |
enabled: listView.count > 0 | |
onClicked: listView.model.removeLast() | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "usermodel.h" | |
#include <QQmlInfo> | |
UserModel::UserModel() | |
{ | |
mUsers.append({ "Guy", 40 }); | |
mUsers.append({ "Girl", 40 }); | |
} | |
QHash<int, QByteArray> UserModel::roleNames() const | |
{ | |
return { | |
{ static_cast<int>(Roles::Name), "name" }, | |
{ static_cast<int>(Roles::Age), "age" } | |
}; | |
} | |
int UserModel::rowCount(const QModelIndex &) const | |
{ | |
return mUsers.size(); | |
} | |
QVariant UserModel::data(const QModelIndex &index, int role) const | |
{ | |
if (!checkIndex(index)) | |
return QVariant(); | |
const auto rowData = mUsers.at(index.row()); | |
switch (static_cast<Roles>(role)) { | |
case Roles::Name: | |
return rowData.name; | |
break; | |
case Roles::Age: | |
return rowData.age; | |
break; | |
} | |
return QVariant(); | |
} | |
bool UserModel::setData(const QModelIndex &index, const QVariant &value, int role) | |
{ | |
switch (static_cast<Roles>(role)) { | |
case Roles::Name: { | |
const QString newName = value.toString(); | |
mUsers[index.row()].name = newName; | |
emit dataChanged(index, index, { static_cast<int>(Roles::Name) }); | |
return true; | |
} | |
case Roles::Age: { | |
const int newAge = value.toInt(); | |
mUsers[index.row()].age = newAge; | |
emit dataChanged(index, index, { static_cast<int>(Roles::Age) }); | |
return true; | |
} | |
} | |
return false; | |
} | |
void UserModel::append() | |
{ | |
beginInsertRows({}, rowCount(), rowCount()); | |
mUsers.append({ "Untitled", 1 }); | |
endInsertRows(); | |
} | |
void UserModel::insert(int rowIndex) | |
{ | |
if (rowIndex < 0 || rowIndex > mUsers.size()) { | |
qmlWarning(this) << "Invalid index " << rowIndex; | |
return; | |
} | |
beginInsertRows({}, rowIndex, rowIndex); | |
mUsers.insert(rowIndex, { "Unknown", 1 }); | |
endInsertRows(); | |
} | |
void UserModel::removeFirst() | |
{ | |
if (mUsers.isEmpty()) { | |
qmlWarning(this) << "No rows to remove"; | |
return; | |
} | |
beginRemoveRows(QModelIndex(), 0, 0); | |
mUsers.takeFirst(); | |
endRemoveRows(); | |
} | |
void UserModel::removeLast() | |
{ | |
if (mUsers.isEmpty()) { | |
qmlWarning(this) << "No rows to remove"; | |
return; | |
} | |
beginRemoveRows(QModelIndex(), rowCount() - 1, rowCount() - 1); | |
mUsers.takeLast(); | |
endRemoveRows(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef CUSTOMMODEL_H | |
#define CUSTOMMODEL_H | |
#include <QAbstractListModel> | |
#include <QQmlEngine> | |
class UserModel : public QAbstractListModel | |
{ | |
Q_OBJECT | |
QML_ELEMENT | |
public: | |
UserModel(); | |
QHash<int, QByteArray> roleNames() const override; | |
int rowCount(const QModelIndex &parent = QModelIndex()) const override; | |
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; | |
bool setData(const QModelIndex &index, const QVariant &value, int role) override; | |
Q_INVOKABLE void append(); | |
Q_INVOKABLE void insert(int rowIndex); | |
Q_INVOKABLE void removeFirst(); | |
Q_INVOKABLE void removeLast(); | |
private: | |
enum class Roles { | |
Name = Qt::UserRole, | |
Age | |
}; | |
// This is defined here for convenience, but would usually be in a separate file | |
// as part of the app's domain-specific code. | |
struct User { | |
QString name; | |
int age; | |
}; | |
QList<User> mUsers; | |
}; | |
#endif // CUSTOMMODEL_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment