Uzante apartajn dosierojn
Disigante neoportunan kodon en malsamajn dosierojn, kaj aligu signalojn al viaj komponantoj.
Kial kaj kiel
Por la unua fojo, ni apartigos iujn niajn komponantojn en siajn proprajn QML-dosierojn. Se ni daŭre aldonas aferojn al Main.qml
, rapide fariĝos malfacile diri kio faras kion, kaj ni riskas ŝlimigi nian kodon.
In this tutorial, we will be splitting the code in Main.qml
into Main.qml
, AddDialog.qml
and KountdownDelegate.qml
.
Aldone, eĉ dum disvastigo de kodo inter pluraj QML-dosieroj, la kvanto de dosieroj en realaj projektoj povas malaperi. Ofta solvo al ĉi tiu problemo estas logike apartigi dosierojn en malsamajn dosierujojn. Ni rigardos tri oftajn alirojn viditajn en realaj projektoj, kaj efektivigos unu el ili:
- konservante QML-dosierojn kune kun C++-dosieroj
- konservante QML-dosierojn en malsama dosierujo sub la sama modulo
- konservante QML-dosierojn en malsama dosierujo sub malsama modulo
Post la disigo, ni havos disigo de zorgoj inter ĉiu dosiero, kaj [efektivigaj detaloj estos abstraktitaj](https://en.wikipedia.org/ wiki/Abstraction_(komputiko_scienco)), farante la kodon pli legebla.
Konservante QML-dosierojn kune kun C++-dosieroj
Ĉi tio konsistas el konservi la QML-dosierojn de la projekto kune kun C++-dosieroj en src/
. Tia strukturo aspektus jene:
kirigami-tutorial/
├── CMakeLists.txt
├── org.kde.tutorial.desktop
└── src/
├── CMakeLists.txt
├── main.cpp
├── Main.qml
├── AddDialog.qml
└── KountdownDelegate.qml
Jen kion ni faris antaŭe. En la ĉi-supra kazo, vi nur bezonus daŭre aldoni QML-dosierojn al la ekzistanta kirigami-tutorial/src/CMakeLists.txt
. Tute ne ekzistas logika disiĝo, kaj post kiam la projekto ricevas pli ol kelkajn QML-dosierojn (kaj C++-dosieroj, kiuj kreas tipojn por esti uzataj en QML), la dosierujo povas rapide pleniĝi.
Konservante QML-dosierojn en malsama dosierujo sub la sama modulo
Ĉi tio konsistas el konservi ĉiujn QML-dosierojn en aparta dosierujo, kutime src/qml/
. Tia strukturo aspektus jene:
kirigami-tutorial/
├── CMakeLists.txt
├── org.kde.tutorial.desktop
└── src/
├── CMakeLists.txt
├── main.cpp
└── qml/
├── Main.qml
├── AddDialog.qml
└── KountdownDelegate.qml
Ĉi tiu strukturo estas tre ofta en KDE-projektoj, plejparte por eviti havi kroman CMakeLists.txt-dosieron por la dosierujo src/qml/
kaj krei apartan modulon. Ĉi tiu metodo konservas la dosierojn mem en aparta dosierujo, sed vi ankaŭ bezonus aldoni ilin en kirigami-tutorial/src/CMakeLists.txt
. Ĉiuj kreitaj QML-dosieroj tiam apartenus al la sama QML-modulo kiel Main.qml
.
En praktiko, post kiam la projekto ricevas pli ol dekduon QML-dosierojn, dum ĝi ne plenigos la dosierujon src/
, ĝi plenigos la dosieron src/CMakeLists.txt
. Malfaciliĝos diferenci inter tradiciaj C++-dosieroj kaj C++-dosieroj, kiuj havas tipojn elmontritajn al QML.
Ĝi ankaŭ rompos la koncepton de loko (lokigo de dependecaj detaloj), kie vi konservus la priskribon de viaj dependecoj en la sama loko kiel la dependecoj mem.
Konservi QML-dosierojn en malsama dosierujo sub malsama modulo
Ĉi tio konsistas el konservi ĉiujn QML-dosierojn en aparta dosierujo kun sia propra CMakeLists.txt kaj propra aparta QML-modulo. Tia strukturo aspektus jene:
kirigami-tutorial/
├── CMakeLists.txt
├── org.kde.tutorial.desktop
└── src/
├── CMakeLists.txt
├── main.cpp
├── Main.qml
└── components/
├── CMakeLists.txt
├── AddDialog.qml
└── KountdownDelegate.qml
Ĉi tiu strukturo ne estas tiel ofta en KDE-projektoj kaj postulas verki plian CMakeLists.txt, sed ĝi estas la plej fleksebla. En nia kazo, ni nomas nian dosierujon "components" ĉar ni kreas du novajn QML-komponentojn el nia antaŭa dosiero Main.qml
, kaj konservas informojn pri ili en kirigami-tutorial/src/components/CMakeLists.txt.
. La dosiero Main.qml
mem restas en src/
do ĝi estas aŭtomate uzata dum rulado de la rulebla, kiel antaŭe.
Pli poste, eblus krei pliajn dosierujojn kun pluraj QML-dosieroj, ĉiuj grupigitaj laŭ funkcio, kiel "modeloj" kaj "agordoj", kaj C++-dosieroj kiuj havas tipojn elmontritajn al QML (kiel modeloj) povus. estu konservita kune kun aliaj QML-dosieroj kie ĝi havas sencon.
Ni uzos ĉi tiun strukturon en ĉi tiu lernilo.
Preparante CMake por la novaj dosieroj
Unue, kreu la dosieron kirigami-tutorial/src/components/CMakeLists.txt
kun la jena enhavo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| add_library(kirigami-hello-components)
ecm_add_qml_module(kirigami-hello-components
URI "org.kde.tutorial.components"
GENERATE_PLUGIN_SOURCE
)
ecm_target_qml_sources(kirigami-hello-components
SOURCES
AddDialog.qml
KountdownDelegate.qml
)
ecm_finalize_qml_module(kirigami-hello-components)
install(TARGETS kirigami-hello-components ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
Ni kreas novan celon nomitan kirigami-hello-components
kaj poste turnas ĝin en QML-modulon uzante ecm_add_qml_module() sub la importnomo. org.kde.tutorial.components
kaj aldonu la koncernajn QML-dosierojn.
Ĉar la celo estas diferenca de la rulebla, ĝi funkcios kiel malsama QML-modulo, en kiu kazo ni devos fari du aferojn: fari ĝin generi kodon por ke ĝi funkciu kiel Qt-kromaĵo kun [GENERATE_PLUGIN_SOURCE](https: //api.kde.org/ecm/module/ECMQmlModule.html), kaj finaligu ĝin per ecm_finalize_qml_module(). Ni tiam instalas ĝin ĝuste kiel en antaŭaj lecionoj.
Ni bezonis uzi add_library() por ke ni povu ligi kirigami-hello-components
al la plenumebla en la target_link_libraries( ) voku en kirigami-tutorial/src/CMakeLists.txt
:
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
| add_executable(kirigami-hello)
ecm_add_qml_module(kirigami-hello
URI
org.kde.tutorial
)
target_sources(kirigami-hello
PRIVATE
main.cpp
)
ecm_target_qml_sources(kirigami-hello
SOURCES
Main.qml
)
target_link_libraries(kirigami-hello
PRIVATE
Qt6::Quick
Qt6::Qml
Qt6::Gui
Qt6::QuickControls2
Qt6::Widgets
KF6::I18n
KF6::CoreAddons
kirigami-hello-components
)
install(TARGETS kirigami-hello ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
add_subdirectory(components)
|
Ni ankaŭ devas uzi add_subdirectory() do CMake trovos la dosierujon kirigami-tutorial/src/components/
.
En la antaŭaj lecionoj, ni ne bezonis aldoni la importon org.kde.tutorial
al nia Main.qml
ĉar ĝi ne estis bezonata: estante la enirpunkto por la aplikaĵo, la efektivigebla ĉiuokaze rulus la dosieron tuj. Ĉar niaj komponantoj estas en aparta QML-modulo, la nova importo en kirigami-tutorial/src/Main.qml
estas necesa, la sama difinita antaŭe, org.kde.tutorial.components
:
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
import org.kde.tutorial.components
// La resto de la kodo...
Kaj ni estas pretaj iri.
Spliting Main.qml
Ni rigardu denove la originalan 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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
| import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
Kirigami.ApplicationWindow {
id: root
width: 600
height: 400
title: i18nc("@title:window", "Day Kountdown")
globalDrawer: Kirigami.GlobalDrawer {
isMenu: true
actions: [
Kirigami.Action {
text: i18n("Quit")
icon.name: "application-exit-symbolic"
shortcut: StandardKey.Quit
onTriggered: Qt.quit()
}
]
}
ListModel {
id: kountdownModel
}
Component {
id: kountdownDelegate
Kirigami.AbstractCard {
contentItem: Item {
implicitWidth: delegateLayout.implicitWidth
implicitHeight: delegateLayout.implicitHeight
GridLayout {
id: delegateLayout
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 {
level: 1
text: i18n("%1 days", Math.round((date-Date.now())/86400000))
}
ColumnLayout {
Kirigami.Heading {
Layout.fillWidth: true
level: 2
text: name
}
Kirigami.Separator {
Layout.fillWidth: true
visible: description.length > 0
}
Controls.Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: description
visible: description.length > 0
}
}
Controls.Button {
Layout.alignment: Qt.AlignRight
Layout.columnSpan: 2
text: i18n("Edit")
}
}
}
}
}
Kirigami.Dialog {
id: addDialog
title: i18nc("@title:window", "Add kountdown")
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
padding: Kirigami.Units.largeSpacing
preferredWidth: Kirigami.Units.gridUnit * 20
// 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 a label attached to the textfield
Kirigami.FormData.label: i18nc("@label:textbox", "Name*:")
// What to do after input is accepted (i.e. pressed Enter)
// In this case, it moves the focus to the next field
onAccepted: descriptionField.forceActiveFocus()
}
Controls.TextField {
id: descriptionField
Kirigami.FormData.label: i18nc("@label:textbox", "Description:")
placeholderText: i18n("Optional")
// Again, it moves the focus to the next field
onAccepted: dateField.forceActiveFocus()
}
Controls.TextField {
id: dateField
Kirigami.FormData.label: i18nc("@label:textbox", "ISO Date*:")
// D means a required number between 1-9,
// 9 means a required number between 0-9
inputMask: "D999-99-99"
// Here we confirm the operation just like
// clicking the OK button
onAccepted: addDialog.onAccepted()
}
Controls.Label {
text: "* = required fields"
}
}
// Once the Kirigami.Dialog is initialized,
// we want to create a custom binding to only
// make the Ok button visible if the required
// text fields are filled.
// For this we use Kirigami.Dialog.standardButton(button):
Component.onCompleted: {
const button = standardButton(Kirigami.Dialog.Ok);
// () => is a JavaScript arrow function
button.enabled = Qt.binding( () => requiredFieldsFilled() );
}
onAccepted: {
// The binding is created, but we still need to make it
// unclickable unless the fields are filled
if (!addDialog.requiredFieldsFilled()) return;
appendDataToModel();
clearFieldsAndClose();
}
// We check that the nameField is not empty and that the
// dateField (which has an inputMask) is completely filled
function requiredFieldsFilled() {
return (nameField.text !== "" && dateField.acceptableInput);
}
function appendDataToModel() {
kountdownModel.append({
name: nameField.text,
description: descriptionField.text,
date: new Date(dateField.text)
});
}
function clearFieldsAndClose() {
nameField.text = ""
descriptionField.text = ""
dateField.text = ""
addDialog.close();
}
}
pageStack.initialPage: Kirigami.ScrollablePage {
title: i18nc("@title", "Kountdown")
// Kirigami.Action encapsulates a UI action. Inherits from Controls.Action
actions: [
Kirigami.Action {
id: addAction
// Name of icon associated with the action
icon.name: "list-add-symbolic"
// Action text, i18n function returns translated string
text: i18nc("@action:button", "Add kountdown")
// What to do when triggering the action
onTriggered: addDialog.open()
}
]
Kirigami.CardsListView {
id: cardsView
model: kountdownModel
delegate: kountdownDelegate
}
}
}
|
La propra delegito kun id: kountdownDelegate
povas esti tute dividita ĉar ĝi jam estas envolvita en QML Component-tipo. Ni uzas Komponaĵon por povi difini ĝin sen bezoni instantiigi ĝin; apartaj QML-dosieroj funkcias same.
Se ni movas la kodon al apartaj dosieroj, do ne utilas lasi ĝin envolvita en Komponento: ni povas dividi nur la Kirigami.AbstractCard en la aparta dosiero. Jen la rezulta 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
| import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
Kirigami.AbstractCard {
contentItem: Item {
implicitWidth: delegateLayout.implicitWidth
implicitHeight: delegateLayout.implicitHeight
GridLayout {
id: delegateLayout
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 {
level: 1
text: i18n("%1 days", Math.round((date-Date.now())/86400000))
}
ColumnLayout {
Kirigami.Heading {
Layout.fillWidth: true
level: 2
text: name
}
Kirigami.Separator {
Layout.fillWidth: true
visible: description.length > 0
}
Controls.Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: description
visible: description.length > 0
}
}
Controls.Button {
Layout.alignment: Qt.AlignRight
Layout.columnSpan: 2
text: i18n("Edit")
}
}
}
}
|
Our dialog with id: addDialog
is not enveloped in a Component, and it is not a component that is visible by default, so the code can be copied as is into the AddDialog.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
| import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
Kirigami.Dialog {
id: addDialog
title: i18nc("@title:window", "Add kountdown")
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
padding: Kirigami.Units.largeSpacing
preferredWidth: Kirigami.Units.gridUnit * 20
Kirigami.FormLayout {
Controls.TextField {
id: nameField
Kirigami.FormData.label: i18nc("@label:textbox", "Name*:")
onAccepted: descriptionField.forceActiveFocus()
}
Controls.TextField {
id: descriptionField
Kirigami.FormData.label: i18nc("@label:textbox", "Description:")
onAccepted: dateField.forceActiveFocus()
}
Controls.TextField {
id: dateField
Kirigami.FormData.label: i18nc("@label:textbox", "ISO Date*:")
inputMask: "D999-99-99"
onAccepted: addDialog.accepted()
}
Controls.Label {
text: "* = required fields"
}
}
Component.onCompleted: {
const button = standardButton(Kirigami.Dialog.Ok);
button.enabled = Qt.binding( () => requiredFieldsFilled() );
}
onAccepted: {
if (!addDialog.requiredFieldsFilled()) return;
appendDataToModel();
clearFieldsAndClose();
}
function requiredFieldsFilled() {
return (nameField.text !== "" && dateField.acceptableInput);
}
function appendDataToModel() {
kountdownModel.append({
name: nameField.text,
description: descriptionField.text,
date: new Date(dateField.text)
});
}
function clearFieldsAndClose() {
nameField.text = ""
descriptionField.text = ""
dateField.text = ""
addDialog.close();
}
}
|
Kun la koddivido, Main.qml
tiel fariĝas multe pli mallonga:
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
| import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami
import org.kde.tutorial.components
Kirigami.ApplicationWindow {
id: root
width: 600
height: 400
title: i18nc("@title:window", "Day Kountdown")
globalDrawer: Kirigami.GlobalDrawer {
isMenu: true
actions: [
Kirigami.Action {
text: i18n("Quit")
icon.name: "application-exit-symbolic"
shortcut: StandardKey.Quit
onTriggered: Qt.quit()
}
]
}
ListModel {
id: kountdownModel
}
AddDialog {
id: addDialog
}
pageStack.initialPage: Kirigami.ScrollablePage {
title: i18nc("@title", "Kountdown")
actions: [
Kirigami.Action {
id: addAction
icon.name: "list-add-symbolic"
text: i18nc("@action:button", "Add kountdown")
onTriggered: addDialog.open()
}
]
Kirigami.CardsListView {
id: cardsView
model: kountdownModel
delegate: KountdownDelegate {}
}
}
}
|
We now have two extra QML files, AddDialog.qml
and KountdownDelegate
, and we need to find some way of using them in Main.qml
. The way to add the contents of the new files to Main.qml
is by instantiating them.
AddDialog.qml
becomes AddDialog {}
:
31
32
33
| AddDialog {
id: addDialog
}
|
KountdownDelegate.qml
fariĝas KountdownDelegate {}
:
47
48
49
50
51
| Kirigami.CardsListView {
id: cardsView
model: kountdownModel
delegate: KountdownDelegate {}
}
|
Plej multaj kazoj, kiujn vi vidis de komponanto komencita per majuskla kaj sekvita per krampoj, estis instancoj de QML-komponento. Jen kial niaj novaj QML-dosieroj devas komenci per majuskla litero.
Kompilu la projekton kaj rulu ĝin, kaj vi devus havi funkcian fenestron kiu kondutas ekzakte same kiel antaŭe, sed kun la kodo dividita en apartajn partojn, farante aferojn multe pli regeblaj.