Skip to content

Instantly share code, notes, and snippets.

@mitchcurtis
Last active November 19, 2023 04:20
Show Gist options
  • Save mitchcurtis/72c745f05b4292660168b1c0cd1ce4d9 to your computer and use it in GitHub Desktop.
Save mitchcurtis/72c745f05b4292660168b1c0cd1ce4d9 to your computer and use it in GitHub Desktop.
Qt Quick ListView displaying a QAbstractListModel-derived model with custom roles
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}
)
#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();
}
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()
}
}
}
}
#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();
}
#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