Een dialoog toevoegen

Grip krijgen op Kirigami dialogen.

Uw toepassing nuttig maken

We hebben een venster, we hebben kaarten en we hebben acties. Nu moeten we nog steeds een manier vinden voor het invoeren van een naam, beschrijving en datum van uw keuze.

Een manier waarop we dit zouden kunnen doen is met het maken van een nieuwe pagina waar we de vereiste invoerelementen in plaatsen. Een gehele pagina bestemd om een naam, beschrijving en datum te leveren lijkt een beetje teveel.

In plaats daarvan zullen we een dialoog gebruiken.

Dialoog die verschijnt in het midden van de toepassing

Het dialoogvenster openen

pageStack.initialPage: Kirigami.ScrollablePage {
    // ...
    actions: [
        Kirigami.Action {
            id: addAction
            icon.name: "list-add"
            text: i18nc("@action:button", "Add kountdown")
            onTriggered: addDialog.open()
        }
    ]
}

Als eerste bewerken we de actie uit de vorige inleiding: gewoon een Kirigami.Action die de functie van de dialoog open() start.

Dialogen die aftellen toevoegen

De nieuwe component die we toevoegen is Kirigami.Dialog. Dialogen verschijnen in het centrum van het venster en kunnen gebruikt worden om extra informatie te leveren relevant voor de huidige inhoud. Ze kunnen niet verplaatst worden, maar ze passen hun eigen grootte aan aan het venster.

Kirigami.ApplicationWindow {
    // ...
    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

        // Formulierindelingen helpen met het uitlijnen en structuren van een layout met meerdere
        // invoervelden
        Kirigami.FormLayout {
            // Tekstvelden laten u tekst invoeren in een tekstvak
            Controls.TextField {
                id: nameField
                // Plaatst een label bij het tekstveld
                Kirigami.FormData.label: i18nc("@label:textbox", "Name*:")
                // Wat te doen nadat de invoer is geaccepteerd (d.w.z. Enter is ingedrukt). In dit
                // geval wordt de focus verplaatst naar het volgende veld
                onAccepted: descriptionField.forceActiveFocus()
            }
            Controls.TextField {
                id: descriptionField
                Kirigami.FormData.label: i18nc("@label:textbox", "Description:")
                placeholderText: i18n("Optional")
                // Opnieuw wordt de focus verplaatst naar het volgende veld
                onAccepted: dateField.forceActiveFocus()
            }
            Controls.TextField {
                id: dateField
                Kirigami.FormData.label: i18nc("@label:textbox", "ISO Date*:")
                // D betekent een vereist cijfer tussen 1-9, 9 betekent een vereist cijfer tussen
                // 0-9
                inputMask: "D999-99-99"
                // Hier accepteren we de wijziging door gewoon op de OK-knop te klikken
                onAccepted: addDialog.onAccepted()
            }
            Controls.Label {
                text: "* = required fields"
            }
        }
        // De dialoog-code komt hier
    }
    // ...
}

Standaard hebben dialogen een Kop (header) en een Voet (footer), beide geërfd van Controls.Dialog.

Standaard heeft de kop een titel en een sluitknop die uitgeschakeld kan worden met showCloseButton. Standaard heeft de voet een sluitknop, dit is aanpasbaar met standardButtons.

We stellen het eerst zodanig in dat het een "Ok"-knop en een knop "Annuleren" toont, vervolgens voegen we nog wat opvulruimte toe, en voegen een redelijke preferredWidth toe. De voorkeur breedte is de verwachte standaard afmeting van het dialoogvenster, die indien nodig nog groter kan worden. We kunnen de standaard Kirigami.Units gebruiken die we later opnieuw zullen bezoeken.

En dan komen we bij een Kirigami.FormLayout. In tegenstelling tot een ColumnLayout, is de indeling van zijn kinderen automatisch en gecentreerd, met optioneel labels. Zoals de naam al aangeeft, wordt het gebruikt om invul-formulieren te creëren.

Deze formulierindelingen zijn ontworpen om te werken met een variëteit van verschillende invoertypen, maar nu blijven we bij eenvoudige Controls.Textfield invoer die ons eenvoudige tekstvelden geeft om dingen in te schrijven.

We hebben Textfield-elementen gemaakt die acteren als:

  1. Invoer voor de naam van onze aftelling
  2. Invoer voor de beschrijving van onze aftelling
  3. Invoer voor de datum waar we naar aftellen, die geleverd moet worden in een formaat JJJJ-MM-DD

