Dodajanje pogovornega okna

Spoznavanje pogovornih oken Kirigami.

Izdelava naše aplikacije uporabne

Imamo okno, kartice in akcije. Vendar moramo še vedno najti način vnosa imena, opisa in datuma po naši izbiri.

To lahko storimo tako, da ustvarimo novo stran, kjer postavimo zahtevane vhodne elemente. Vendar pa se zdi cela stran, namenjena zagotavljanju imena, opisa in datuma, nekoliko pretirana.

Namesto tega bomo uporabili pogovorno okno.

Pogovorno okno se pojavi na sredini aplikacije

Odpiranje pogovornega okna

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

Najprej uredimo dejanje iz prejšnje vadnice: samo Kirigami.Action, ki sproži funkcijo pogovornega okna open().

Pogovorna okna za dodajanje odštevanja

Nova komponenta, ki jo dodamo, je Kirigami.Dialog. Pogovorna okna se prikažejo na sredini okna in jih je mogoče uporabiti za zagotavljanje dodatnih informacij, ki so pomembne za trenutno vsebino. Ni jih mogoče premikati, ampak se po svoji velikosti prilagajajo oknu.

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

        // Postavitve obrazcev pomagajo uskladiti in strukturirati postavitev z več vnosi
        Kirigami.FormLayout {
            // Textfields vam omogočajo vnos besedila v tanko besedilno polje
            Controls.TextField {
                id: nameField
                // Zagotavlja oznako, pripeto besedilnemu polju
                Kirigami.FormData.label: i18nc("@label:textbox", "Name*:")
                // Kaj storiti, ko je vnos sprejet (tj. pritisnjen Enter) V tem primeru premakne
                // fokus na naslednje polje
                onAccepted: descriptionField.forceActiveFocus()
            }
            Controls.TextField {
                id: descriptionField
                Kirigami.FormData.label: i18nc("@label:textbox", "Description:")
                placeholderText: i18n("Optional")
                // Spet premakne fokus na naslednje polje
                onAccepted: dateField.forceActiveFocus()
            }
            Controls.TextField {
                id: dateField
                Kirigami.FormData.label: i18nc("@label:textbox", "ISO Date*:")
                // D pomeni zahtevano število med 1-9, 9 pomeni zahtevano število med 0-9
                inputMask: "D999-99-99"
                // Tukaj potrdimo operacijo tako kot s klikom na gumb V redu
                onAccepted: addDialog.onAccepted()
            }
            Controls.Label {
                text: "* = required fields"
            }
        }
        // Logika dialoga gre sem
    }
    // ...
}

