Utilisation de fichiers et de signaux séparés

Séparer le code difficile à manier dans différents fichiers et attacher des signaux à vos composants.

Mais pourquoi ?

Pour la première fois, nous allons séparer certains de nos composants dans leurs propres fichiers QML. Si nous continuons à ajouter des choses au fichier « main.qml », il va rapidement devenir difficile de dire ce qui fait quoi et nous risquons de rendre notre code illisible.

D’abord, nous devons ajouter nos nouveaux fichiers dans notre fichier « resources.qrc » que nous avons créé dans la première partie de ce tutoriel.

<RCC>
	<qresource prefix="/">
		<file alias="main.qml">contents/ui/main.qml</file>
		<file alias="AddEditSheet.qml">contents/ui/AddEditSheet.qml</file>
		<file alias="KountdownDelegate.qml">contents/ui/KountdownDelegate.qml</file>
	</qresource>
</RCC>

Utilisation de nos nouveaux fichiers

Nous devons trouver certains moyens pour utiliser nos nouveaux fichiers dans « main.qml ». Heureusement, tout ce que nous avons à faire est d’inclure une déclaration de ces composants dans notre fichier « main.qml » comme ceci :

AddEditSheet {
	id: addEditSheet
}

Extension de notre feuille d’ajout en une feuille d’ajout / modification

Alors que dans le dernier tutoriel, nous avons fait en sorte que notre bouton d’ajout de compte à rebours fasse quelque chose, le bouton d’édition de nos cartes de compte à rebours est toujours inactif. Nous avons également créé une feuille d’ajout que nous pouvons maintenant utiliser aussi comme feuille d’édition… Mais avant cela, nous devons ajouter quelques éléments supplémentaires à notre fichier « main.qml ».

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import QtQuick 2.6
import QtQuick.Controls 2.0 as Controls
import QtQuick.Layouts 1.2
import org.kde.kirigami 2.13 as Kirigami

// Base element, provides basic features needed for all kirigami applications
Kirigami.ApplicationWindow {
	// ID provides unique identifier to reference this element
	id: root
	
	// Window title
	title: i18nc("@title:window", "Day Kountdown")

	// Global drawer element with app-wide actions
	globalDrawer: Kirigami.GlobalDrawer {
		// Makes drawer a small menu rather than sliding pane
		isMenu: true
		actions: [
			Kirigami.Action {
				text: i18n("Quit")
				icon.name: "gtk-quit"
				shortcut: StandardKey.Quit
				onTriggered: Qt.quit()
			}
		]
	}
	
	// ListModel needed for ListView, contains elements to be displayed
	ListModel {
		id: kountdownModel
	}
	
	// Fetches item from addEditSheet.qml and does action on signal
	AddEditSheet { 
		id: addEditSheet
		onAdded: kountdownModel.append({
			"name": name, 
			"description": description, 
			"date": Date.parse(kdate)
		});
		onEdited: kountdownModel.set(index, {
			"name": name, 
			"description": description, 
			"date": Date.parse(kdate) 
		});
		onRemoved: kountdownModel.remove(index, 1)
	}

	// Function called by 'edit' button on card
	function openPopulateSheet(mode, index = -1, listName = "", listDesc = "", listDate = "") {
		addEditSheet.mode = mode
		if(mode === "edit") {
			addEditSheet.index = index;
			addEditSheet.name = listName
			addEditSheet.description = listDesc
			addEditSheet.kdate = listDate
		}
		addEditSheet.open()
	}


	// Initial page to be loaded on app load
	pageStack.initialPage: Kirigami.ScrollablePage {
		// Title for the current page, placed on the toolbar
		title: i18nc("@title", "Kountdown")

		// Kirigami.Action encapsulates a UI action. Inherits from QQC2 Action
		actions.main: Kirigami.Action {
			id: addAction
			// Name of icon associated with the action
			icon.name: "list-add"
			// Action text, i18n function returns translated string
			text: i18nc("@action:button", "Add kountdown")
			// What to do when triggering the action
			onTriggered: openPopulateSheet("add")
		}
		
		// List view for card elements
		Kirigami.CardsListView {
			id: layout
			// Model contains info to be displayed
			model: kountdownModel
			// Delegate is how the information will be presented in the ListView
			delegate: KountdownDelegate {}
		}
	}
}

Les principaux changements que nous avons effectués concernent l’ajout de la définition de notre composant « AddEditSheet » (et « KountdownDelegate » plus bas) et d’une nouvelle fonction appelée « openPopulateSheet ».

