Afegir un diàleg

Abordar a fons els diàlegs del Kirigami.

Fer que la nostra aplicació sigui útil

Tenim una finestra, tenim targetes i tenim accions. No obstant això, encara necessitem trobar alguna forma d'introduir un nom, una descripció i una data de la nostra elecció.

Una manera en què podríem fer això és creant una pàgina nova on col·loquem els elements d'introducció requerits. No obstant això, una pàgina sencera dedicada a proporcionar un nom, una descripció i una data sembla una mica excessiu.

En el seu lloc, utilitzarem un diàleg.

Diàleg que apareix al mig de l'aplicació

Obrir el diàleg

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

Primer editarem l'acció de la guia d'aprenentatge anterior: només una Kirigami.Action que activi la funció open() del diàleg.

Diàlegs que afegeixen un compte enrere

El component nou que afegim és un Kirigami.Dialog. Els diàlegs apareixen al centre de la finestra i es poden utilitzar per a proporcionar informació addicional rellevant per al contingut actual. No es poden moure, però adapten la seva mida a la finestra.

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

        // Les disposicions de formulari ajuden a alinear i estructurar una disposició amb diverses
        // entrades
        Kirigami.FormLayout {
            // Els camps de text permeten introduir text en un quadre de text fi
            Controls.TextField {
                id: nameField
                // Proporciona una etiqueta adjuntada al camp de text
                Kirigami.FormData.label: i18nc("@label:textbox", "Name*:")
                // Què s'ha de fer després d'acceptar l'entrada (p. ex. es prem Retorn) En aquest
                // cas, mou el focus al camp següent
                onAccepted: descriptionField.forceActiveFocus()
            }
            Controls.TextField {
                id: descriptionField
                Kirigami.FormData.label: i18nc("@label:textbox", "Description:")
                placeholderText: i18n("Optional")
                // De nou, mou el focus al camp següent
                onAccepted: dateField.forceActiveFocus()
            }
            Controls.TextField {
                id: dateField
                Kirigami.FormData.label: i18nc("@label:textbox", "ISO Date*:")
                // D significa un nombre necessari entre 1-9, 9 significa un nombre necessari entre
                // 0-9
                inputMask: "D999-99-99"
                // Aquí confirmem l'operació igual que fer clic al botó D'acord
                onAccepted: addDialog.onAccepted()
            }
            Controls.Label {
                text: "* = required fields"
            }
        }
        // La lògica del diàleg va aquí
    }
    // ...
}

De manera predeterminada, els diàlegs tenen una header (capçalera) i un footer (peu), tots dos heretats de Controls.Dialog.

La capçalera per defecte inclou un title (títol) i un botó de tancament que es pot desactivar amb showCloseButton. El peu predeterminat inclou un botó de tancament, i es pot anul·lar amb standardButtons.

Primer, l'establim per a mostrar un botó «Ok» i un botó «Cancel·la», afegir algun farciment i afegir una preferredWidth. L'amplada preferida és la mida esperada predeterminada del diàleg, que pot augmentar si cal. Podem utilitzar Kirigami.Units que tornarem a revisar més endavant.

Després arribem a un Kirigami.FormLayout. A diferència del ColumnLayout, la disposició dels seus components fills és automàtica i centrada, amb etiquetes opcionals. Com el nom indica, s'utilitza per crear formularis d'entrada.

Aquestes disposicions de formulari estan dissenyades per a funcionar amb diversos tipus d'entrada diferents, encara que ens cenyim a entrades senzilles Controls.Textfield que ens brinden quadres de text senzills per a escriure-hi.

Hem creat elements Textfield que actuen com:

  1. Entrada per al nom del nostre compte enrere
  2. Entrada per a la descripció del nostre compte enrere
  3. Entrada per a la data cap a la qual estem comptant cap enrere, la qual s'ha de proporcionar en el format YYYY-MM-DD

