Aldonante dialogon

Ekkompreni dialogojn de Kirigami.

Farante nian apon utila

Ni havas fenestron, ni havas kartojn, kaj ni havas agojn. Tamen, ni ankoraŭ bezonas trovi ian manieron enigi nomon, priskribon kaj daton de nia elekto.

Unu maniero, kiel ni povus fari tion, estas kreante novan paĝon, kie ni metas la postulatajn enigelementojn. Tamen tuta paĝo dediĉita al liverado de nomo, priskribo kaj dato ŝajnas iom troa.

Anstataŭe, ni uzos dialogon.

Dialogo aperanta meze de la aplikaĵo

Malfermante la dialogon

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

First we edit the action from the previous tutorial: just a Kirigami.Action that triggers the dialog's open() function.

Retronombrado-aldonado de dialogoj

La nova komponanto, kiun ni aldonas, estas Kirigami.Dialogo. Dialogoj aperas en la centro de la fenestro kaj povas esti uzataj por provizi kromajn informojn rilatajn al la aktuala enhavo. Ili ne povas esti movitaj, sed ili adaptas sian propran grandecon al la fenestro.

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

        // Formaranĝoj helpas vicigi kaj strukturi aranĝon kun pluraj enigaĵoj
        Kirigami.FormLayout {
            // Tekstaj kampoj permesas vin enigi tekston en maldika tekstujo
            Controls.TextField {
                id: nameField
                // Disponigas etikedon alfiksitan al la tekstokampo
                Kirigami.FormData.label: i18nc("@label:textbox", "Name*:")
                // Kion fari post kiam enigo estas akceptita (t.e. premita Enigu) En ĉi tiu kazo, ĝi
                // movas la fokuson al la sekva kampo
                onAccepted: descriptionField.forceActiveFocus()
            }
            Controls.TextField {
                id: descriptionField
                Kirigami.FormData.label: i18nc("@label:textbox", "Description:")
                placeholderText: i18n("Optional")
                // Denove, ĝi movas la fokuson al la sekva kampo
                onAccepted: dateField.forceActiveFocus()
            }
            Controls.TextField {
                id: dateField
                Kirigami.FormData.label: i18nc("@label:textbox", "ISO Date*:")
                // D signifas postulatan nombron inter 1-9, 9 signifas postulatan nombron inter 0-9
                inputMask: "D999-99-99"
                // Ĉi tie ni konfirmas la operacion same kiel klaki la OK butonon
                onAccepted: addDialog.onAccepted()
            }
            Controls.Label {
                text: "* = required fields"
            }
        }
        // La dialoglogiko iras ĉi tie
    }
    // ...
}