Passons en revue notre définition de « AddEditSheet » :

AddEditSheet { 
	id: addEditSheet
	onEdited: kountdownModel.set(index, {
		name, 
		description, 
		date, 
	});
	onAdded: kountdownModel.append({
		name, 
		description, 
		date, 
	});
}

Les objets « onAdded » et « onEdited » sont des gestionnaires de signaux. De la même manière que l’objet « onTriggered » est appelé lorsque nous cliquons sur une action. Nous pouvons également utiliser des gestionnaires répondant à nos signaux personnalisés. En regardant notre nouvelle feuille « AddEditSheet.qml » – notre feuille d’ajout – nous pouvons voir comment ces signaux fonctionnent :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import QtQuick 2.6
import QtQuick.Controls 2.3 as Controls
import QtQuick.Layouts 1.2
import org.kde.kirigami 2.13 as Kirigami

// Overlay sheets appear over a part of the window
Kirigami.OverlaySheet {
	id: addEditSheet
	
	// Sheet mode
	property string mode: "add"
	
	property int index: -1
	property string name: ""
	property string description: ""
	property string kdate: ""
	
	// Signals can be read and certain actions performed when these happen
	signal added (string name, string description, var kdate)
	signal edited(int index, string name, string description, var kdate)
	signal removed(int index)
	
	header: Kirigami.Heading {
		// i18nc is useful for adding context for translators
		text: mode === "add" ? i18nc("@title:window", "Add kountdown") : 
			i18nc("@title:window", "Edit kountdown")
	}
	// Form layouts help align and structure a layout with several inputs
	Kirigami.FormLayout {
		// Textfields let you input text in a thin textbox
		Controls.TextField {
			id: nameField
			// Provides label attached to the textfield
			Kirigami.FormData.label: i18nc("@label:textbox", "Name:")
			// Placeholder text is visible before you enter anything
			placeholderText: i18n("Event name (required)")
			// What to do after input is accepted (i.e. pressed enter)
			// In this case, it moves the focus to the next field
			text: mode === "add" ? "" : name
			onAccepted: descriptionField.forceActiveFocus()
		}
		Controls.TextField {
			id: descriptionField
			Kirigami.FormData.label: i18nc("@label:textbox", "Description:")
			placeholderText: i18n("Optional")
			text: mode === "add" ? "" : description
			onAccepted: dateField.forceActiveFocus()
		}
		Controls.TextField {
			id: dateField
			Kirigami.FormData.label: i18nc("@label:textbox", "Date:")
			inputMask: "0000-00-00"
			placeholderText: i18n("YYYY-MM-DD")
			text: mode === "add" ? "" : kdate
		}
		// This is a button.
		Controls.Button {
			id: deleteButton
			Layout.fillWidth: true
			text: i18nc("@action:button", "Delete")
			visible: mode === "edit"
			onClicked: {
				addEditSheet.removed(addEditSheet.index)
				close();
			}
		}
		Controls.Button {
			id: doneButton
			Layout.fillWidth: true
			text: i18nc("@action:button", "Done")
			// Button is only enabled if the user has entered something into the nameField
			enabled: nameField.text.length > 0
			onClicked: {
				// Add a listelement to the kountdownModel ListModel
				if(mode === "add") {
					addEditSheet.added(
						nameField.text, 
						descriptionField.text, 
						dateField.text
					);
				}
				else {
					addEditSheet.edited(
						index, 
						nameField.text, 
						descriptionField.text, 
						dateField.text 
					);
				}
				close();
			}
		}
	}
}

Les signaux invoquent leurs gestionnaires lorsqu’ils sont appelés. Dans ce cas, nous avons créé deux signaux que nous pouvons invoquer avec des résultats différents, et auxquels nous pouvons attacher des informations sur le compte à rebours ajouté ou créé. Une des particularités des signaux est qu’ils exposent les variables qui y sont définies aux fonctions qui les écoutent. C’est pourquoi nous pouvons simplement appeler ces noms de variables dans nos gestionnaires « onEdited » et « onAdded » dans « main.qml ». Nos signaux sont invoqués par le bouton « Done » en fonction de la valeur de la propriété « mode », définie en haut de notre « AddEditSheet ».

La propriété « mode  » contrôle également plusieurs autres choses : principalement le titre de notre feuille et le texte devant être inclus dans nos champs de texte. Cependant, par défaut, notre propriété « mode » est juste définie pour ajouter…

