Używanie osobnych plików i sygnałów

Oddzielenie kodu tak dużego, że nie do ogarnięcia, do osobnych plików i dołączenie sygnałów do twoich składników.

Ale dlaczego?

Po raz pierwszy, będziemy rozdzielać część naszych składników do ich własnych plików QML. Jeśli kontynuowalibyśmy dodawanie rzeczy do main.qml, to szybko trudnym stało by się rozpoznanie co jest odpowiedzialne za co i ryzykowalibyśmy zamulonym kodem.

Najpierw musimy dodać nasze nowe pliki do naszego resources.qrc, którego stworzyliśmy w pierwszej części tego samouczka.

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

Używanie naszych nowych plików

Będziemy musieli znaleźć jakiś sposób na użycie naszych nowych plików w main.qml. Na szczęście, wystarczy zawrzeć deklarację tych składników w naszym main.qml w następujący sposób:

AddEditSheet {
	id: addEditSheet
}

Rozszerzanie naszego arkusza z dodawaniem na arkusz z dodawaniem/edytowaniem

W ostatnim samouczku nadaliśmy naszemu przyciskowi dodawania odliczań jakieś działanie, a przycisk edytowania nadal go nie ma. Dodaliśmy także arkusz dodawania, który może nam teraz posłużyć jako arkusz edytowania… lecz zanim dojdziemy do tego, musimy dodać kilka dodatkowych rzeczy do naszegomain.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 {}
		}
	}
}

Zmiany klawiszy, których dokonaliśmy, uwzględniają dodanie określenia naszego składnika AddEditSheet (oraz dalej KountdownDelegate) oraz nowej funkcji o nazwie openPopulateSheet.

Prześledźmy definicję AddEditSheet:

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

onAdded oraz onEdited są obsługującymi sygnały. Tak jak w przypadku onTriggered, które jest wywoływane po naciśnięciu działania, możemy używać metod obsługujących, które odpowiadają na nasze własne sygnały. Patrząc an nasze nowe AddEditSheet.qml – nasz przystosowany arkusz dodawania – możemy zobaczyć sposób działania sygnałów:

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

Sygnały wywołują swoje metody obsługujące, gdy zostaną wywołana. W tym przypadku utworzyliśmy dwa sygnały, które możemy wywołać z dwoma różnymi wynikami, i do których możemy dołączyć szczegóły o odliczaniu, które dodajemy lub tworzymy. Przydatną własnością sygnałów jest to, że udostępniają one zmienne w nich określone do funkcji, które ich słuchają i to dlatego odwołujemy się do nich po nazwach w metodach onEdited oraz onAdded w main.qml. Nasze sygnały są wywoływane przez przycisk ‘Gotowe’ w zależności od ustawionej własności mode, na górze naszego AddEditSheet.

Właściwość mode steruje także kilkoma innymi rzeczami: głównie tym na jaki tytuł jest ustawiony nasz arkusz i jaki tekst będą zawierały nasze pola tekstowe. Jednakże, domyślnie, nasza właściwość mode jest ustawiona tylko na dodawanie …

Co sprowadza nas do main.qml i naszej nowej funkcji openPopulateSheet. Mogłeś już zauważyć, że to jest tym, co jest wykonywane, gdy wyzwalane jest działanie dodające odliczanie. Ta funkcja przyjmuje kilka argumentów, które są wypełniane wartościami domyślnymi. Jest to użyteczne, gdy po prostu chcemy dodać nowe odliczanie, bo możemy krótko wywołać funkcję poprzez openPopulateSheet("add"). Co ważne, funkcja ta ustawia wszystkie odpowiednie własności w 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 zmienia dodaj/edytuj arkusz w zależności od tego, czy argument ten jest ustawiony na "add" czy "edit"
  • Argument index jest potrzebny, że gdy zapiszemy nasze zmienione odliczanie, to żeby odpowiednie odliczanie zostało zmienione w programie
  • listName, listDesc, oraz listDate są odpowiednimi szczegółami odliczeń, które należy umieścić w polach arkusza

Oczywiście, aby używać naszych arkuszy do czegokolwiek innego poza dodawaniem odliczań, to musimy najpierw sprawić, że przecisk edytowania na naszej karcie zacznie działać. Lecz jeśli spojrzysz na nasze Kirigami.CardsListView w main.qml

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

Zastąpiliśmy nasze Kirigami.AbstractCard delegatem definicji składnika z 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))
			}
		}
	}
}

Właściwość onClicked przycisku ‘Edit’ na naszych kartach wywołuje teraz funkcję openPopulateSheet, z właściwościami listy kart jako argumentami do tej funkcji. Dzięki nim, arkusz może być wypełniony poprawnym tekstem.

Dzięki temu, mamy w pełni funkcjonalny arkusz, do którego możemy dodawać i edytować nasze odliczania!