Pogovorna okna imajo privzeto glavo header in nogo [footer](https://doc.qt .io/qt-6/qml-qtquick-controls-dialog.html#footer-prop), oba podedovana iz Controls.Dialog.

Glava privzeto vključuje naslov title in gumb za zapiranje, ki ga je mogoče onemogočiti s [showCloseButton] (docs:kirigami2;Dialog::showCloseButton). Noga privzeto vključuje gumb za zapiranje in ga je mogoče preglasiti s standardButtons.

Najprej smo ga nastavili tako, da prikaže gumb »V redu« in gumb »Prekliči«, dodali nekaj oblazinjenja in dodali razumno širino preferredWidth. Želena širina je privzeta pričakovana velikost pogovornega okna, ki se lahko po potrebi poveča. Uporabimo lahko standardne Kirigami.Units, ki jih bomo ponovno pregledali pozneje.

Nato pridemo do Kirigami.FormLayout. Za razliko od ColumnLayout je postavitev njegovih podrejenih komponent samodejna in na sredini, z neobveznimi oznakami. Kot že ime pove, se uporablja za ustvarjanje vnosnih obrazcev.

Te postavitve obrazcev so zasnovane tako, da delujejo z različnimi vrstami vnosa, čeprav se držimo preprostih vnosov Controls.Textfield, ki nam dajejo preprosta besedilna polja, v katera lahko pišemo stvari.

Ustvarili smo elemente Textfield, ki delujejo kot:

  1. Vnos za ime našega odštevanja
  2. Vnos za opis našega odštevanja
  3. Vnos za datum, ki ga odštevamo proti vrednosti, ki jo je treba navesti v formatu 'YYYY-MM-DD'

Znotraj vsakega od teh elementov Controls.Textfield nastavimo lastnost Kirigami.FormData.label, ki nam omogoča definiranje oznak zanj. Obrazec bo prikazal pravilne oznake na levi strani vsakega od teh polj za vnos besedila.

Nazadnje nastavimo tudi lastnost onAccepted, da sproži [forceActiveFocus()](https:/ /doc.qt.io/qt-6/qml-qtquick-item.html#forceActiveFocus-method) metodo naslednjega polja; to bo preklopilo aktivno polje, ko uporabnik pritisne tipko ENTER, kar izboljša uporabnost obrazca.

Prav tako smo nastavili lastnost z imenom inputMask v besedilnem polju za naš datum. Če to nastavite na D999-99-99, preprečite uporabnikom, da bi vnesli nekaj, kar bi lahko pokvarilo funkcionalnost aplikacije (kot je besedilo), in jih omejite na vnos samo številk, ki jih lahko nato poskusimo razčleniti v objekt datuma.

Ko je uporabniški vmesnik za pogovorno okno končan, moramo spremeniti njegovo obnašanje. Za to potrebujemo tri stvari:

  1. Pokaži gumb V redu samo, ko so izpolnjena zahtevana polja
  2. Dodajte vhodne informacije v model
  3. Počistite obrazec za vnos
Kirigami.Dialog {
    // ... Ko je Kirigami.Dialog inicializiran, želimo ustvariti vezavo po meri, da bo gumb V redu
    // viden le, če so izpolnjena zahtevana besedilna polja. Za to uporabljamo
    // Kirigami.Dialog.standardButton(button):
    Component.onCompleted: {
        const button = standardButton(Kirigami.Dialog.Ok);
        // () => je funkcija JavaScript puščice
        button.enabled = Qt.binding( () => requiredFieldsFilled() );
    }
    onAccepted: {
        // Vezava je ustvarjena, vendar moramo še vedno onemogočiti klikanje, razen če so polja
        // izpolnjena
        if (!addDialog.requiredFieldsFilled()) return;
        appendDataToModel();
        clearFieldsAndClose();
    }
}

Prva stvar, ki jo je treba narediti, je ustvariti povezavo med lastnostjo enabled gumba OK in preverjanje, ali so polja izpolnjena, kar je v tem primeru treba narediti s [Qt.binding()]Qt.binding() v JavaScript. Dejansko je vrstica:

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

podobna vezavam QML, ki smo jih videli do sedaj, kot v naslednji psevdo kodi:

enabled: requiredFieldsFilled()

Upravljalnik signalov, ki sproži gumb V redu, je onAccepted. Če so zahtevana polja izpolnjena, ostane prazen in ne naredi ničesar; v nasprotnem primeru bo dodal vnos v model in počistil pogovorno okno za naslednje odprtje.

Kirigami.Dialog {
    // ... Preverimo, da nameField ni prazno in da je dateField (ki ima inputMask) v celoti
    // izpolnjeno
    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();
    }
}

Za naše zahtevano polje z imenom moramo le preveriti, ali je besedilo polja prazen niz. Ker ima polje z datumom vnosno masko, moramo namesto tega uporabiti acceptableInput, kar samo postane true, ko je celotno polje izpolnjeno in vsebuje samo sprejemljive znake.

Nato metoda append() našega modela seznama kountdownModel doda objekt JavaScript, ki vključuje lastnosti, ki smo jih zagotovili.

Na koncu poskrbimo, da počistimo besedilna polja tako, da nastavimo njihove lastnosti text na prazen niz, nato ga zapremo s close().

Ko shranimo svoje datoteke in zgradimo svoj program, bomo lahko dodali lastna odštevanja po meri! Za izboljšanje vmesnika lahko naredimo še zadnji dotik, in sicer odstranimo navidezno odštevanje, ki smo ga imeli v prejšnjih lekcijah:

26
27
28
    ListModel {
        id: kountdownModel
    }

Drugič, zdaj, ko imamo dejanski datum, s katerim se lahko igramo, lahko izračunamo čas do omenjenega datuma:

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

In tretjič, povečamo velikost okna, da bomo imeli več prostora za naše nove kartice:

 9
10
    width: 600
    height: 400

Veliko lepše.

Naša aplikacija do sedaj

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

Posnetek zaslona aplikacije s primerom štirih kartic