Uporaba ločenih datotek in signalov

Ločevanje neusmiljene kode v različne datoteke in spajanje signalov komponentam.

Ampak zakaj?

Prvič bomo nekatere naše komponente ločili v lastne QML datoteke. Če bomo še naprej dodajali stvari na "main.qml", bo hitro postalo težko določljivo, kaj vse to počne in tvegamo blatenje naše kode.

Najprej moramo dodati naše nove datoteke v naše `resources.qrc' ki smo jih ustvarili v prvem delu te vaje.

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

Uporaba naših novih datotek

Najti moramo način uporabe naših novih datotek v "main.qml". na srečo moramo le vključiti izjavo o teh komponentah v naš 'main.qml' takole:

AddEditSheet {
    id: addEditSheet
}

Razširitev lista z dodajanjem v list za dodajanje/urejanje

Če smo v zadnji vaji naredili, da je naš gumb za dodajanje odštevanja nekaj opravil, je gumb za urejanje na naših karticah odštevanja še vedno nedejaven. Ustvarili smo tudi seštevalni list, kateremu bi zdaj lahko spremenili namen, da bi služil tudi kot list za urejanje … ampak preden pridemo do tega, moramo dodati nekaj reči naši datoteki main.qml.

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
import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.20 as Kirigami

Kirigami.ApplicationWindow {
    id: root

    title: i18nc("@title:window", "Day Kountdown")

    globalDrawer: Kirigami.GlobalDrawer {
        isMenu: true
        actions: [
            Kirigami.Action {
                text: i18n("Quit")
                icon.name: "gtk-quit"
                shortcut: StandardKey.Quit
                onTriggered: Qt.quit()
            }
        ]
    }

    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 and by 'add'-Action
    function openPopulatedSheet(mode, index = -1, listName = "", listDesc = "", listDate = "") {
        addEditSheet.mode = mode
        addEditSheet.index = index;
        addEditSheet.name = listName
        addEditSheet.description = listDesc
        addEditSheet.kdate = listDate

        addEditSheet.open()
    }

    pageStack.initialPage: Kirigami.ScrollablePage {
        title: i18nc("@title", "Kountdown")

        // Kirigami.Action encapsulates a UI action. Inherits from Controls.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: openPopulatedSheet("add")
        }

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

Ključne spremembe, ki smo jih naredili, vključujejo dodajanje definicije naše komponente 'AddEditSheet' (in naprej spodaj "KountdownDelegate") in novo funkcijo, imenovano 'openPopulatedSheet()'.

Pojdimo čez definicijo AddEditSheet:

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

'onAdded' in 'onEdited' so krmilniki signala. Tako kot se imenuje 'onTriggered', ko kliknemo dejanje, lahko uporabimo tudi krmilnike, ki se odzivajo na naše signale po meri.

AddEditSheet.qml

Če pogledamo našo novo AdditSheet.qml - našo predelavo dodajanja lista - lahko vidimo, kako delujejo ti signali:

 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
import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.20 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 alias name: nameField.text
    property alias description: descriptionField.text
    property alias kdate: dateField.text

    // 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
            onAccepted: descriptionField.forceActiveFocus()
        }
        Controls.TextField {
            id: descriptionField
            Kirigami.FormData.label: i18nc("@label:textbox", "Description:")
            placeholderText: i18n("Optional")
            onAccepted: dateField.forceActiveFocus()
        }
        Controls.TextField {
            id: dateField
            Kirigami.FormData.label: i18nc("@label:textbox", "Date:")
            inputMask: "0000-00-00"
            placeholderText: i18n("YYYY-MM-DD")
        }
        // 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();
            }
        }
    }
}

Signali prikličejo svoje obdelovalnike, ko so klicani. V tem primeru smo ustvarili dva signala added in edited, ki ju lahko prikličemo z različnimi izidi in katerim lahko priložimo informacije o odštevanju, ki ga dodajamo ali ustvarjamo. Pri signalih je priročno, da spremenljivke, opredeljene v njih, izpostavijo funkcijam, ki jih poslušajo, zato lahko te spremenljivke kličemo v naših obdelovalnikih onAdded in onEdited v main.qml. Naše signale prikliče gumb "Done" glede na to, na kaj je nastavljena lastnost mode na vrhu naše AddEditSheet.

Lastnost 'mode' nadzira tudi več drugih stvari: predvsem na kakšen naslov našega lista je nastavljen, in kakšno besedilo naj bi bilo vključeno v naša besedilna polja. Vendar je privzeto naša lastnost 'mode' nastavljena tako, da doda...

Kar nas pripelje nazaj do 'main.qml' in naše nove funkcije 'openPopulateSheet()'. Morda ste opazili, da se to imenuje tedaj, ko se sproži dejanje dodajanja odštevanja. Ta funkcija vključuje več argumentov, ki so bili zagotovljeni s privzetimi vrednostmi. To nam je v pomoč, ko želimo preprosto dodati novo odštevanje, saj lahko imamo jedrnato funkcijo klica 'openPopulateSheet("add")'. Še pomembneje je, da ta funkcija določa vse ustrezne lastnosti v AddEditSheet.

function openPopulatedSheet(mode, index = -1, listName = "", listDesc = "", listDate = "") {
    addEditSheet.mode = mode
    addEditSheet.index = index;
    addEditSheet.name = listName
    addEditSheet.description = listDesc
    addEditSheet.kdate = listDate

    addEditSheet.open()
}
  • 'mode' spremeni list z dodajanjem/urejanjem, odvisno od tega, ali je ta argument nastavljen na "add" ali na "edit"
  • argument 'index' je potreben, tako da se pri shranjevanje urejenega odštevanja spremeni pravega
  • 'listName', 'listDesc' in 'listDate' so ustrezne podrobnosti odštevanja, ki jih je treba vnesti v polja lista

Seveda, da lahko dejansko uporabite naš list za vse, moramo poleg dodajanja odštevanja najprej narediti gumb urejanja, da na naših karticah to deluje. Če pa pogledate naš Kirigami.CardsListView v 'main.qml'...

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

KountdownDelegate.qml

Smo zamenjali našo Kirigami.AbstractCard z opredelitvijo komponente delegata iz '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
import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.20 as Kirigami

Kirigami.AbstractCard {
    id: kountdownDelegate
    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 {
                Layout.fillHeight: true
                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
                // Column spanning within grid layout (vertically in this case)
                Layout.columnSpan: 2
                text: i18n("Edit")
                onClicked: openPopulatedSheet("edit", index, name, description, new Date(date).toISOString().slice(0,10))
            }
        }
    }
}

Lastnost onClicked gumba 'Edit' na naših karticah zdaj kliče funkcijo 'openPopulateSheet()', pri tem pa so lastnosti elementov seznama kartice nastavljene kot argumenti za to funkcijo. S temi se lahko list napolni s pravilnim besedilom.

S tem imamo popolnoma funkcionalni list, na katerem lahko dodamo in uredimo naše odštevanje!