Dialogoj defaŭlte havas header kaj [footer](https://doc.qt .io/qt-6/qml-qtquick-controls-dialog.html#footer-prop), ambaŭ hereditaj de Controls.Dialog.

La kaplinio defaŭlte inkluzivas titolon kaj fermbutonon kiu povas esti malŝaltita per [showCloseButton] (docs:kirigami2;Dialog::showCloseButton). La piedlinio defaŭlte inkluzivas fermbutonon, kaj ĝi povas esti anstataŭita per standardButtons.

Ni unue starigis ĝin por montri butonon "Ok" kaj butonon "Nuligi", aldonu iom da kompletigo kaj aldonu racian preferredWidth. La preferata larĝo estas la defaŭlta atendata grandeco de la dialogo, kiu povas pliiĝi se necese. Ni povas uzi norman Kirigami.Unuoj kiujn ni revizitos poste.

Poste ni venas al Kirigami.FormLayout. Male al ColumnLayout, la aranĝo de ĝiaj infanaj komponantoj estas aŭtomata kaj centrita, kun laŭvolaj etikedoj. Kiel la nomo implicas, ĝi estas uzata por krei enigformularojn.

Ĉi tiuj formularaj aranĝoj estas dezajnitaj por funkcii kun diversaj malsamaj enigspecoj, kvankam ni restas al simplaj Controls.Textfield enigaĵoj, kiuj donas al ni simplajn tekstskatolojn por skribi aferojn.

Ni kreis Textfield-elementojn kiuj funkcias kiel:

  1. Enigo por la nomo de nia retronombrado
  2. Enigo por la priskribo de nia retronombrado
  3. Enigo por la dato al kiu ni kalkulas malsupren, kiu devas esti provizita en formato YYYY-MM-DD

Ene de ĉiu el ĉi tiuj Controls.Textfield elementoj, ni fiksas Kirigami.FormData.label proprecon kiu ebligas al ni difini etikedojn por ilin. La formularo prezentos la ĝustajn etikedojn maldekstre de ĉiu el ĉi tiuj tekstaj enigokampoj.

Fine, ni ankaŭ fiksas la onAccepted proprecon por ekigi la [forceActiveFocus()](https:/ /doc.qt.io/qt-6/qml-qtquick-item.html#forceActiveFocus-method) metodo de la sekva kampo; ĉi tio ŝanĝos la aktivan kampon post kiam la uzanto trafos la ENTER-klavon, plibonigante la uzeblecon de la formo.

Ni ankaŭ starigis proprecon nomitan inputMask sur la teksta kampo por nia dato. Agordi ĉi tion al D999-99-99 malhelpas uzantojn enigi ion, kio povus rompi la funkciecon de la aplikaĵo (kiel teksto), limigante ilin nur enigi ciferojn, kiujn ni tiam povas provi analizi en datan objekton.

Post kiam la uzantinterfaco por la dialogo estas farita, ni devas ŝanĝi kiel ĝi kondutas. Por tio ni bezonas tri aferojn:

  1. Montri la Ok-butonon nur kiam la postulataj kampoj estas plenigitaj
  2. Aldoni la enigajn informojn al la modelo
  3. Forigi la enigformularon
Kirigami.Dialog {
    // ... 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);
        // () => estas JavaScript-sago-funkcio
        button.enabled = Qt.binding( () => requiredFieldsFilled() );
    }
    onAccepted: {
        // La ligado estas kreita, sed ni ankoraŭ devas igi ĝin neklakebla krom se la kampoj estas
        // plenigitaj
        if (!addDialog.requiredFieldsFilled()) return;
        appendDataToModel();
        clearFieldsAndClose();
    }
}

La unua afero, kiu devas esti farita, estas krei ligon inter la [enabled] de la OK butono (https://doc.qt.io/qt-6/qml-qtquick-controls-popup.html#enabled-prop) propreco kaj kontrolo ĉu la kampoj estas plenigitaj, kio ĉi-kaze devas esti farita per [Qt.binding()](https://doc.qt.io/qt-6/qtqml-syntax-propertybinding.html#creating- property-bindings-from-javascript) en JavaScript. Efektive, la linio:

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

estas simila al la QML-ligoj, kiujn ni vidis ĝis nun, kiel en la sekva pseŭdokodo:

enabled: requiredFieldsFilled()

La signala prizorganto, kiu ekigas la Ok-butonon, estas onAccepted. Ĝi restas malplena kaj sen fari ion se la postulataj kampoj estas plenigitaj; alie, ĝi aldonos la enigon al la modelo kaj malplenigos la dialogon por la venonta fojo, kiam ĝi estos malfermita.

Kirigami.Dialog {
    // ... Ni kontrolas, ke la nomKampo ne estas malplena kaj ke la datKampo (kiu havas enigMaskon)
    // estas tute plenigita
    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();
    }
}

Por nia postulata nomkampo, ĉio, kion ni devas fari, estas kontroli ĉu la kampoteksto estas malplena ĉeno. Por la data kampo, ĉar ĝi havas enigmaskon, ni devas uzi acceptableInput anstataŭe, kiu nur fariĝas vera post kiam la tuta kampo estas plenigita kaj enhavas nur akcepteblajn signojn.

Tiam, la append() metodo de nia lista modelo kountdownModel aldonas JavaScript-objekton inkluzive de la proprecoj, kiujn ni disponigis.

Finfine, ni certigas forigi la tekstajn kampojn agordante iliajn text ecojn al malplena ĉeno, tiam close() ĝi.

Post kiam ni konservos niajn dosierojn kaj konstruos nian programon, ni povos aldoni niajn proprajn proprajn retronombradojn! Ni povas fari la lastan tuŝon por plibonigi la interfacon, nome forigi la falsan retronombradon, kiun ni havis en la antaŭaj lecionoj:

26
27
28
    ListModel {
        id: kountdownModel
    }

Secondly now that we have an actual date to play around with, we can calculate the time until said date:

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

And thirdly increase the window size so that we have more room for our new cards:

 9
10
    width: 600
    height: 400

Multe pli bela.

Nia aplikaĵo ĝis nun

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

Ekrankopio de la aplikaĵo kun kvar ekzemplokartoj