Ce qui nous ramène à « main.qml » et à notre nouvelle fonction « openPopulateSheet ». Vous avez peut-être remarqué que c’est ce qui est appelé maintenant quand l’action de décompte et d’ajout est déclenchée. Cette fonction prend en compte plusieurs arguments fournis avec des valeurs par défaut. Ceci est utile lorsque nous voulons simplement ajouter un nouveau compteur à rebours, parce que nous pouvons utiliser l’appel à la fonction concise « openPopulateSheet(« add ») ». Plus important encore, cette fonction définit toutes propriétés pertinentes dans « AddEditSheet ».

function openPopulateSheet(mode, index = -1, listName = "", listDesc = "", listDate = "") {
	addEditSheet.mode = mode
	if(mode == "edit") {
		addEditSheet.index = index;
		addEditSheet.name = listName
		addEditSheet.description = listDesc
		addEditSheet.date = listDate
	}
	addEditSheet.open()
}
  • Le paramètre « mode » modifie la feuille d’ajout / modification selon que cet argument est défini comme « add » ou « edit ».
  • L’argument « index » est nécessaire pour que lorsque nous enregistrions notre compte à rebours modifié, le bon compte à rebours est modifié.
  • Les objets « listName », « listDesc » et « listDate » sont les détails pertinents du compte à rebours, devant être placés dans les champs de la feuille.

Bien sûr, pour utiliser actuellement notre feuille à d’autres fins que l’ajout de comptes à rebours, nous devons d’abord faire fonctionner le bouton de modification de nos cartes. Mais si vous regardez notre objet « Kirigami.CardsListView » dans « main.qml »…

Kirigami.CardsListView {
	id: layout
	model: kountdownModel
	delegate: KountdownDelegate {}
}

Nous avons remplacé notre objet « Kirigami.AbstractCard » par une définition de composant délégué à partir de « KountdownDelegate.qml ».

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import QtQuick 2.6
import QtQuick.Controls 2.0 as Controls
import QtQuick.Layouts 1.2
import org.kde.kirigami 2.13 as Kirigami

Kirigami.AbstractCard {
	id: kountdownDelegate
	// contentItem property includes the content to be displayed on the card
	contentItem: Item {
    	// implicitWidth/Height define the natural width/height of an item if no width or height is specified
		// The setting below defines a component's preferred size based on its content
		implicitWidth: delegateLayout.implicitWidth
		implicitHeight: delegateLayout.implicitHeight
		GridLayout {
			id: delegateLayout
			// QtQuick anchoring system allows quick definition of anchor points for positioning
			anchors {
				left: parent.left
				top: parent.top
				right: parent.right
			}
			rowSpacing: Kirigami.Units.largeSpacing
			columnSpacing: Kirigami.Units.largeSpacing
			columns: root.wideScreen ? 4 : 2
				
			Kirigami.Heading {
				// Heading will be as tall as possible while respecting constraints
				Layout.fillHeight: true
				// Level determines the size of the heading
				level: 1
				text: i18n("%1 days", Math.round((date-Date.now())/86400000))
			}
			
			// Layout for positioning elements vertically
			ColumnLayout {
				Kirigami.Heading {
					Layout.fillWidth: true
					level: 2
					text: name
				}
				// Horizontal rule
				Kirigami.Separator {
					Layout.fillWidth: true
					visible: description.length > 0
				}
				// Labels contain text
				Controls.Label {
					Layout.fillWidth: true
					// Word wrap makes text stay within box and shift with size
					wrapMode: Text.WordWrap
					text: description
					visible: description.length > 0
				}
			}
			Controls.Button {
				Layout.alignment: Qt.AlignRight
				// Column spanning within grid layout (vertically in this case)
				Layout.columnSpan: 2
				text: i18n("Edit")
				onClicked: openPopulateSheet("edit", index, name, description, date = new Date(date).toISOString().slice(0,10))
			}
		}
	}
}

La propriété « onClicked » du bouton « Edit » de nos cartes appelle maintenant la fonction « openPopulateSheet »avec les propriétés des éléments trouvés dans la liste pour les cartes, utilisées comme arguments de cette fonction. Avec celles-ci, la feuille peut être remplie avec le texte correct.

Avec cela, nous avons une feuille entièrement fonctionnelle où nous pouvons ajouter et modifier nos comptes à rebours !

 !