Använda separata filer och signaler

Separera tungrodd kod i olika filer, och koppla signaler till dina komponenter.

Men varför?

För första gången separerar vi vissa av våra komponenter i sina egna QML-filer. Om vi fortsätter att lägga till saker i main.qml, blir det snart svårt att säga vad som gör vad, och vi riskerar att trassla till vår kod.

Först måste vi lägga till våra nya filer i vår resources.qrc som vi skapade i den första delen av handledningen.

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

Använda våra nya filer

Vi behöver komma på något sätt att använda våra nya filer i main.qml. Som tur är behöver vi bara inkludera en deklaration av komponenterna i vår main.qml på följande sätt:

AddEditSheet {
    id: addEditSheet
}

Utöka vårt tilläggsblad till ett tilläggs- och redigeringsblad

Medan vi fick vår knapp för tillägg av nedräknare att göra någonting i den förra handledningen, är redigeringsknappen på våra nedräkningskort fortfarande inaktiv. Vi skapade också ett tilläggsblad som vi nu skulle kunna återanvända för att också fungera som ett redigeringsblad ... men innan vi kommer till det, behöver vi lägga till några extra saker i vår 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 {}
        }
    }
}

De viktigaste ändringarna vi har gjort omfattar tillägg av vår komponentdefinition AddEditSheet (och KountdownDelegate längre ner) och en ny funktion som kallas openPopulatedSheet().

Låt oss gå igenom definitionen av vårt AddEditSheet:

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

onAdded och onEdited är signalhanterare. Precis som onTriggered anropas när vi klickar på en åtgärd, kan vi också använda hanterare som svarar på våra egna signaler.

AddEditSheet.qml

Om vi betraktar vår nya AddEditSheet.qml, det återanvända additionsbladet, kan vi se hur signalerna fungerar:

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

Signaler anropar sina hanterare när de anropas. I detta fall, har vi skapat två signaler, added och edited, som vi kan anropa med olika resultat, och som vi kan bifoga information om nedräknaren vi lägger till eller skapar. En snygg sak med signaler är att de exponerar variablerna definierad inne i dem för funktionerna som lyssnar på dem, vilket är anledningen till att vi helt enkelt kan anropa variabelnamnen i våra hanterare onEdited och onAdded i main.qml. Våra signaler anropas av knappen 'Done' beroende på vad egenskapen mode, definierad längst upp i vårt AddEditSheet, är inställd till.

Egenskapen mode styr också flera andra saker: i huvudsak vad bladets titel ställs in till, och vilken text som ska inkluderas i våra textfält. Dock är egenskapen mode bara inställd att lägga till ...

Vilket tar oss tillbaka till main.qml och vår nya funktion openPopulatedSheet(). Du kanske har märkt att det är vad som anropas nu när åtgärden för tillägg av nedräknare utlöses. Funktionen har flera argument som har försetts med förvalda värden. Det är till hjälp när vi helt enkelt vill lägga till en ny nedräknare, eftersom vi kan ha det kortfattade funktionsanropet openPopulatedSheet("add"). Vad som är viktigare är att funktionen tilldelar alla relevanta egenskaper i 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 ändrar tilläggs- och redigeringsbladet beroende på om argumentet är tilldelat "add" eller "edit"
  • index behövs så att den rätta ändras när vi sparar vår redigerade nedräknare
  • listName, listDesc och listDate är den relevanta nedräkningsinformationen som måste lägga till i bladets fält

Naturligtvis, för att verkligen använda vårt blad förutom att lägga till nedräknare, måste vi först få redigeringsknappen på våra kort att fungera. Men om du tittar på vår Kirigami.CardsListView i main.qml...

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

KountdownDelegate.qml

Vi har ersatt vår Kirigami.AbstractCard med en definition av en delegatkomponent från 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))
            }
        }
    }
}

Egenskapen onClicked för knappen "Edit" på våra kort anropar nu funktionen openPopulatedSheet(), med kortets hämtade egenskaper för listelement använda som funktionens argument. Med dessa kan bladet befolkas med rätt text.

Därmet har vi ett fullständigt fungerande blad där vi kan lägga till och redigera våra nedräkningar.