Qt Quick Controls - Contact List¶
A QML app using Qt Quick Controls and a Python class that implements a simple contact list. This example can also be deployed to Android using pyside6-android-deploy
A PySide6 application that demonstrates the analogous example in Qt ContactsList
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
"""
PySide6 port of Qt Quick Controls Contact List example from Qt v6.x
"""
import sys
from pathlib import Path
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
from contactmodel import ContactModel # noqa: F401
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
app.setOrganizationName("QtProject")
app.setApplicationName("ContactsList")
engine = QQmlApplicationEngine()
engine.addImportPath(Path(__file__).parent)
engine.loadFromModule("Contact", "ContactList")
if not engine.rootObjects():
sys.exit(-1)
exit_code = app.exec()
del engine
sys.exit(exit_code)
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
import bisect
from dataclasses import dataclass
from enum import IntEnum
from PySide6.QtCore import (QAbstractListModel, QEnum, Qt, QModelIndex, Slot,
QByteArray)
from PySide6.QtQml import QmlElement
QML_IMPORT_NAME = "Backend"
QML_IMPORT_MAJOR_VERSION = 1
@QmlElement
class ContactModel(QAbstractListModel):
@QEnum
class ContactRole(IntEnum):
FullNameRole = Qt.ItemDataRole.DisplayRole
AddressRole = Qt.ItemDataRole.UserRole
CityRole = Qt.ItemDataRole.UserRole + 1
NumberRole = Qt.ItemDataRole.UserRole + 2
@dataclass
class Contact:
fullName: str
address: str
city: str
number: str
def __init__(self, parent=None) -> None:
super().__init__(parent)
self.m_contacts = []
self.m_contacts.append(self.Contact("Angel Hogan", "Chapel St. 368 ", "Clearwater",
"0311 1823993"))
self.m_contacts.append(self.Contact("Felicia Patton", "Annadale Lane 2", "Knoxville",
"0368 1244494"))
self.m_contacts.append(self.Contact("Grant Crawford", "Windsor Drive 34", "Riverdale",
"0351 7826892"))
self.m_contacts.append(self.Contact("Gretchen Little", "Sunset Drive 348", "Virginia Beach",
"0343 1234991"))
self.m_contacts.append(self.Contact("Geoffrey Richards", "University Lane 54", "Trussville",
"0423 2144944"))
self.m_contacts.append(self.Contact("Henrietta Chavez", "Via Volto San Luca 3",
"Piobesi Torinese", "0399 2826994"))
self.m_contacts.append(self.Contact("Harvey Chandler", "North Squaw Creek 11",
"Madisonville", "0343 1244492"))
self.m_contacts.append(self.Contact("Miguel Gomez", "Wild Rose Street 13", "Trussville",
"0343 9826996"))
self.m_contacts.append(self.Contact("Norma Rodriguez", " Glen Eagles Street 53",
"Buffalo", "0241 5826596"))
self.m_contacts.append(self.Contact("Shelia Ramirez", "East Miller Ave 68", "Pickerington",
"0346 4844556"))
self.m_contacts.append(self.Contact("Stephanie Moss", "Piazza Trieste e Trento 77",
"Roata Chiusani", "0363 0510490"))
def rowCount(self, parent=QModelIndex()):
return len(self.m_contacts)
def data(self, index: QModelIndex, role: int):
row = index.row()
if row < self.rowCount():
if role == ContactModel.ContactRole.FullNameRole:
return self.m_contacts[row].fullName
elif role == ContactModel.ContactRole.AddressRole:
return self.m_contacts[row].address
elif role == ContactModel.ContactRole.CityRole:
return self.m_contacts[row].city
elif role == ContactModel.ContactRole.NumberRole:
return self.m_contacts[row].number
def roleNames(self):
default = super().roleNames()
default[ContactModel.ContactRole.FullNameRole] = QByteArray(b"fullName")
default[ContactModel.ContactRole.AddressRole] = QByteArray(b"address")
default[ContactModel.ContactRole.CityRole] = QByteArray(b"city")
default[ContactModel.ContactRole.NumberRole] = QByteArray(b"number")
return default
@Slot(int)
def get(self, row: int):
contact = self.m_contacts[row]
return {"fullName": contact.fullName, "address": contact.address,
"city": contact.city, "number": contact.number}
@Slot(str, str, str, str)
def append(self, full_name: str, address: str, city: str, number: str):
contact = self.Contact(full_name, address, city, number)
contact_names = [contact.fullName for contact in self.m_contacts]
index = bisect.bisect(contact_names, contact.fullName)
self.beginInsertRows(QModelIndex(), index, index)
self.m_contacts.insert(index, contact)
self.endInsertRows()
@Slot(int, str, str, str, str)
def set(self, row: int, full_name: str, address: str, city: str, number: str):
if row < 0 or row >= len(self.m_contacts):
return
self.m_contacts[row] = self.Contact(full_name, address, city, number)
self.dataChanged(self.index(row, 0), self.index(row, 0),
[ContactModel.ContactRole.FullNameRole,
ContactModel.ContactRole.AddressRole,
ContactModel.ContactRole.CityRole,
ContactModel.ContactRole.NumberRole])
@Slot(int)
def remove(self, row):
if row < 0 or row >= len(self.m_contacts):
return
self.beginRemoveRows(QModelIndex(), row, row)
del self.m_contacts[row]
self.endRemoveRows()
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
Dialog {
id: dialog
signal finished(string fullName, string address, string city, string number)
function createContact() {
form.fullName.clear();
form.address.clear();
form.city.clear();
form.number.clear();
dialog.title = qsTr("Add Contact");
dialog.open();
}
function editContact(contact) {
form.fullName.text = contact.fullName;
form.address.text = contact.address;
form.city.text = contact.city;
form.number.text = contact.number;
dialog.title = qsTr("Edit Contact");
dialog.open();
}
x: parent.width / 2 - width / 2
y: parent.height / 2 - height / 2
focus: true
modal: true
title: qsTr("Add Contact")
standardButtons: Dialog.Ok | Dialog.Cancel
contentItem: ContactForm {
id: form
}
onAccepted: finished(form.fullName.text, form.address.text, form.city.text, form.number.text)
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
ItemDelegate {
id: delegate
checkable: true
contentItem: ColumnLayout {
spacing: 10
Label {
text: fullName
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
}
GridLayout {
id: grid
visible: false
columns: 2
rowSpacing: 10
columnSpacing: 10
Label {
text: qsTr("Address:")
Layout.leftMargin: 60
}
Label {
text: address
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
}
Label {
text: qsTr("City:")
Layout.leftMargin: 60
}
Label {
text: city
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
}
Label {
text: qsTr("Number:")
Layout.leftMargin: 60
}
Label {
text: number
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
}
}
}
states: [
State {
name: "expanded"
when: delegate.checked
PropertyChanges {
// TODO: When Qt Design Studio supports generalized grouped properties, change to:
// grid.visible: true
target: grid
visible: true
}
}
]
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
GridLayout {
id: grid
property alias fullName: fullName
property alias address: address
property alias city: city
property alias number: number
property int minimumInputSize: 120
property string placeholderText: qsTr("<enter>")
rows: 4
columns: 2
Label {
text: qsTr("Full Name")
Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
}
TextField {
id: fullName
focus: true
Layout.fillWidth: true
Layout.minimumWidth: grid.minimumInputSize
Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
placeholderText: grid.placeholderText
}
Label {
text: qsTr("Address")
Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
}
TextField {
id: address
Layout.fillWidth: true
Layout.minimumWidth: grid.minimumInputSize
Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
placeholderText: grid.placeholderText
}
Label {
text: qsTr("City")
Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
}
TextField {
id: city
Layout.fillWidth: true
Layout.minimumWidth: grid.minimumInputSize
Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
placeholderText: grid.placeholderText
}
Label {
text: qsTr("Number")
Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
}
TextField {
id: number
Layout.fillWidth: true
Layout.minimumWidth: grid.minimumInputSize
Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
placeholderText: grid.placeholderText
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
ApplicationWindow {
id: window
property int currentContact: -1
width: 320
height: 480
visible: true
title: qsTr("Contact List")
ContactDialog {
id: contactDialog
onFinished: function(fullName, address, city, number) {
if (currentContact == -1)
contactView.model.append(fullName, address, city, number)
else
contactView.model.set(currentContact, fullName, address, city, number)
}
}
Menu {
id: contactMenu
x: parent.width / 2 - width / 2
y: parent.height / 2 - height / 2
modal: true
Label {
padding: 10
font.bold: true
width: parent.width
horizontalAlignment: Qt.AlignHCenter
text: currentContact >= 0 ? contactView.model.get(currentContact).fullName : ""
}
MenuItem {
text: qsTr("Edit...")
onTriggered: contactDialog.editContact(contactView.model.get(currentContact))
}
MenuItem {
text: qsTr("Remove")
onTriggered: contactView.model.remove(currentContact)
}
}
ContactView {
id: contactView
anchors.fill: parent
onPressAndHold: {
currentContact = index
contactMenu.open()
}
}
RoundButton {
text: qsTr("+")
highlighted: true
anchors.margins: 10
anchors.right: parent.right
anchors.bottom: parent.bottom
onClicked: {
currentContact = -1
contactDialog.createContact()
}
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
import Backend
ListView {
id: listView
signal pressAndHold(int index)
width: 320
height: 480
focus: true
boundsBehavior: Flickable.StopAtBounds
section.property: "fullName"
section.criteria: ViewSection.FirstCharacter
section.delegate: SectionDelegate {
width: listView.width
}
delegate: ContactDelegate {
id: delegate
width: listView.width
onPressAndHold: listView.pressAndHold(index)
}
model: ContactModel {
id: contactModel
}
ScrollBar.vertical: ScrollBar { }
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
ToolBar {
id: background
Label {
id: label
text: section
anchors.fill: parent
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
}
}