In elk van deze Controls.Textfield-elementen, voegen we een Kirigami.FormData.label-eigenschap toe waarmee we labels voor elk daarvan kunnen definiëren. Het formulier zal dan voor elk de correcte label links van de tekstvelden tonen.

En tenslotte voegen we de onAccepted-eigenschap toe om het startsignaal te geven voor forceActiveFocus()-methode voor het volgende veld; dit zal het veld dat actief is omschakelen naar het volgende veld nadat de gebruiker de ENTER-toets heeft ingedrukt, wat de bruikbaarheid van het formulier verbetert.

We hebben ook een eigenschap genaamd inputMask ingesteld op het tekstveld voor onze datum. Dit instellen op D999-99-99" voorkomt dat gebruikers iets invoeren dat de functionaliteit van de toepassing laat breken (zoals tekst), die hen beperkt tot het alleen invoeren van cijfers die we daarna kunnen proberen te ontleden in een datum object.

Nadat de gebruikersinterface voor het dialoogvenster klaar is, moeten we het het gedrag ervan wijzigen. Daarvoor hebben we drie dingen nodig:

  1. De OK-knop is alleen zichtbaar als de vereiste velden ingevuld zijn
  2. Voeg de invoerinformatie toe aan het model
  3. Formulier wissen
Kirigami.Dialog {
    // ... Nadat het Kirigami.Dialog is geïnitialiseerd, willen we een aangepaste binding creëren om
    // ervoor te zorgen dat de Ok-knop pas zichtbaar word als de vereiste tekstvelden zijn ingevuld.
    // Hiervoor gebruiken we Kirigami.Dialog.standardButton(button):
    Component.onCompleted: {
        const button = standardButton(Kirigami.Dialog.Ok);
        // () => is een JavaScript pijl-functie
        button.enabled = Qt.binding( () => requiredFieldsFilled() );
    }
    onAccepted: {
        // De binding is gecreëerd, maar we moeten het niet klikbaar maken tenzij de velden ingevuld
        // zijn
        if (!addDialog.requiredFieldsFilled()) return;
        appendDataToModel();
        clearFieldsAndClose();
    }
}

Wat als eerste gedaan moet worden is de creatie van een binding tussen de eigenschap enabled van de OK-knop en een controle of de velden zijn ingevuld, wat in dit geval gedaan moet worden met Qt.binding() in JavaScript. In feite, is de regel:

button.enabled = Qt.binding( () => requiredFieldsFilled() );

vergelijkbaar met de QML bindings die we tot nog toe hebben gezien, zoals in de volgende voorbeeld-code:

enabled: requiredFieldsFilled()

De signal handler dat de Ok-knop vrijgeeft is onAccepted. Het blijft leeg en zonder dat het iets doet als de vereiste velden zijn gevuld; als dat niet het geval is dan voert het de invoer naar het model en maakt het dialoogvenster leeg voor de volgende keer dat het wordt geopend.

Kirigami.Dialog {
    // ... We controleren dat de nameField niet leeg is en dat de dateField (wat een inputMask
    // heeft) compleet is ingevuld
    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();
    }
}

Voor ons vereiste nameField, is alles wat we moeten doen is controleren of de veldtekst een lege tekst is. Voor het dateField, omdat het een inputMask heeft, moeten we in plaats daarvan acceptableInput gebruiken, wat alleen 1 wordt als het hele veld is ingevuld is en alleen acceptable karakters bevat.

Daarna, de methode append() van ons lijstmodel kountdownModel voegt een JavaScript object toe inclusief de eigenschappen die we hebben geleverd.

Tenslotte verzekeren we ons ervan om de tekstvelden te wissen door hun teksteigenschappen op een lege tekenreeks in te stellen, daarna close() sluit het.

Nadat we onze bestanden hebben opgeslagen en ons programma hebben gebouwd, kunnen we onze eigen aftelling toevoegen! We kunnen een laatste wijziging aanbrengen om de interface te verbeteren, namelijk de dummy aftelling verwijderen die we in de vorige lessen hadden:

26
27
28
    ListModel {
        id: kountdownModel
    }

Ten tweede nu we een actuele datum hebben om mee te spelen, kunnen we de tijd tot de vermelde datum berekenen:

47
48
49
50
                    Kirigami.Heading {
                        level: 1
                        text: i18n("%1 days", Math.round((date-Date.now())/86400000))
                    }

En ten derde verhoog de venstergrootte zodat we meer ruimte hebben voor onze nieuwe kaarten:

 9
10
    width: 600
    height: 400

Veel beter

Onze toepassing (app) tot zover

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

Schermafdruk van de toepassing met vier voorbeeldkaarten