Користування окремими файлами і сигналами

Розподіл громіздкого коду між різними файлами і долучення сигналів до ваших компонентів.

Але чому?

Ми вперше відокремлюємо деякі з наших компонентів у окремі файли QML. Якщо ми продовжуватимемо додавати код до main.qml, розростання файла ускладнить розбір його частин і збільшить ризик помилок у нашому коді.

Спочатку нам слід додати наші нові файли до нашого resources.qrc, який ми створили у першій частині цього розділу підручника.

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

Використання наших нових файлів

Нам слід знайти якийсь спосіб використання наших нових файлів у main.qml. На щастя, нам достатньо просто включити оголошення цих компонентів у наш main.qml ось так:

AddEditSheet {
	id: addEditSheet
}

Розширюємо можливості нашого аркуша додавання так, щоб він став аркушем додавання-редагування

Хоча у останній частині підручника ми додали до кнопки додавання відліку дії, кнопка редагування наших карток відліку усе ще лишається неактивною. Нами також було створено аркуш додавання відліків, якими ми тепер можемо повторно скористатися як аркушем редагування… Але перш ніж ми перейдемо до цього, нам слід додати до нашого 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 {}
		}
	}
}

Ключовими внесеними змінами є додавання нашого визначення компонента AddEditSheetKountdownDelegate нижче) і нової функції із назвою openPopulateSheet.

Пройдімося нашим визначенням AddEditSheet:

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

onAdded і onEdited є обробниками сигналів. Подібно до того, як onTriggered викликається, коли ми натискаємо пункт дії, ми можемо скористатися обробниками, які відповідають на наші нетипові сигнали. Поглянувши на наш новий AddEditSheet.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
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();
			}
		}
	}
}

Сигнали викликають обробники при виклику. У нашому випадку, ми створили два сигнали, які ми можемо викликати для отримання різних результатів, і з якими ми можемо пов’язати відомості щодо відліку, який ми додаємо або створюємо. Чудовою властивістю сигналів є те, що вони розкривають змінні, які у них визначено для функцій, які очікують на сигнали. Ось чому ми можемо просто викликати назви цих змінних у наших обробниках onEdited і onAdded у main.qml. Наші сигнали викликає кнопка «Done», залежно від значення, встановленого для властивості mode, визначеної у верхній частині нашого AddEditSheet.

Крім того, властивість mode керує декількома іншими речима: в основному, тим, який заголовок буде встановлено для нашого аркуша, і який текст буде включено до наших текстових полів. Втім, типово, для нашої властивості mode просто встановлено режим додавання…

Що повертає нас до main.qml і нашої нової функції openPopulateSheet. Ви могли помітити, що це саме те, що буде тепер викликано при вмиканні дії з додавання відліку. Ця функція приймає декілька аргументів, які мають типові значення. Це корисно, коли ми просто хочемо додати новий відлік, оскільки ми можемо скористатися скороченим викликом функції — openPopulateSheet("add"). Що важливіше, ця функція встановлює значення усіх пов’язаних із її призначенням властивостей в 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 змінює аркуш додавання-редагування залежно від встановленого аргументу: «add» або «"edit"
  • Аргумент index потрібен для того, щоб при збереження нашого редагованого відліку було внесено зміни до належного запису
  • listName, listDesc і listDate — відповідні параметри відліків, які має бути вписано у поля аркуша

Звичайно, щоб справді скористатися нашим аркушем для чогось, окрім додавання відліків, нам спершу слід зробити працездатною кнопку редагування на наших картках. Але, якщо ви поглянете на нашу Kirigami.CardsListView у main.qml

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

Ми замінили наш Kirigami.AbstractCard визначенням компонента-делегата з 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))
			}
		}
	}
}

Тепер властивість onClicked кнопки «Edit» на наших картках викликає функцію openPopulateSheet із отриманими карткою властивостями елементів списку як аргументами цієї функції. Ці дані надають змогу заповнити аркуш належними текстовими даними.

Тепер ми маємо повнофункціональний аркуш, за допомогою якого ми можемо додавати і редагувати наші відліки!