Dins de cadascun d'aquests elements Controls.TextField, estem establint una propietat Kirigami.FormData.label que permet definir etiquetes per a ells. El formulari presentarà les etiquetes correctes a l'esquerra de cadascun d'aquests camps d'entrada de text.

Finalment, també estem establint la propietat onAccepted per a activar el mètode forceActiveFocus() del camp següent; això canviarà el camp actiu una vegada que l'usuari prem la tecla Retorn, millorant la usabilitat del formulari.

També hem establert una propietat anomenada inputMask en el camp de text per a la nostra data. Establint això a D999-99-9 evitarà que els usuaris introdueixin alguna cosa que pugui trencar la funcionalitat de l'aplicació (com ara text), restringint-lo a només la introducció de dígits, després podrem intentar analitzar-ho en un objecte de data.

Un cop feta la interfície d'usuari del diàleg, hem de canviar com es comporta. Per a això necessitem tres coses:

  1. Mostrar el botó Ok només quan s'omplin els camps requerits
  2. Afegir la informació d'entrada al model
  3. Netejar el formulari d'entrada
Kirigami.Dialog {
    // ...Un cop el Kirigami.Dialog està inicialitzat, volem crear una vinculació personalitzada per
    // a només fer visible el botó Ok si s'omplen els camps de text requerits. Per a això utilitzem
    // el Kirigami.Dialog.standardButton(button):
    Component.onCompleted: {
        const button = standardButton(Kirigami.Dialog.Ok);
        // () => és una funció de fletxa del JavaScript
        button.enabled = Qt.binding( () => requiredFieldsFilled() );
    }
    onAccepted: {
        // Es crea el vincle, però encara cal fer-lo no clicable a menys que s'omplin els camps
        if (!addDialog.requiredFieldsFilled()) return;
        appendDataToModel();
        clearFieldsAndClose();
    }
}

El primer que s'ha de fer és crear una vinculació entre la propietat activada del botó OK i una comprovació de si els camps s'omplen, que en aquest cas s'ha de fer amb Qt.binding() en JavaScript. En efecte, la línia:

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

és similar a les vinculacions en QML que hem vist fins ara, com en el pseudocodi següent:

enabled: requiredFieldsFilled()

El gestor de senyal que activa el botó Ok és onAccepted. Es manté buit i sense fer res si s'omplen els camps requerits; en cas contrari, afegirà l'entrada al model i netejarà el diàleg per a la propera vegada que s'obri.

Kirigami.Dialog {
    // ...Comprovem que el «nameField» no estigui buit i que el «dateField» (que té una «inputMask»)
    // estigui completament omplert
    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();
    }
}

Per al nostre camp de nom requerit, tot el que hem de fer és comprovar si el text del camp és una cadena buida. Per al camp de data, com que té una màscara d'entrada, cal utilitzar acceptableInput en el seu lloc, el qual només serà cert una vegada que tot el camp s'ompli i només contingui caràcters acceptables.

Llavors, el mètode append() del nostre model de llista kountdownModel afegeix un objecte JavaScript incloent-hi les propietats que hem proporcionat.

Per acabar, ens assegurem, de netejar els camps de text definint les propietats del seu text a una cadena buida, després fem un close().

Un cop desem els fitxers i construïm el programa, podrem afegir els nostres propis comptes enrere personalitzats! Podem fer un últim toc per a millorar la interfície, és a dir, eliminar el compte enrere fictici que teníem a les lliçons anteriors:

26
27
28
    ListModel {
        id: kountdownModel
    }

En segon lloc, ara que tenim una data real amb la qual jugar, podem calcular el temps fins a aquesta data:

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

I en tercer lloc, augmentar la mida de la finestra perquè tinguem més espai per a les targetes noves:

 9
10
    width: 600
    height: 400

Molt millor.

La nostra aplicació fins ara

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

Captura de pantalla de l'aplicació amb quatre targetes d'exemple