Aparte bestanden en signalen gebruiken

Onpraktische code in verschillende bestanden scheiden en signalen aan uw componenten hangen.

Maar waarom?

Voor de eerste keer, zullen we enige van onze componenten in hun eigen QML bestanden scheiden. Als we dingen aan main.qml blijven toevoegen, wordt het snel moeilijk te vertellen wat wat doet en riskeren we modderige code.

Eerst moeten we onze nieuwe bestanden in onze resources.qrc toevoegen die we hebben aangemaakt in het eerste deel van deze inleiding.

<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>

Onze nieuwe bestanden gebruiken

We zullen een manier moeten vinden o onze nieuwe bestanden in main.qml te gebruiken. Gelukkig is alles wat we te doen hebben ze met een include declaratie van deze componenten in onze main.qml in te voegen, zoals dit:

AddEditSheet {
	id: addEditSheet
}

Ons vel met toevoegen uitbreiden naar een toevoegen/bewerken vel

In de laatste inleiding maakten we onze aftelling-toevoegenknop om iets te doen, de bewerkingsknop op onze aftelkaarten is nog steeds inactief. We maakten ook een toevoegvel die we nu een ander doel geven om ook te dienen als een bewerkingsvel… maar voordat we daar zijn, moeten we een aantal extra dingen aan onze main.qml toevoegen.

 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 {}
		}
	}
}

De belangrijkste wijzigingen die we hebben gemaakt omvatten het toevoegen van onze componentdefinitie AddEditSheet (en KountdownDelegate verder omlaag) en een nieuwe functie genaamd openPopulateSheet.

Laten we door onze definitie AddEditSheet lopen:

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

onAdded en onEdited zijn behandelaars van signalen. Net als onTriggered wordt aangeroepen wanneer we op een actie klikken, kunnen we ook behandelaars gebruiken die reageren op onze aangepaste signalen. Kijkend naar onze nieuwe AddEditSheet.qml – ons toevoegvel met een nieuw doel – kunnen we zien hoe deze signalen werken:

 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();
			}
		}
	}
}

Signalen roepen hun behandelaars aan wanneer ze worden aangeroepen. In dit geval hebben we twee signalen gemaakt die we aan kunnen roepen met verschillend resultaat en waaraan we informatie kunnen hangen over de aftelling die we toevoegen of aanmaken. Een net ding over signalen is dat ze de erin gedefinieerde variabelen blootstellen aan de functies die er naar luisteren, wat is waarom we deze variabelenamen in onze onEdited en onAdded behandelaars in main.qml kunne aanroepen. Onze signalen worden aangeroepen door de knop ‘Gereed’ afhankelijk van op wat de eigenschap mode, gedefinieerd bovenaan onze AddEditSheet, is gezet.

De eigenschap mode bestuurt ook verschillende andere dingen: hoofdzakelijk wat in de titel van ons vel is gezet en welke tekst ingevoegd moet worden in onze tekstvelden. Standaard is echter onze eigenschap mode gewoon ingesteld op toevoegen…

Wat ons terugbrengt bij main.qml en onze nieuwe functie openPopulateSheet. U zult gemerkt hebben dat dit is wat nu aangeroepen wordt wanneer de actie aftelling-toevoegen wordt geactiveerd. Deze functie neemt verschillende argumenten die voorzien van standaarden. Dit helpt wanneer we eenvoudig een nieuwe aftelling willen toevoegen, omdat we de beknopte functie-aanroep openPopulateSheet("add") kunnen gebruiken. Belangrijker, deze functie zet alle relevante eigenschappen in 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()
}
  • mode wijzigt het toevoegen/bewerken vel afhankelijk van of dit argument is gezet op "toevoegen" of op "bewerken"
  • Het index argument is nodig zodat wanneer we onze bewerkte aftelling opslaan, de juiste wordt gewijzigd
  • listName, listDesc en listDate zijn de relevante aftellingdetails die nodig zijn om in de velden van het vel te stoppen

Natuurlijk, om actueel ons vel voor iets naast aftellingen te gebruiken moeten we eerst de bewerkingsknop op onze kaarten laten werken. Maar als u kijkt op onze Kirigami.CardsListView in main.qml

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

We hebben onze Kirigami.AbstractCard vervangen door een gedelegeerde componentdefinitie uit 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))
			}
		}
	}
}

De eigenschap onClicked van de knop ‘Bewerken’ op onze kaarten roept nu de functie openPopulateSheet aan, met de opgehaalde set lijstelementeigenschappen van de kaart ingesteld als de argumenten voor deze functie. Hiermee kan het vel bevolkt worden met de juiste tekst.

Daarmee hebben we een volledig functioneel vel waar we onze aftellingen kunne toevoegen en bewerken!